1use std::str::FromStr;
21
22use aes_gcm::aead::generic_array::typenum::U12;
23use aes_gcm::aead::rand_core::RngCore;
24use aes_gcm::aead::{Aead, AeadCore, KeyInit, OsRng, Payload};
25use aes_gcm::{Aes128Gcm, Aes256Gcm, AesGcm, Nonce};
26
27type Aes192Gcm = AesGcm<aes_gcm::aes::Aes192, U12>;
30
31use crate::sensitive::SensitiveBytes;
32use crate::{Error, ErrorKind, Result};
33
34#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
39pub enum AesKeySize {
40 #[default]
42 Bits128 = 128,
43 Bits192 = 192,
45 Bits256 = 256,
47}
48
49impl AesKeySize {
50 pub fn key_length(&self) -> usize {
52 match self {
53 Self::Bits128 => 16,
54 Self::Bits192 => 24,
55 Self::Bits256 => 32,
56 }
57 }
58
59 pub fn from_key_length(len: usize) -> Result<Self> {
64 match len {
65 16 => Ok(Self::Bits128),
66 24 => Ok(Self::Bits192),
67 32 => Ok(Self::Bits256),
68 _ => Err(Error::new(
69 ErrorKind::FeatureUnsupported,
70 format!("Unsupported data key length: {len} (must be 16, 24, or 32)"),
71 )),
72 }
73 }
74}
75
76impl FromStr for AesKeySize {
77 type Err = Error;
78
79 fn from_str(s: &str) -> Result<Self> {
80 match s {
81 "128" | "AES_GCM_128" | "AES128_GCM" => Ok(Self::Bits128),
82 "192" | "AES_GCM_192" | "AES192_GCM" => Ok(Self::Bits192),
83 "256" | "AES_GCM_256" | "AES256_GCM" => Ok(Self::Bits256),
84 _ => Err(Error::new(
85 ErrorKind::FeatureUnsupported,
86 format!("Unsupported AES key size: {s}"),
87 )),
88 }
89 }
90}
91
92#[derive(Clone, Debug, PartialEq, Eq)]
97pub struct SecureKey {
98 key: SensitiveBytes,
99 key_size: AesKeySize,
100}
101
102impl SecureKey {
103 pub fn new(key: &[u8]) -> Result<Self> {
108 let key_size = AesKeySize::from_key_length(key.len())?;
109 Ok(Self {
110 key: SensitiveBytes::new(key),
111 key_size,
112 })
113 }
114
115 pub fn generate(key_size: AesKeySize) -> Self {
117 let mut key = vec![0u8; key_size.key_length()];
118 OsRng.fill_bytes(&mut key);
119 Self {
120 key: SensitiveBytes::new(key),
121 key_size,
122 }
123 }
124
125 pub fn key_size(&self) -> AesKeySize {
127 self.key_size
128 }
129
130 pub fn as_bytes(&self) -> &[u8] {
132 self.key.as_bytes()
133 }
134}
135
136impl TryFrom<SensitiveBytes> for SecureKey {
137 type Error = Error;
138
139 fn try_from(key: SensitiveBytes) -> Result<Self> {
140 let key_size = AesKeySize::from_key_length(key.len())?;
141 Ok(Self { key, key_size })
142 }
143}
144
145pub struct AesGcmCipher {
147 key: SensitiveBytes,
148 key_size: AesKeySize,
149}
150
151impl AesGcmCipher {
152 pub const NONCE_LEN: usize = 12;
154 pub const TAG_LEN: usize = 16;
156
157 pub fn new(key: SecureKey) -> Self {
159 Self {
160 key: SensitiveBytes::new(key.as_bytes()),
161 key_size: key.key_size(),
162 }
163 }
164
165 pub fn encrypt(&self, plaintext: &[u8], aad: Option<&[u8]>) -> Result<Vec<u8>> {
175 match self.key_size {
176 AesKeySize::Bits128 => {
177 encrypt_aes_gcm::<Aes128Gcm>(self.key.as_bytes(), plaintext, aad)
178 }
179 AesKeySize::Bits192 => {
180 encrypt_aes_gcm::<Aes192Gcm>(self.key.as_bytes(), plaintext, aad)
181 }
182 AesKeySize::Bits256 => {
183 encrypt_aes_gcm::<Aes256Gcm>(self.key.as_bytes(), plaintext, aad)
184 }
185 }
186 }
187
188 pub fn decrypt(&self, ciphertext: &[u8], aad: Option<&[u8]>) -> Result<Vec<u8>> {
197 if ciphertext.len() < Self::NONCE_LEN + Self::TAG_LEN {
198 return Err(Error::new(
199 ErrorKind::DataInvalid,
200 format!(
201 "Ciphertext too short: expected at least {} bytes, got {}",
202 Self::NONCE_LEN + Self::TAG_LEN,
203 ciphertext.len()
204 ),
205 ));
206 }
207
208 match self.key_size {
209 AesKeySize::Bits128 => {
210 decrypt_aes_gcm::<Aes128Gcm>(self.key.as_bytes(), ciphertext, aad)
211 }
212 AesKeySize::Bits192 => {
213 decrypt_aes_gcm::<Aes192Gcm>(self.key.as_bytes(), ciphertext, aad)
214 }
215 AesKeySize::Bits256 => {
216 decrypt_aes_gcm::<Aes256Gcm>(self.key.as_bytes(), ciphertext, aad)
217 }
218 }
219 }
220}
221
222fn encrypt_aes_gcm<C>(key_bytes: &[u8], plaintext: &[u8], aad: Option<&[u8]>) -> Result<Vec<u8>>
223where C: Aead + AeadCore + KeyInit {
224 let cipher = C::new_from_slice(key_bytes).map_err(|e| {
225 Error::new(ErrorKind::DataInvalid, "Invalid AES key").with_source(anyhow::anyhow!(e))
226 })?;
227 let nonce = C::generate_nonce(&mut OsRng);
228
229 let ciphertext = if let Some(aad) = aad {
230 cipher.encrypt(&nonce, Payload {
231 msg: plaintext,
232 aad,
233 })
234 } else {
235 cipher.encrypt(&nonce, plaintext.as_ref())
236 }
237 .map_err(|e| {
238 Error::new(ErrorKind::Unexpected, "AES-GCM encryption failed")
239 .with_source(anyhow::anyhow!(e))
240 })?;
241
242 let mut result = Vec::with_capacity(nonce.len() + ciphertext.len());
244 result.extend_from_slice(&nonce);
245 result.extend_from_slice(&ciphertext);
246 Ok(result)
247}
248
249fn decrypt_aes_gcm<C>(key_bytes: &[u8], ciphertext: &[u8], aad: Option<&[u8]>) -> Result<Vec<u8>>
250where C: Aead + AeadCore + KeyInit {
251 let cipher = C::new_from_slice(key_bytes).map_err(|e| {
252 Error::new(ErrorKind::DataInvalid, "Invalid AES key").with_source(anyhow::anyhow!(e))
253 })?;
254
255 let nonce = Nonce::from_slice(&ciphertext[..AesGcmCipher::NONCE_LEN]);
256 let encrypted_data = &ciphertext[AesGcmCipher::NONCE_LEN..];
257
258 let plaintext = if let Some(aad) = aad {
259 cipher.decrypt(nonce, Payload {
260 msg: encrypted_data,
261 aad,
262 })
263 } else {
264 cipher.decrypt(nonce, encrypted_data)
265 }
266 .map_err(|e| {
267 Error::new(ErrorKind::Unexpected, "AES-GCM decryption failed")
268 .with_source(anyhow::anyhow!(e))
269 })?;
270
271 Ok(plaintext)
272}
273
274#[cfg(test)]
275mod tests {
276 use super::*;
277
278 #[test]
279 fn test_aes_key_size() {
280 assert_eq!(AesKeySize::Bits128.key_length(), 16);
281 assert_eq!(AesKeySize::Bits192.key_length(), 24);
282 assert_eq!(AesKeySize::Bits256.key_length(), 32);
283
284 assert_eq!(
285 AesKeySize::from_key_length(16).unwrap(),
286 AesKeySize::Bits128
287 );
288 assert_eq!(
289 AesKeySize::from_key_length(24).unwrap(),
290 AesKeySize::Bits192
291 );
292 assert_eq!(
293 AesKeySize::from_key_length(32).unwrap(),
294 AesKeySize::Bits256
295 );
296 assert!(AesKeySize::from_key_length(8).is_err());
297
298 assert_eq!(AesKeySize::from_str("128").unwrap(), AesKeySize::Bits128);
299 assert_eq!(
300 AesKeySize::from_str("AES_GCM_128").unwrap(),
301 AesKeySize::Bits128
302 );
303 assert_eq!(
304 AesKeySize::from_str("AES_GCM_256").unwrap(),
305 AesKeySize::Bits256
306 );
307 assert!(AesKeySize::from_str("INVALID").is_err());
308 }
309
310 #[test]
311 fn test_secure_key() {
312 let key1 = SecureKey::generate(AesKeySize::Bits128);
314 assert_eq!(key1.as_bytes().len(), 16);
315 assert_eq!(key1.key_size(), AesKeySize::Bits128);
316
317 let valid_key = [0u8; 16];
319 assert!(SecureKey::new(valid_key.as_slice()).is_ok());
320
321 let invalid_key = [0u8; 33];
322 assert!(SecureKey::new(invalid_key.as_slice()).is_err());
323 }
324
325 #[test]
326 fn test_aes128_gcm_encryption_roundtrip() {
327 let key = SecureKey::generate(AesKeySize::Bits128);
328 let cipher = AesGcmCipher::new(key);
329
330 let plaintext = b"Hello, Iceberg encryption!";
331 let aad = b"additional authenticated data";
332
333 let ciphertext = cipher.encrypt(plaintext, None).unwrap();
335 assert!(ciphertext.len() > plaintext.len() + 12); assert_ne!(&ciphertext[12..], plaintext); let decrypted = cipher.decrypt(&ciphertext, None).unwrap();
339 assert_eq!(decrypted, plaintext);
340
341 let ciphertext = cipher.encrypt(plaintext, Some(aad)).unwrap();
343 let decrypted = cipher.decrypt(&ciphertext, Some(aad)).unwrap();
344 assert_eq!(decrypted, plaintext);
345
346 assert!(cipher.decrypt(&ciphertext, Some(b"wrong aad")).is_err());
348 }
349
350 #[test]
351 fn test_aes192_gcm_encryption_roundtrip() {
352 let key = SecureKey::generate(AesKeySize::Bits192);
353 let cipher = AesGcmCipher::new(key);
354
355 let plaintext = b"Hello, Iceberg encryption!";
356 let aad = b"additional authenticated data";
357
358 let ciphertext = cipher.encrypt(plaintext, None).unwrap();
360 let decrypted = cipher.decrypt(&ciphertext, None).unwrap();
361 assert_eq!(decrypted, plaintext);
362
363 let ciphertext = cipher.encrypt(plaintext, Some(aad)).unwrap();
365 let decrypted = cipher.decrypt(&ciphertext, Some(aad)).unwrap();
366 assert_eq!(decrypted, plaintext);
367
368 assert!(cipher.decrypt(&ciphertext, Some(b"wrong aad")).is_err());
370 }
371
372 #[test]
373 fn test_aes256_gcm_encryption_roundtrip() {
374 let key = SecureKey::generate(AesKeySize::Bits256);
375 let cipher = AesGcmCipher::new(key);
376
377 let plaintext = b"Hello, Iceberg encryption!";
378 let aad = b"additional authenticated data";
379
380 let ciphertext = cipher.encrypt(plaintext, None).unwrap();
382 let decrypted = cipher.decrypt(&ciphertext, None).unwrap();
383 assert_eq!(decrypted, plaintext);
384
385 let ciphertext = cipher.encrypt(plaintext, Some(aad)).unwrap();
387 let decrypted = cipher.decrypt(&ciphertext, Some(aad)).unwrap();
388 assert_eq!(decrypted, plaintext);
389
390 assert!(cipher.decrypt(&ciphertext, Some(b"wrong aad")).is_err());
392 }
393
394 #[test]
395 fn test_cross_key_size_incompatibility() {
396 let plaintext = b"Cross-key test";
397
398 let key128 = SecureKey::generate(AesKeySize::Bits128);
399 let key256 = SecureKey::generate(AesKeySize::Bits256);
400
401 let cipher128 = AesGcmCipher::new(key128);
402 let cipher256 = AesGcmCipher::new(key256);
403
404 let ciphertext = cipher128.encrypt(plaintext, None).unwrap();
406 assert!(cipher256.decrypt(&ciphertext, None).is_err());
407 }
408
409 #[test]
410 fn test_encryption_with_empty_plaintext() {
411 let key = SecureKey::generate(AesKeySize::Bits128);
412 let cipher = AesGcmCipher::new(key);
413
414 let plaintext = b"";
415 let ciphertext = cipher.encrypt(plaintext, None).unwrap();
416
417 assert_eq!(ciphertext.len(), 12 + 16); let decrypted = cipher.decrypt(&ciphertext, None).unwrap();
421 assert_eq!(decrypted, plaintext);
422 }
423
424 #[test]
425 fn test_decryption_with_tampered_ciphertext() {
426 let key = SecureKey::generate(AesKeySize::Bits128);
427 let cipher = AesGcmCipher::new(key);
428
429 let plaintext = b"Sensitive data";
430 let mut ciphertext = cipher.encrypt(plaintext, None).unwrap();
431
432 if ciphertext.len() > 12 {
434 ciphertext[12] ^= 0xFF;
435 }
436
437 assert!(cipher.decrypt(&ciphertext, None).is_err());
439 }
440
441 #[test]
442 fn test_different_keys_produce_different_ciphertexts() {
443 let key1 = SecureKey::generate(AesKeySize::Bits128);
444 let key2 = SecureKey::generate(AesKeySize::Bits128);
445
446 let cipher1 = AesGcmCipher::new(key1);
447 let cipher2 = AesGcmCipher::new(key2);
448
449 let plaintext = b"Same plaintext";
450
451 let ciphertext1 = cipher1.encrypt(plaintext, None).unwrap();
452 let ciphertext2 = cipher2.encrypt(plaintext, None).unwrap();
453
454 assert_ne!(&ciphertext1[12..], &ciphertext2[12..]);
457 }
458
459 #[test]
460 fn test_ciphertext_format_java_compatible() {
461 let key = SecureKey::generate(AesKeySize::Bits128);
463 let cipher = AesGcmCipher::new(key);
464
465 let plaintext = b"Test data";
466 let ciphertext = cipher.encrypt(plaintext, None).unwrap();
467
468 assert_eq!(
470 ciphertext.len(),
471 12 + plaintext.len() + 16,
472 "Ciphertext should be nonce + plaintext + tag length"
473 );
474
475 let nonce = &ciphertext[..12];
477 assert_eq!(nonce.len(), 12, "Nonce should be 12 bytes");
478
479 let encrypted_with_tag = &ciphertext[12..];
481 assert_eq!(
482 encrypted_with_tag.len(),
483 plaintext.len() + 16,
484 "Encrypted portion should be plaintext length + 16-byte tag"
485 );
486 }
487}