为何XChaCha20Poly1305作为AEAD流时不支持24字节Nonce?
问题
XChaCha20Poly1305 本应使用24字节Nonce,但作为AEAD流使用时却要求19字节;Aes256Gcm也存在类似情况,本该使用12字节Nonce却要求7字节。我对密码学知识了解有限,想弄清楚:是我对加密Nonce和AEAD流的理解有误,还是误用了Rust加密库?
依赖配置
[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.
解答
这既不是你的理解错误,也不是库的误用,是aead库中流模式的设计逻辑导致的:
stream::EncryptorBE32/stream::DecryptorBE32会自动预留**5字节(32位大端格式)**作为流的块计数器,用来区分不同加密块的唯一nonce。- 因此用户需要提供的nonce长度 = 算法标准nonce长度 - 5字节:
- XChaCha20Poly1305标准nonce是24字节,24-5=19字节,对应你遇到的要求;
- AES-256-GCM标准nonce是12字节,12-5=7字节,就是你提到的另一种情况。
流模式的设计目的是简化大文件/大消息的加密:标准AEAD是单次加密完整消息,使用唯一nonce;而流模式把消息分块加密,每块需要唯一nonce,库自动用「用户提供的固定nonce + 递增计数器」生成每块的唯一nonce,避免用户手动管理计数器导致的nonce重复风险。
修正后的代码调整
只需要把nonce的长度改成对应流模式要求的即可:
// 对于XChaCha20Poly1305流模式 let mut nonce = [0u8; 19]; // 如果换成Aes256Gcm,就改成 // let mut nonce = [0u8; 7];
这样代码就能正常运行,不会再触发长度不匹配的panic。
内容的提问来源于stack exchange,提问作者Lex
相关产品推荐
相关产品推荐

