Skip to main content

encryption/encryptor/
aes256gcm_siv.rs

1// Copyright (c) 2026 erik <erik@erik.xyz> — https://erik.xyz
2
3//! AES-256-GCM-SIV(RFC 8452,误用稳健 AEAD);载荷:`v1 | Nonce(12) | Tag(16) | 密文`。
4//!
5//! 与 `aes-256-gcm` 的区别:GCM-SIV 用合成 IV(先以 nonce + 明文派生 tag,再以
6//! tag 作为 CTR 计数器)——nonce 复用不会像 GCM 那样泄露异或与认证密钥,代价是
7//! 每帧多一次 AES 调用。适合 nonce 唯一性难以保证的场景。
8
9use aes_gcm_siv::aead::AeadInPlace;
10use aes_gcm_siv::{Aes256GcmSiv, KeyInit, Nonce, Tag};
11use rand_core::{OsRng, RngCore};
12
13use crate::contract::{Identified, SymmetricCipher};
14use crate::error::{Error, Result};
15use crate::key::Key;
16
17const LABEL: &str = "AES-256-GCM-SIV";
18const PREFIX: &[u8] = b"v1";
19const NONCE_LEN: usize = 12;
20const TAG_LEN: usize = 16;
21
22/// AES-256-GCM-SIV 加密器。
23#[derive(Debug)]
24pub struct Aes256GcmSivEncryptor {
25    key: Key<32>,
26}
27
28impl Aes256GcmSivEncryptor {
29    pub const IDENTIFIER: &'static str = "aes-256-gcm-siv";
30
31    pub fn new(key: [u8; 32]) -> Self {
32        Self { key: Key::new(key) }
33    }
34
35    pub fn from_slice(key: &[u8]) -> Result<Self> {
36        Ok(Self {
37            key: Key::from_slice(key, LABEL)?,
38        })
39    }
40
41    /// 显式 nonce 的加密,返回完整载荷(`v1 | Nonce | Tag | 密文`)。
42    ///
43    /// 生产路径只经由 [`SymmetricCipher::encrypt`] 调用;独立的 nonce 入口是
44    /// 为了让测试能对 RFC 8452 附录 C.2 的官方向量做逐字节比对。
45    pub(crate) fn encrypt_with_nonce(
46        &self,
47        nonce: &[u8; NONCE_LEN],
48        plaintext: &[u8],
49    ) -> Result<Vec<u8>> {
50        // Key<32> 保证长度,new_from_slice 不会失败。
51        let cipher =
52            Aes256GcmSiv::new_from_slice(self.key.as_bytes()).expect("key length is fixed at 32");
53
54        let mut buffer = plaintext.to_vec();
55        let tag = cipher
56            .encrypt_in_place_detached(Nonce::from_slice(nonce), b"", &mut buffer)
57            .map_err(|_| Error::EncryptionFailed { label: LABEL })?;
58
59        let mut blob = Vec::with_capacity(PREFIX.len() + NONCE_LEN + TAG_LEN + buffer.len());
60        blob.extend_from_slice(PREFIX);
61        blob.extend_from_slice(nonce);
62        blob.extend_from_slice(&tag);
63        blob.extend_from_slice(&buffer);
64        Ok(blob)
65    }
66}
67
68impl Identified for Aes256GcmSivEncryptor {
69    fn identifier(&self) -> &str {
70        Self::IDENTIFIER
71    }
72}
73
74impl SymmetricCipher for Aes256GcmSivEncryptor {
75    fn encrypt(&self, plaintext: &[u8]) -> Result<Vec<u8>> {
76        let mut nonce = [0u8; NONCE_LEN];
77        OsRng.fill_bytes(&mut nonce);
78        self.encrypt_with_nonce(&nonce, plaintext)
79    }
80
81    fn decrypt(&self, ciphertext: &[u8]) -> Result<Vec<u8>> {
82        let body = ciphertext
83            .strip_prefix(PREFIX)
84            .ok_or(Error::InvalidPrefix { label: LABEL })?;
85        if body.len() < NONCE_LEN + TAG_LEN {
86            return Err(Error::TooShort { label: LABEL });
87        }
88
89        let (nonce, rest) = body.split_at(NONCE_LEN);
90        let (tag, ct) = rest.split_at(TAG_LEN);
91
92        let cipher =
93            Aes256GcmSiv::new_from_slice(self.key.as_bytes()).expect("key length is fixed at 32");
94        let mut buffer = ct.to_vec();
95        cipher
96            .decrypt_in_place_detached(
97                Nonce::from_slice(nonce),
98                b"",
99                &mut buffer,
100                Tag::from_slice(tag),
101            )
102            .map_err(|_| Error::DecryptionFailed { label: LABEL })?;
103        Ok(buffer)
104    }
105}
106
107#[cfg(test)]
108mod tests {
109    use super::*;
110    use crate::internal::hex_decode;
111
112    // 官方向量:RFC 8452 附录 C.2(AEAD_AES_256_GCM_SIV),逐字节誊自
113    // RFC 文本 www.rfc-editor.org/rfc/rfc8452.txt(2026-10 取用)。
114    // 该附录的 Result 是「密文 ‖ Tag」,本库帧把 Tag 提前(见文件头)。
115    const KEY: &str = "0100000000000000000000000000000000000000000000000000000000000000";
116    const NONCE: &str = "030000000000000000000000";
117
118    /// (明文, 密文, Tag) —— 附录 C.2 前四组向量(AAD 均为 0 字节)。
119    const VECTORS: &[(&str, &str, &str)] = &[
120        ("", "", "07f5f4169bbf55a8400cd47ea6fd400f"),
121        (
122            "0100000000000000",
123            "c2ef328e5c71c83b",
124            "843122130f7364b761e0b97427e3df28",
125        ),
126        (
127            "01000000000000000000000000000000",
128            "85a01b63025ba19b7fd3ddfc033b3e76",
129            "c9eac6fa700942702e90862383c6c366",
130        ),
131        (
132            "0100000000000000000000000000000002000000000000000000000000000000",
133            "4a6a9db4c8c6549201b9edb53006cba821ec9cf850948a7c86c68ac7539d027f",
134            "e819e63abcd020b006a976397632eb5d",
135        ),
136    ];
137
138    #[test]
139    fn kat_rfc_8452_appendix_c_2_is_byte_for_byte() {
140        let encryptor = Aes256GcmSivEncryptor::new(hex_decode(KEY).unwrap().try_into().unwrap());
141        let nonce: [u8; NONCE_LEN] = hex_decode(NONCE).unwrap().try_into().unwrap();
142
143        for &(pt_hex, ct_hex, tag_hex) in VECTORS {
144            let plaintext = hex_decode(pt_hex).unwrap();
145            let ct = hex_decode(ct_hex).unwrap();
146            let tag = hex_decode(tag_hex).unwrap();
147            // 先验证「无填充」:官方密文长度 == 官方明文长度。
148            assert_eq!(ct.len(), plaintext.len(), "plaintext {pt_hex}");
149
150            let mut expected = PREFIX.to_vec();
151            expected.extend_from_slice(&nonce);
152            expected.extend_from_slice(&tag);
153            expected.extend_from_slice(&ct);
154            let blob = encryptor.encrypt_with_nonce(&nonce, &plaintext).unwrap();
155            assert_eq!(blob, expected, "plaintext {pt_hex}");
156
157            // 反方向:官方密文帧(本库布局)解出官方明文。
158            assert_eq!(encryptor.decrypt(&blob).unwrap(), plaintext);
159        }
160    }
161
162    #[test]
163    fn roundtrip_and_tamper_detection() {
164        let encryptor = Aes256GcmSivEncryptor::new([7u8; 32]);
165        let blob = encryptor.encrypt(b"data").unwrap();
166        assert_eq!(blob.len(), 2 + NONCE_LEN + TAG_LEN + 4);
167        assert_eq!(encryptor.decrypt(&blob).unwrap(), b"data");
168
169        let mut tampered = blob.clone();
170        let last = tampered.len() - 1;
171        tampered[last] ^= 0x01;
172        assert_eq!(
173            encryptor.decrypt(&tampered).unwrap_err(),
174            Error::DecryptionFailed { label: LABEL }
175        );
176
177        // nonce 也被认证:改 nonce 位同样必败(GCM 里这会改变计数器起点)。
178        let mut nonce_tampered = blob.clone();
179        nonce_tampered[2] ^= 0x01;
180        assert_eq!(
181            encryptor.decrypt(&nonce_tampered).unwrap_err(),
182            Error::DecryptionFailed { label: LABEL }
183        );
184    }
185
186    #[test]
187    fn every_encrypt_uses_a_fresh_nonce() {
188        let encryptor = Aes256GcmSivEncryptor::new([7u8; 32]);
189        let first = encryptor.encrypt(b"same").unwrap();
190        let second = encryptor.encrypt(b"same").unwrap();
191        assert_ne!(first, second);
192        assert_ne!(first[2..2 + NONCE_LEN], second[2..2 + NONCE_LEN]);
193    }
194
195    #[test]
196    fn wrong_key_and_bad_prefix_fail() {
197        let blob = Aes256GcmSivEncryptor::new([7u8; 32]).encrypt(b"x").unwrap();
198        let other = Aes256GcmSivEncryptor::new([8u8; 32]);
199        assert_eq!(
200            other.decrypt(&blob).unwrap_err(),
201            Error::DecryptionFailed { label: LABEL }
202        );
203        assert_eq!(
204            other.decrypt(b"v9....").unwrap_err(),
205            Error::InvalidPrefix { label: LABEL }
206        );
207        assert_eq!(
208            other.decrypt(b"v1short").unwrap_err(),
209            Error::TooShort { label: LABEL }
210        );
211    }
212
213    #[test]
214    fn key_length_is_checked() {
215        assert_eq!(
216            Aes256GcmSivEncryptor::from_slice(&[0u8; 16]).unwrap_err(),
217            Error::InvalidKeyLength {
218                label: LABEL,
219                expected: 32,
220                got: 16
221            }
222        );
223    }
224}