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