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#[derive(Debug)]
20pub struct Aes256GcmEncryptor {
21    key: Key<32>,
22}
23
24impl Aes256GcmEncryptor {
25    pub const IDENTIFIER: &'static str = "aes-256-gcm";
26
27    pub fn new(key: [u8; 32]) -> Self {
28        Self { key: Key::new(key) }
29    }
30
31    pub fn from_slice(key: &[u8]) -> Result<Self> {
32        Ok(Self {
33            key: Key::from_slice(key, LABEL)?,
34        })
35    }
36}
37
38impl Identified for Aes256GcmEncryptor {
39    fn identifier(&self) -> &str {
40        Self::IDENTIFIER
41    }
42}
43
44impl SymmetricCipher for Aes256GcmEncryptor {
45    fn encrypt(&self, plaintext: &[u8]) -> Result<Vec<u8>> {
46        // Key<32> 保证长度,new_from_slice 不会失败。
47        let cipher =
48            Aes256Gcm::new_from_slice(self.key.as_bytes()).expect("key length is fixed at 32");
49
50        let mut iv = [0u8; IV_LEN];
51        OsRng.fill_bytes(&mut iv);
52
53        let mut buffer = plaintext.to_vec();
54        let tag = cipher
55            .encrypt_in_place_detached(Nonce::from_slice(&iv), b"", &mut buffer)
56            .map_err(|_| Error::EncryptionFailed { label: LABEL })?;
57
58        let mut blob = Vec::with_capacity(PREFIX.len() + IV_LEN + TAG_LEN + buffer.len());
59        blob.extend_from_slice(PREFIX);
60        blob.extend_from_slice(&iv);
61        blob.extend_from_slice(&tag);
62        blob.extend_from_slice(&buffer);
63        Ok(blob)
64    }
65
66    fn decrypt(&self, ciphertext: &[u8]) -> Result<Vec<u8>> {
67        let body = ciphertext
68            .strip_prefix(PREFIX)
69            .ok_or(Error::InvalidPrefix { label: LABEL })?;
70        if body.len() < IV_LEN + TAG_LEN {
71            return Err(Error::TooShort { label: LABEL });
72        }
73
74        let (iv, rest) = body.split_at(IV_LEN);
75        let (tag, ct) = rest.split_at(TAG_LEN);
76
77        let cipher =
78            Aes256Gcm::new_from_slice(self.key.as_bytes()).expect("key length is fixed at 32");
79        let mut buffer = ct.to_vec();
80        cipher
81            .decrypt_in_place_detached(
82                Nonce::from_slice(iv),
83                b"",
84                &mut buffer,
85                Tag::from_slice(tag),
86            )
87            .map_err(|_| Error::DecryptionFailed { label: LABEL })?;
88        Ok(buffer)
89    }
90}
91
92#[cfg(test)]
93mod tests {
94    use super::*;
95
96    #[test]
97    fn roundtrip_and_tamper_detection() {
98        let encryptor = Aes256GcmEncryptor::new([7u8; 32]);
99        let blob = encryptor.encrypt(b"\xe5\xaf\x86\xe6\x96\x87").unwrap();
100        assert_eq!(blob.len(), 2 + IV_LEN + TAG_LEN + 6);
101        assert_eq!(
102            encryptor.decrypt(&blob).unwrap(),
103            b"\xe5\xaf\x86\xe6\x96\x87"
104        );
105
106        let mut tampered = blob.clone();
107        let last = tampered.len() - 1;
108        tampered[last] ^= 0x01;
109        assert_eq!(
110            encryptor.decrypt(&tampered).unwrap_err(),
111            Error::DecryptionFailed { label: LABEL }
112        );
113    }
114
115    #[test]
116    fn every_encrypt_uses_a_fresh_iv() {
117        let encryptor = Aes256GcmEncryptor::new([7u8; 32]);
118        let first = encryptor.encrypt(b"same").unwrap();
119        let second = encryptor.encrypt(b"same").unwrap();
120        assert_ne!(first, second);
121        assert_ne!(first[2..14], second[2..14]);
122    }
123
124    #[test]
125    fn wrong_key_and_bad_prefix_fail() {
126        let blob = Aes256GcmEncryptor::new([7u8; 32]).encrypt(b"x").unwrap();
127        let other = Aes256GcmEncryptor::new([8u8; 32]);
128        assert_eq!(
129            other.decrypt(&blob).unwrap_err(),
130            Error::DecryptionFailed { label: LABEL }
131        );
132        assert_eq!(
133            other.decrypt(b"v9....").unwrap_err(),
134            Error::InvalidPrefix { label: LABEL }
135        );
136        assert_eq!(
137            other.decrypt(b"v1short").unwrap_err(),
138            Error::TooShort { label: LABEL }
139        );
140    }
141
142    #[test]
143    fn key_length_is_checked() {
144        assert_eq!(
145            Aes256GcmEncryptor::from_slice(&[0u8; 16]).unwrap_err(),
146            Error::InvalidKeyLength {
147                label: LABEL,
148                expected: 32,
149                got: 16
150            }
151        );
152    }
153}