dcrypt_algorithms/aead/gcm/
mod.rs1#[cfg(not(feature = "std"))]
23#[cfg(feature = "alloc")]
24use alloc::vec::Vec;
25
26#[cfg(feature = "std")]
27use std::vec::Vec;
28
29use dcrypt_internal::constant_time::ConstantTimeEq;
30use dcrypt_internal::random::{try_fill_bytes_zeroing_on_error, CryptoRng, RngCore};
31use dcrypt_internal::zeroing::{
32 boxed_bytes_zeroed, Zeroize, ZeroizeOnDrop, Zeroizing, ZeroizingBytes,
33};
34
35use dcrypt_common::security::SecretBuffer;
37
38use crate::block::BlockCipher;
40use dcrypt_api::traits::symmetric::{DecryptOperation, EncryptOperation, Operation};
41use dcrypt_api::traits::AuthenticatedCipher;
42use dcrypt_api::traits::SymmetricCipher;
43
44use crate::error::{validate, Error, Result};
45use crate::types::nonce::AesGcmCompatible; use crate::types::Nonce; use crate::types::SecretBytes;
48use dcrypt_api::error::Error as CoreError;
49use dcrypt_api::types::Ciphertext;
50
51mod ghash;
53use ghash::{process_ghash, GHash};
54
55const GCM_BLOCK_SIZE: usize = 16;
57const GCM_TAG_SIZE: usize = 16;
58
59#[derive(Clone)]
61pub struct Gcm<B: BlockCipher + Zeroize + ZeroizeOnDrop> {
62 cipher: B,
63 h: SecretBuffer<GCM_BLOCK_SIZE>, tag_len: usize, }
66
67impl<B: BlockCipher + Zeroize + ZeroizeOnDrop> Zeroize for Gcm<B> {
68 fn zeroize(&mut self) {
69 self.cipher.zeroize();
70 self.h.zeroize();
71 self.tag_len.zeroize();
72 }
73}
74
75impl<B: BlockCipher + Zeroize + ZeroizeOnDrop> Drop for Gcm<B> {
76 fn drop(&mut self) {
77 self.zeroize();
78 }
79}
80
81impl<B: BlockCipher + Zeroize + ZeroizeOnDrop> ZeroizeOnDrop for Gcm<B> {}
82
83pub trait GcmKey: AsRef<[u8]> + AsMut<[u8]> + Clone + Zeroize {
85 fn from_key_bytes(bytes: &[u8]) -> core::result::Result<Self, CoreError>;
87}
88
89impl<const N: usize> GcmKey for SecretBytes<N> {
90 fn from_key_bytes(bytes: &[u8]) -> core::result::Result<Self, CoreError> {
91 SecretBytes::<N>::from_slice(bytes)
92 }
93}
94
95fn gcm_block_count(data_len: usize) -> Result<usize> {
96 let num_blocks = data_len.div_ceil(GCM_BLOCK_SIZE);
97 validate::parameter(
98 (num_blocks as u128) <= u128::from(u32::MAX - 1),
99 "message_length",
100 "GCM message exceeds the 2^32-2 block construction limit",
101 )?;
102 Ok(num_blocks)
103}
104
105pub struct GcmEncryptOperation<'a, B: BlockCipher + Zeroize + ZeroizeOnDrop> {
107 cipher: &'a Gcm<B>,
108 nonce: Option<&'a Nonce<12>>, aad: Option<&'a [u8]>,
110}
111
112pub struct GcmDecryptOperation<'a, B: BlockCipher + Zeroize + ZeroizeOnDrop> {
114 cipher: &'a Gcm<B>,
115 nonce: Option<&'a Nonce<12>>, aad: Option<&'a [u8]>,
117}
118
119impl<B: BlockCipher + Zeroize + ZeroizeOnDrop> Gcm<B> {
120 pub fn new(cipher: B) -> Result<Self> {
125 Self::new_with_tag_len(cipher, GCM_TAG_SIZE)
126 }
127
128 pub fn new_with_tag_len(cipher: B, tag_len: usize) -> Result<Self> {
132 validate::parameter(
134 B::block_size() == GCM_BLOCK_SIZE,
135 "block_size",
136 "GCM only works with 128-bit block ciphers",
137 )?;
138
139 validate::parameter(
140 (12..=GCM_TAG_SIZE).contains(&tag_len),
141 "tag_length",
142 "GCM tag length must be between 12 and 16 bytes",
143 )?;
144
145 let mut h_bytes = Zeroizing::new([0u8; GCM_BLOCK_SIZE]);
147 cipher.encrypt_block(h_bytes.as_mut())?;
148
149 let h = SecretBuffer::new(*h_bytes);
151
152 Ok(Self { cipher, h, tag_len })
153 }
154
155 fn generate_j0<const N: usize>(
157 &self,
158 nonce: &Nonce<N>,
159 ) -> Result<Zeroizing<[u8; GCM_BLOCK_SIZE]>>
160 where
161 Nonce<N>: AesGcmCompatible,
162 {
163 validate::parameter(
164 !nonce.is_empty() && nonce.len() <= 16,
165 "nonce_length",
166 "GCM nonce must be between 1 and 16 bytes",
167 )?;
168 let mut j0 = Zeroizing::new([0u8; GCM_BLOCK_SIZE]);
169 if nonce.len() == 12 {
170 j0[..12].copy_from_slice(nonce.as_ref());
171 j0[15] = 1;
172 } else {
173 let h_array: &[u8; GCM_BLOCK_SIZE] = self
175 .h
176 .as_ref()
177 .try_into()
178 .expect("SecretBuffer has correct size");
179
180 let mut g = GHash::new(h_array);
181 g.update(nonce.as_ref())?;
184 g.update_lengths(0, nonce.len() as u64)?;
185 j0 = g.finalize_protected();
186 }
187 Ok(j0)
188 }
189
190 fn generate_keystream(
192 &self,
193 j0: &[u8; GCM_BLOCK_SIZE],
194 data_len: usize,
195 ) -> Result<ZeroizingBytes> {
196 let num_blocks = gcm_block_count(data_len)?;
199 let mut keystream = Zeroizing::new(boxed_bytes_zeroed(num_blocks * GCM_BLOCK_SIZE));
200 let mut keystream_offset = 0usize;
201
202 let mut counter = Zeroizing::new(*j0);
203 let mut ctr_val =
204 u32::from_be_bytes(counter[12..16].try_into().expect("four bytes")).wrapping_add(1);
205 counter[12..16].copy_from_slice(&ctr_val.to_be_bytes());
206
207 for _ in 0..num_blocks {
208 let mut block = Zeroizing::new(*counter);
209 self.cipher.encrypt_block(block.as_mut())?;
210 keystream[keystream_offset..keystream_offset + GCM_BLOCK_SIZE]
211 .copy_from_slice(block.as_ref());
212 keystream_offset += GCM_BLOCK_SIZE;
213 ctr_val = ctr_val.wrapping_add(1);
214 counter[12..16].copy_from_slice(&ctr_val.to_be_bytes());
215 }
216
217 Ok(keystream)
218 }
219
220 fn generate_tag(
222 &self,
223 j0: &[u8; GCM_BLOCK_SIZE],
224 aad: &[u8],
225 ciphertext: &[u8],
226 ) -> Result<[u8; GCM_TAG_SIZE]> {
227 let h_array: &[u8; GCM_BLOCK_SIZE] = self
229 .h
230 .as_ref()
231 .try_into()
232 .expect("SecretBuffer has correct size");
233
234 let mut tag = process_ghash(h_array, aad, ciphertext)?;
236
237 let mut j0_copy = Zeroizing::new(*j0);
239 self.cipher.encrypt_block(j0_copy.as_mut())?;
240
241 for i in 0..GCM_TAG_SIZE {
243 tag[i] ^= j0_copy[i];
244 }
245
246 Ok(tag)
247 }
248
249 pub fn internal_encrypt<const N: usize>(
251 &self,
252 nonce: &Nonce<N>,
253 plaintext: &[u8],
254 associated_data: Option<&[u8]>,
255 ) -> Result<Vec<u8>>
256 where
257 Nonce<N>: AesGcmCompatible,
258 {
259 let aad = associated_data.unwrap_or(&[]);
260 let j0 = self.generate_j0(nonce)?;
261
262 let keystream = if plaintext.is_empty() {
263 None
264 } else {
265 Some(self.generate_keystream(&*j0, plaintext.len())?)
266 };
267 let output_len = plaintext
268 .len()
269 .checked_add(self.tag_len)
270 .ok_or(Error::Processing {
271 operation: "GCM encryption",
272 details: "ciphertext length overflow",
273 })?;
274 let mut ciphertext = Vec::with_capacity(output_len);
275 if let Some(keystream) = keystream {
276 for i in 0..plaintext.len() {
277 ciphertext.push(plaintext[i] ^ keystream[i]);
278 }
279 }
280
281 let full_tag = self.generate_tag(&*j0, aad, &ciphertext)?;
282 ciphertext.extend_from_slice(&full_tag[..self.tag_len]);
283 Ok(ciphertext)
284 }
285
286 pub fn internal_decrypt<const N: usize>(
288 &self,
289 nonce: &Nonce<N>,
290 ciphertext: &[u8],
291 associated_data: Option<&[u8]>,
292 ) -> Result<Vec<u8>>
293 where
294 Nonce<N>: AesGcmCompatible,
295 {
296 Ok(self
297 .internal_decrypt_protected(nonce, ciphertext, associated_data)?
298 .into_inner()
299 .into_vec())
300 }
301
302 pub fn internal_decrypt_protected<const N: usize>(
306 &self,
307 nonce: &Nonce<N>,
308 ciphertext: &[u8],
309 associated_data: Option<&[u8]>,
310 ) -> Result<ZeroizingBytes>
311 where
312 Nonce<N>: AesGcmCompatible,
313 {
314 validate::min_length("GCM ciphertext", ciphertext.len(), self.tag_len)?;
316
317 let aad = associated_data.unwrap_or(&[]);
318 let ciphertext_len = ciphertext.len() - self.tag_len;
319 let (ciphertext_data, received_tag) = ciphertext.split_at(ciphertext_len);
320
321 let j0 = self.generate_j0(nonce)?;
323 let full_expected = self.generate_tag(&*j0, aad, ciphertext_data)?;
324 let expected_tag = &full_expected[..self.tag_len];
325
326 let keystream = self.generate_keystream(&*j0, ciphertext_len)?;
328 let mut plaintext = Zeroizing::new(boxed_bytes_zeroed(ciphertext_len));
329 for i in 0..ciphertext_len {
330 plaintext[i] = ciphertext_data[i] ^ keystream[i];
331 }
332
333 let tag_matches = expected_tag.ct_eq(received_tag);
335
336 if tag_matches.unwrap_u8() == 0 {
340 Err(Error::Authentication { algorithm: "GCM" })
341 } else {
342 Ok(plaintext)
343 }
344 }
345}
346
347impl<B: BlockCipher + Zeroize + ZeroizeOnDrop> AuthenticatedCipher for Gcm<B> {
349 const TAG_SIZE: usize = GCM_TAG_SIZE;
350 const ALGORITHM_ID: &'static str = "GCM";
351}
352
353impl<B> SymmetricCipher for Gcm<B>
355where
356 B: BlockCipher + Zeroize + ZeroizeOnDrop,
357 B::Key: GcmKey,
358{
359 type Key = B::Key;
360 type Nonce = Nonce<12>; type Ciphertext = Ciphertext;
362 type EncryptOperation<'a>
363 = GcmEncryptOperation<'a, B>
364 where
365 Self: 'a;
366 type DecryptOperation<'a>
367 = GcmDecryptOperation<'a, B>
368 where
369 Self: 'a;
370
371 fn name() -> &'static str {
372 "GCM"
373 }
374
375 fn encrypt(&self) -> <Self as SymmetricCipher>::EncryptOperation<'_> {
376 GcmEncryptOperation {
377 cipher: self,
378 nonce: None,
379 aad: None,
380 }
381 }
382
383 fn decrypt(&self) -> <Self as SymmetricCipher>::DecryptOperation<'_> {
384 GcmDecryptOperation {
385 cipher: self,
386 nonce: None,
387 aad: None,
388 }
389 }
390
391 fn generate_key<R: RngCore + CryptoRng>(
392 rng: &mut R,
393 ) -> core::result::Result<<Self as SymmetricCipher>::Key, CoreError> {
394 B::generate_key(rng).map_err(CoreError::from)
395 }
396
397 fn generate_nonce<R: RngCore + CryptoRng>(
398 rng: &mut R,
399 ) -> core::result::Result<<Self as SymmetricCipher>::Nonce, CoreError> {
400 let mut nonce_data = [0u8; 12];
401 try_fill_bytes_zeroing_on_error(rng, &mut nonce_data).map_err(|_| CoreError::Other {
402 context: "randomness",
403 #[cfg(feature = "std")]
404 message: "caller-provided randomness source failed".to_string(),
405 })?;
406 Ok(Nonce::<12>::new(nonce_data)) }
408
409 fn derive_key_from_bytes(
410 bytes: &[u8],
411 ) -> core::result::Result<<Self as SymmetricCipher>::Key, CoreError> {
412 if bytes.len() != B::key_size() {
413 return Err(CoreError::InvalidLength {
414 context: "GCM key derivation",
415 expected: B::key_size(),
416 actual: bytes.len(),
417 });
418 }
419 B::Key::from_key_bytes(bytes)
420 }
421}
422
423impl<B> Operation<Ciphertext> for GcmEncryptOperation<'_, B>
425where
426 B: BlockCipher + Zeroize + ZeroizeOnDrop,
427 B::Key: GcmKey,
428{
429 fn execute(self) -> core::result::Result<Ciphertext, CoreError> {
430 let nonce = self.nonce.ok_or_else(|| CoreError::InvalidParameter {
431 context: "GCM encryption",
432 #[cfg(feature = "std")]
433 message: "Nonce is required for GCM encryption".to_string(),
434 })?;
435 let plaintext = b""; let ciphertext = self
438 .cipher
439 .internal_encrypt(nonce, plaintext, self.aad)
440 .map_err(CoreError::from)?;
441
442 Ok(Ciphertext::new(ciphertext))
443 }
444}
445
446impl<'a, B> EncryptOperation<'a, Gcm<B>> for GcmEncryptOperation<'a, B>
448where
449 B: BlockCipher + Zeroize + ZeroizeOnDrop,
450 B::Key: GcmKey,
451{
452 fn with_nonce(mut self, nonce: &'a <Gcm<B> as SymmetricCipher>::Nonce) -> Self {
453 self.nonce = Some(nonce);
454 self
455 }
456
457 fn with_aad(mut self, aad: &'a [u8]) -> Self {
458 self.aad = Some(aad);
459 self
460 }
461
462 fn encrypt(self, plaintext: &'a [u8]) -> core::result::Result<Ciphertext, CoreError> {
463 let nonce = self.nonce.ok_or_else(|| CoreError::InvalidParameter {
464 context: "GCM encryption",
465 #[cfg(feature = "std")]
466 message: "Nonce is required for GCM encryption".to_string(),
467 })?;
468
469 let ciphertext = self
470 .cipher
471 .internal_encrypt(nonce, plaintext, self.aad)
472 .map_err(CoreError::from)?;
473
474 Ok(Ciphertext::new(ciphertext))
475 }
476}
477
478impl<B> Operation<Vec<u8>> for GcmDecryptOperation<'_, B>
480where
481 B: BlockCipher + Zeroize + ZeroizeOnDrop,
482 B::Key: GcmKey,
483{
484 fn execute(self) -> core::result::Result<Vec<u8>, CoreError> {
485 Err(CoreError::InvalidParameter {
486 context: "GCM decryption",
487 #[cfg(feature = "std")]
488 message: "Use decrypt method instead".to_string(),
489 })
490 }
491}
492
493impl<'a, B> DecryptOperation<'a, Gcm<B>> for GcmDecryptOperation<'a, B>
495where
496 B: BlockCipher + Zeroize + ZeroizeOnDrop,
497 B::Key: GcmKey,
498{
499 fn with_nonce(mut self, nonce: &'a <Gcm<B> as SymmetricCipher>::Nonce) -> Self {
500 self.nonce = Some(nonce);
501 self
502 }
503
504 fn with_aad(mut self, aad: &'a [u8]) -> Self {
505 self.aad = Some(aad);
506 self
507 }
508
509 fn decrypt(
510 self,
511 ciphertext: &'a <Gcm<B> as SymmetricCipher>::Ciphertext,
512 ) -> core::result::Result<Vec<u8>, CoreError> {
513 let nonce = self.nonce.ok_or_else(|| CoreError::InvalidParameter {
514 context: "GCM decryption",
515 #[cfg(feature = "std")]
516 message: "Nonce is required for GCM decryption".to_string(),
517 })?;
518
519 self.cipher
520 .internal_decrypt(nonce, ciphertext.as_ref(), self.aad)
521 .map_err(CoreError::from)
522 }
523}
524
525#[cfg(test)]
526mod tests;