修复加密 nonce 全局重用

PacketCrypto 的 nonce 由 msg_type+seq+src+dst 构成,但 seq 字段从未赋值,
同一 (msg_type,src,dst) 的所有包共用同一 nonce,AEAD 密钥流重用。

- PacketCrypto 增加 Arc<AtomicU32> 出站序号计数器(随机起始值,避免
  进程重启后相同密钥复用低序号段),encrypt_in_place 时自动分配 seq
- 补充 nonce 唯一性与 clone 共享计数器的测试
This commit is contained in:
lbl
2026-08-20 21:33:37 +08:00
parent 216cf16310
commit 3c0532b4c5
+55 -1
View File
@@ -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<AtomicU32>,
}
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<B>,
) -> 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());
}
}