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
18#[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 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}