encryptionrustnoncerust-crypto

Why XChaCha20Poly1305 does not accept 24 byte nonce when used as AEAD stream?


XChaCha20Poly1305 should utilise a 24 byte nonce, but it requires a 19 byte nonce when used as AEAD stream. The same situation with Aes256Gcm which requires a 7 byte nonce instead of declared 12 bytes. I have very limited knowledge of cryptography. I want to figure out: do I missunderstand crypto nonce and AEAD stream or do I missuse Rust libs?

[dependencies]
aead = { version = "0.5.1", features = ["getrandom"] }
chacha20poly1305 = { version = "0.10.1", features = ["stream"] }
aes-gcm = "0.10.1"
use std::{ fs::File, io::{ Write, Read, Error as ErrorIo } };

use aead::{ stream, KeyInit, OsRng, Error as ErrorAead, rand_core::RngCore };
use chacha20poly1305::XChaCha20Poly1305;
// use aes_gcm::Aes256Gcm;

const BUFFER_LEN_ENC: usize = 500;
const BUFFER_LEN_DEC: usize = BUFFER_LEN_ENC + 16;

fn main() -> Result<(), Error> {
    let mut key = [0u8; 32];
    let mut nonce = [0u8; 24]; // no error with [0u8; 19]
    OsRng.fill_bytes(&mut key);
    OsRng.fill_bytes(&mut nonce);

    let path_raw = "src.txt";
    let path_enc = "enc";
    let path_dec = "dec.txt";

    encrypt(&key, &nonce, path_raw, path_enc)?;
    decrypt(&key, &nonce, path_enc, path_dec)?;

    Ok(())
}

fn encrypt(key: &[u8], nonce: &[u8], path_src: &str, path_dst: &str) -> Result<(), Error> {
    let aead = XChaCha20Poly1305::new(key[..32].as_ref().into());
    let mut stream_encryptor = stream::EncryptorBE32::from_aead(aead, nonce.as_ref().into());
    let mut src = File::open(path_src).map_err(Error::make_io)?;
    let mut dst = File::create(path_dst).map_err(Error::make_io)?;
    let mut buffer = [0u8; BUFFER_LEN_ENC];
    loop {
        let read_count = src.read(&mut buffer).map_err(Error::make_io)?;
        if read_count == BUFFER_LEN_ENC {
            let ciphertext = stream_encryptor
                .encrypt_next(buffer.as_slice())
                .map_err(Error::make_aead)?;
                dst.write(&ciphertext).map_err(Error::make_io)?;
        } else {
            let ciphertext = stream_encryptor
                .encrypt_last(&buffer[..read_count])
                .map_err(Error::make_aead)?;
                dst.write(&ciphertext).map_err(Error::make_io)?;
            break;
        }
    }
    Ok(())
}

fn decrypt(key: &[u8], nonce: &[u8], path_src: &str, path_dst: &str) -> Result<(), Error> {
    let aead = XChaCha20Poly1305::new(key[..32].as_ref().into());
    let mut stream_decryptor = stream::DecryptorBE32::from_aead(aead, nonce.as_ref().into());
    let mut src = File::open(path_src).map_err(Error::make_io)?;
    let mut dst = File::create(path_dst).map_err(Error::make_io)?;
    let mut buffer = [0u8; BUFFER_LEN_DEC];
    loop {
        let read_count = src.read(&mut buffer).map_err(Error::make_io)?;
        if read_count == BUFFER_LEN_DEC {
            let plaintext = stream_decryptor
                .decrypt_next(buffer.as_slice())
                .map_err(Error::make_aead)?;
                dst.write(&plaintext).map_err(Error::make_io)?;
        } else if read_count == 0 {
            break;
        } else {
            let plaintext = stream_decryptor
                .decrypt_last(&buffer[..read_count])
                .map_err(Error::make_aead)?;
                dst.write(&plaintext).map_err(Error::make_io)?;
            break;
        }
    }
    Ok(())
}


#[derive(Debug)]
enum Error {
    Io(ErrorIo),
    Aead(ErrorAead),
}

impl Error {
    fn make_io(err: ErrorIo) -> Self { Self::Io(err) }
    fn make_aead(err: ErrorAead) -> Self { Self::Aead(err) }
}
thread 'main' panicked at 'assertion failed: `(left == right)`
  left: `24`,
 right: `19`', /home/lex/.cargo/registry/src/github.com-1ecc6299db9ec823/generic-array-0.14.6/src/lib.rs:565:9
stack backtrace:
   0: rust_begin_unwind
             at /rustc/a55dd71d5fb0ec5a6a3a9e8c27b2127ba491ce52/library/std/src/panicking.rs:584:5
   1: core::panicking::panic_fmt
             at /rustc/a55dd71d5fb0ec5a6a3a9e8c27b2127ba491ce52/library/core/src/panicking.rs:142:14
   2: core::panicking::assert_failed_inner
   3: core::panicking::assert_failed
             at /rustc/a55dd71d5fb0ec5a6a3a9e8c27b2127ba491ce52/library/core/src/panicking.rs:181:5
   4: <&generic_array::GenericArray<T,N> as core::convert::From<&[T]>>::from
             at /home/lex/.cargo/registry/src/github.com-1ecc6299db9ec823/generic-array-0.14.6/src/lib.rs:565:9
   5: <T as core::convert::Into<U>>::into
             at /rustc/a55dd71d5fb0ec5a6a3a9e8c27b2127ba491ce52/library/core/src/convert/mod.rs:550:9
   6: crydec::encrypt
             at ./src/main.rs:28:71
   7: crydec::main
             at ./src/main.rs:20:5
   8: core::ops::function::FnOnce::call_once
             at /rustc/a55dd71d5fb0ec5a6a3a9e8c27b2127ba491ce52/library/core/src/ops/function.rs:248:5
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.

Solution

  • The last 5 bytes of the nonce are used for a 32 bits counter, and a 1-byte "last block" flag, so we only need to generate 19 (or in general nonce_size - 5) bytes of random data.
    https://docs.rs/aead/latest/aead/stream/struct.StreamBE32.html