Skip to main content

encryption/encryptor/
aes256gcm.rs

1// Copyright (c) 2026 erik <erik@erik.xyz> — https://erik.xyz
2
3//! AES-256-GCM(认证加密);载荷:`v1 | IV(12) | Tag(16) | 密文`。
4
5use aes_gcm::aead::AeadInPlace;
6use aes_gcm::{Aes256Gcm, KeyInit, Nonce, Tag};
7use rand_core::{OsRng, RngCore};
8
9use crate::contract::{Identified, SymmetricCipher};
10use crate::error::{Error, Result};
11use crate::key::Key;
12
13const LABEL: &str = "AES-256-GCM";
14const PREFIX: &[u8] = b"v1";
15const IV_LEN: usize = 12;
16const TAG_LEN: usize = 16;
17
18/// AES-256-GCM 加密器。
19///
20/// 块密码实例在构造时就建立(轮密钥只展开一次),加密与解密都复用它——
21/// 短载荷(本库的典型场景)下,避免每次调用重跑一遍 AES 密钥编排。
22pub struct Aes256GcmEncryptor {
23    cipher: Aes256Gcm,
24}
25
26impl Aes256GcmEncryptor {
27    pub const IDENTIFIER: &'static str = "aes-256-gcm";
28
29    pub fn new(key: [u8; 32]) -> Self {
30        Self {
31            cipher: Aes256Gcm::new_from_slice(&key).expect("key length is fixed at 32"),
32        }
33    }
34
35    pub fn from_slice(key: &[u8]) -> Result<Self> {
36        // 借 Key<N> 做长度校验;临时副本随其 Drop 清零。
37        let key = Key::<32>::from_slice(key, LABEL)?;
38        Ok(Self {
39            cipher: Aes256Gcm::new_from_slice(key.as_bytes()).expect("key length is fixed at 32"),
40        })
41    }
42}
43
44/// 只打印标识符——密钥材质不出现在 `Debug` 里
45/// (`Aes256Gcm` 未实现 `Debug`,此类型无法 derive)。
46impl std::fmt::Debug for Aes256GcmEncryptor {
47    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
48        f.debug_struct("Aes256GcmEncryptor")
49            .field("identifier", &Self::IDENTIFIER)
50            .finish_non_exhaustive()
51    }
52}
53
54impl Identified for Aes256GcmEncryptor {
55    fn identifier(&self) -> &str {
56        Self::IDENTIFIER
57    }
58}
59
60impl SymmetricCipher for Aes256GcmEncryptor {
61    fn encrypt(&self, plaintext: &[u8]) -> Result<Vec<u8>> {
62        let mut iv = [0u8; IV_LEN];
63        OsRng.fill_bytes(&mut iv);
64
65        let mut buffer = plaintext.to_vec();
66        let tag = self
67            .cipher
68            .encrypt_in_place_detached(Nonce::from_slice(&iv), b"", &mut buffer)
69            .map_err(|_| Error::EncryptionFailed { label: LABEL })?;
70
71        let mut blob = Vec::with_capacity(PREFIX.len() + IV_LEN + TAG_LEN + buffer.len());
72        blob.extend_from_slice(PREFIX);
73        blob.extend_from_slice(&iv);
74        blob.extend_from_slice(&tag);
75        blob.extend_from_slice(&buffer);
76        Ok(blob)
77    }
78
79    fn decrypt(&self, ciphertext: &[u8]) -> Result<Vec<u8>> {
80        let body = ciphertext
81            .strip_prefix(PREFIX)
82            .ok_or(Error::InvalidPrefix { label: LABEL })?;
83        if body.len() < IV_LEN + TAG_LEN {
84            return Err(Error::TooShort { label: LABEL });
85        }
86
87        let (iv, rest) = body.split_at(IV_LEN);
88        let (tag, ct) = rest.split_at(TAG_LEN);
89
90        let mut buffer = ct.to_vec();
91        self.cipher
92            .decrypt_in_place_detached(
93                Nonce::from_slice(iv),
94                b"",
95                &mut buffer,
96                Tag::from_slice(tag),
97            )
98            .map_err(|_| Error::DecryptionFailed { label: LABEL })?;
99        Ok(buffer)
100    }
101}
102
103#[cfg(test)]
104mod tests {
105    use super::*;
106
107    #[test]
108    fn roundtrip_and_tamper_detection() {
109        let encryptor = Aes256GcmEncryptor::new([7u8; 32]);
110        let blob = encryptor.encrypt(b"\xe5\xaf\x86\xe6\x96\x87").unwrap();
111        assert_eq!(blob.len(), 2 + IV_LEN + TAG_LEN + 6);
112        assert_eq!(
113            encryptor.decrypt(&blob).unwrap(),
114            b"\xe5\xaf\x86\xe6\x96\x87"
115        );
116
117        let mut tampered = blob.clone();
118        let last = tampered.len() - 1;
119        tampered[last] ^= 0x01;
120        assert_eq!(
121            encryptor.decrypt(&tampered).unwrap_err(),
122            Error::DecryptionFailed { label: LABEL }
123        );
124    }
125
126    #[test]
127    fn every_encrypt_uses_a_fresh_iv() {
128        let encryptor = Aes256GcmEncryptor::new([7u8; 32]);
129        let first = encryptor.encrypt(b"same").unwrap();
130        let second = encryptor.encrypt(b"same").unwrap();
131        assert_ne!(first, second);
132        assert_ne!(first[2..14], second[2..14]);
133    }
134
135    #[test]
136    fn wrong_key_and_bad_prefix_fail() {
137        let blob = Aes256GcmEncryptor::new([7u8; 32]).encrypt(b"x").unwrap();
138        let other = Aes256GcmEncryptor::new([8u8; 32]);
139        assert_eq!(
140            other.decrypt(&blob).unwrap_err(),
141            Error::DecryptionFailed { label: LABEL }
142        );
143        assert_eq!(
144            other.decrypt(b"v9....").unwrap_err(),
145            Error::InvalidPrefix { label: LABEL }
146        );
147        assert_eq!(
148            other.decrypt(b"v1short").unwrap_err(),
149            Error::TooShort { label: LABEL }
150        );
151    }
152
153    #[test]
154    fn key_length_is_checked() {
155        assert_eq!(
156            Aes256GcmEncryptor::from_slice(&[0u8; 16]).unwrap_err(),
157            Error::InvalidKeyLength {
158                label: LABEL,
159                expected: 32,
160                got: 16
161            }
162        );
163    }
164}