From 3c0532b4c5b1a08fb23329f41fa16ad7ad42c7f5 Mon Sep 17 00:00:00 2001 From: lbl <1791778603@qq.com> Date: Thu, 20 Aug 2026 21:33:37 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=8A=A0=E5=AF=86=20nonce=20?= =?UTF-8?q?=E5=85=A8=E5=B1=80=E9=87=8D=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PacketCrypto 的 nonce 由 msg_type+seq+src+dst 构成,但 seq 字段从未赋值, 同一 (msg_type,src,dst) 的所有包共用同一 nonce,AEAD 密钥流重用。 - PacketCrypto 增加 Arc 出站序号计数器(随机起始值,避免 进程重启后相同密钥复用低序号段),encrypt_in_place 时自动分配 seq - 补充 nonce 唯一性与 clone 共享计数器的测试 --- vnt-core/src/crypto/chacha20_poly1305.rs | 56 +++++++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) diff --git a/vnt-core/src/crypto/chacha20_poly1305.rs b/vnt-core/src/crypto/chacha20_poly1305.rs index 633a4d0..424da68 100644 --- a/vnt-core/src/crypto/chacha20_poly1305.rs +++ b/vnt-core/src/crypto/chacha20_poly1305.rs @@ -1,12 +1,17 @@ use crate::protocol::ip_packet_protocol::{HEAD_LENGTH, NetPacket}; use ring::aead::{Aad, CHACHA20_POLY1305, LessSafeKey, Nonce, UnboundKey}; use std::io; +use std::sync::Arc; +use std::sync::atomic::{AtomicU32, Ordering}; pub const TAG_LEN: usize = 16; #[derive(Clone)] pub struct PacketCrypto { key: LessSafeKey, + /// 出站包序号,用于构造唯一 nonce。Clone 共享同一计数器。 + /// 随机起始值可避免进程重启后(相同密钥)复用低序号段的 nonce。 + seq: Arc, } impl PacketCrypto { @@ -31,7 +36,10 @@ impl PacketCrypto { pub fn new(key_bytes: [u8; 32]) -> Self { let unbound = UnboundKey::new(&CHACHA20_POLY1305, &key_bytes).unwrap(); let key = LessSafeKey::new(unbound); - Self { key } + Self { + key, + seq: Arc::new(AtomicU32::new(rand::random())), + } } pub fn new_from_str(s: &str) -> Self { let hash = ring::digest::digest(&ring::digest::SHA256, s.as_bytes()); @@ -69,6 +77,10 @@ impl PacketCrypto { &self, pkt: &mut NetPacket, ) -> io::Result<()> { + // 为每个出站包分配递增 seq,保证 (msg_type, src, dst) 相同包之间 nonce 不重复 + // (nonce 中 seq 占 3 字节,同一四元组约 1600 万个包后才会回绕) + let seq = self.seq.fetch_add(1, Ordering::Relaxed); + pkt.set_seq(seq); let nonce = Nonce::assume_unique_for_key(self.make_nonce(pkt)?); let payload = pkt.payload_mut(); @@ -166,4 +178,46 @@ mod tests { // 解密后与原文一致 assert_eq!(decrypted_payload, &original_payload[..]); } + + #[test] + fn test_nonce_unique_per_packet() { + let crypto = PacketCrypto::new([7u8; 32]); + + let mut pkt1 = build_test_packet(20); + let mut pkt2 = build_test_packet(20); + + let nonce1 = crypto.make_nonce(&pkt1).unwrap(); + crypto.encrypt_in_place(&mut pkt1).expect("encrypt failed"); + crypto.encrypt_in_place(&mut pkt2).expect("encrypt failed"); + let nonce2 = crypto.make_nonce(&pkt1).unwrap(); + let nonce3 = crypto.make_nonce(&pkt2).unwrap(); + + // 加密会自动分配递增 seq,两个相同头部的包 nonce 必须不同 + assert_eq!(pkt1.seq() + 1, pkt2.seq()); + assert_ne!(nonce1, nonce2); + assert_ne!(nonce2, nonce3); + // 密文也必须不同(相同明文、不同 nonce) + assert_ne!(pkt1.buffer(), pkt2.buffer()); + + // 两个包都能正常解密(头部 seq 不同,只比较 payload 区域) + crypto.decrypt_in_place(&mut pkt1).expect("decrypt failed"); + crypto.decrypt_in_place(&mut pkt2).expect("decrypt failed"); + assert_eq!( + &pkt1.buffer()[HEAD_LENGTH..HEAD_LENGTH + 20], + &pkt2.buffer()[HEAD_LENGTH..HEAD_LENGTH + 20] + ); + } + + #[test] + fn test_clone_shares_seq_counter() { + let crypto = PacketCrypto::new([9u8; 32]); + let cloned = crypto.clone(); + + let mut pkt1 = build_test_packet(8); + let mut pkt2 = build_test_packet(8); + crypto.encrypt_in_place(&mut pkt1).expect("encrypt failed"); + cloned.encrypt_in_place(&mut pkt2).expect("encrypt failed"); + + assert_eq!(pkt1.seq() + 1, pkt2.seq()); + } }