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