encryption/encryptor/
aes256gcm.rs1use 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
18pub 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 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
44impl 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}