You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何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: &lt;&amp;generic_array::GenericArray&lt;T,N&gt; as core::convert::From&lt;&amp;[T]&gt;&gt;::from
             at /home/lex/.cargo/registry/src/github.com-1ecc6299db9ec823/generic-array-0.14.6/src/lib.rs:565:9
   5: &lt;T as core::convert::Into&lt;U&gt;&gt;::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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.12 20:11:27