dcrypt_algorithms/aead/gcm/
mod.rs1#![cfg_attr(not(feature = "std"), no_std)]
21
22#[cfg(not(feature = "std"))]
24#[cfg(feature = "alloc")]
25use alloc::vec::Vec;
26
27#[cfg(feature = "std")]
28use std::vec::Vec;
29
30use byteorder::{BigEndian, ByteOrder};
31use subtle::ConstantTimeEq;
32use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};
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, Zeroize, ZeroizeOnDrop)]
60pub struct Gcm<B: BlockCipher + Zeroize + ZeroizeOnDrop> {
61 cipher: B,
62 h: SecretBuffer<GCM_BLOCK_SIZE>, tag_len: usize, }
65
66pub trait GcmKey: AsRef<[u8]> + AsMut<[u8]> + Clone + Zeroize {
68 fn from_key_bytes(bytes: &[u8]) -> core::result::Result<Self, CoreError>;
70}
71
72impl<const N: usize> GcmKey for SecretBytes<N> {
73 fn from_key_bytes(bytes: &[u8]) -> core::result::Result<Self, CoreError> {
74 SecretBytes::<N>::from_slice(bytes)
75 }
76}
77
78fn gcm_block_count(data_len: usize) -> Result<usize> {
79 let num_blocks = data_len.div_ceil(GCM_BLOCK_SIZE);
80 validate::parameter(
81 (num_blocks as u128) <= u128::from(u32::MAX - 1),
82 "message_length",
83 "GCM message exceeds the 2^32-2 block construction limit",
84 )?;
85 Ok(num_blocks)
86}
87
88pub struct GcmEncryptOperation<'a, B: BlockCipher + Zeroize + ZeroizeOnDrop> {
90 cipher: &'a Gcm<B>,
91 nonce: Option<&'a Nonce<12>>, aad: Option<&'a [u8]>,
93}
94
95pub struct GcmDecryptOperation<'a, B: BlockCipher + Zeroize + ZeroizeOnDrop> {
97 cipher: &'a Gcm<B>,
98 nonce: Option<&'a Nonce<12>>, aad: Option<&'a [u8]>,
100}
101
102impl<B: BlockCipher + Zeroize + ZeroizeOnDrop> Gcm<B> {
103 pub fn new(cipher: B) -> Result<Self> {
108 Self::new_with_tag_len(cipher, GCM_TAG_SIZE)
109 }
110
111 pub fn new_with_tag_len(cipher: B, tag_len: usize) -> Result<Self> {
115 validate::parameter(
117 B::block_size() == GCM_BLOCK_SIZE,
118 "block_size",
119 "GCM only works with 128-bit block ciphers",
120 )?;
121
122 validate::parameter(
123 (12..=GCM_TAG_SIZE).contains(&tag_len),
124 "tag_length",
125 "GCM tag length must be between 12 and 16 bytes",
126 )?;
127
128 let mut h_bytes = Zeroizing::new([0u8; GCM_BLOCK_SIZE]);
130 cipher.encrypt_block(h_bytes.as_mut())?;
131
132 let h = SecretBuffer::new(*h_bytes);
134
135 Ok(Self { cipher, h, tag_len })
136 }
137
138 fn generate_j0<const N: usize>(&self, nonce: &Nonce<N>) -> Result<[u8; GCM_BLOCK_SIZE]>
140 where
141 Nonce<N>: AesGcmCompatible,
142 {
143 validate::parameter(
144 !nonce.is_empty() && nonce.len() <= 16,
145 "nonce_length",
146 "GCM nonce must be between 1 and 16 bytes",
147 )?;
148 let mut j0 = [0u8; GCM_BLOCK_SIZE];
149 if nonce.len() == 12 {
150 j0[..12].copy_from_slice(nonce.as_ref());
151 j0[15] = 1;
152 } else {
153 let h_array: &[u8; GCM_BLOCK_SIZE] = self
155 .h
156 .as_ref()
157 .try_into()
158 .expect("SecretBuffer has correct size");
159
160 let mut g = GHash::new(h_array);
161 g.update(nonce.as_ref())?;
164 g.update_lengths(0, nonce.len() as u64)?;
165 j0 = g.finalize();
166 }
167 Ok(j0)
168 }
169
170 fn generate_keystream(
172 &self,
173 j0: &[u8; GCM_BLOCK_SIZE],
174 data_len: usize,
175 ) -> Result<Zeroizing<Vec<u8>>> {
176 let num_blocks = gcm_block_count(data_len)?;
179 let mut keystream = Zeroizing::new(Vec::with_capacity(num_blocks * GCM_BLOCK_SIZE));
180
181 let mut counter = *j0;
182 let mut ctr_val = BigEndian::read_u32(&counter[12..16]).wrapping_add(1);
183 BigEndian::write_u32(&mut counter[12..16], ctr_val);
184
185 for _ in 0..num_blocks {
186 let mut block = Zeroizing::new(counter);
187 self.cipher.encrypt_block(block.as_mut())?;
188 keystream.extend_from_slice(block.as_ref());
189 ctr_val = ctr_val.wrapping_add(1);
190 BigEndian::write_u32(&mut counter[12..16], ctr_val);
191 }
192
193 Ok(keystream)
194 }
195
196 fn generate_tag(
198 &self,
199 j0: &[u8; GCM_BLOCK_SIZE],
200 aad: &[u8],
201 ciphertext: &[u8],
202 ) -> Result<[u8; GCM_TAG_SIZE]> {
203 let h_array: &[u8; GCM_BLOCK_SIZE] = self
205 .h
206 .as_ref()
207 .try_into()
208 .expect("SecretBuffer has correct size");
209
210 let mut tag = process_ghash(h_array, aad, ciphertext)?;
212
213 let mut j0_copy = Zeroizing::new(*j0);
215 self.cipher.encrypt_block(j0_copy.as_mut())?;
216
217 for i in 0..GCM_TAG_SIZE {
219 tag[i] ^= j0_copy[i];
220 }
221
222 Ok(tag)
223 }
224
225 pub fn internal_encrypt<const N: usize>(
227 &self,
228 nonce: &Nonce<N>,
229 plaintext: &[u8],
230 associated_data: Option<&[u8]>,
231 ) -> Result<Vec<u8>>
232 where
233 Nonce<N>: AesGcmCompatible,
234 {
235 let aad = associated_data.unwrap_or(&[]);
236 let j0 = self.generate_j0(nonce)?;
237
238 let keystream = if plaintext.is_empty() {
239 None
240 } else {
241 Some(self.generate_keystream(&j0, plaintext.len())?)
242 };
243 let output_len = plaintext
244 .len()
245 .checked_add(self.tag_len)
246 .ok_or(Error::Processing {
247 operation: "GCM encryption",
248 details: "ciphertext length overflow",
249 })?;
250 let mut ciphertext = Vec::with_capacity(output_len);
251 if let Some(keystream) = keystream {
252 for i in 0..plaintext.len() {
253 ciphertext.push(plaintext[i] ^ keystream[i]);
254 }
255 }
256
257 let full_tag = self.generate_tag(&j0, aad, &ciphertext)?;
258 ciphertext.extend_from_slice(&full_tag[..self.tag_len]);
259 Ok(ciphertext)
260 }
261
262 pub fn internal_decrypt<const N: usize>(
264 &self,
265 nonce: &Nonce<N>,
266 ciphertext: &[u8],
267 associated_data: Option<&[u8]>,
268 ) -> Result<Vec<u8>>
269 where
270 Nonce<N>: AesGcmCompatible,
271 {
272 validate::min_length("GCM ciphertext", ciphertext.len(), self.tag_len)?;
274
275 let aad = associated_data.unwrap_or(&[]);
276 let ciphertext_len = ciphertext.len() - self.tag_len;
277 let (ciphertext_data, received_tag) = ciphertext.split_at(ciphertext_len);
278
279 let j0 = self.generate_j0(nonce)?;
281 let full_expected = self.generate_tag(&j0, aad, ciphertext_data)?;
282 let expected_tag = &full_expected[..self.tag_len];
283
284 let keystream = self.generate_keystream(&j0, ciphertext_len)?;
286 let mut plaintext = Zeroizing::new(Vec::with_capacity(ciphertext_len));
287 for i in 0..ciphertext_len {
288 plaintext.push(ciphertext_data[i] ^ keystream[i]);
289 }
290
291 let tag_matches = expected_tag.ct_eq(received_tag);
293
294 if tag_matches.unwrap_u8() == 0 {
298 Err(Error::Authentication { algorithm: "GCM" })
299 } else {
300 Ok(plaintext.to_vec())
301 }
302 }
303}
304
305impl<B: BlockCipher + Zeroize + ZeroizeOnDrop> AuthenticatedCipher for Gcm<B> {
307 const TAG_SIZE: usize = GCM_TAG_SIZE;
308 const ALGORITHM_ID: &'static str = "GCM";
309}
310
311impl<B> SymmetricCipher for Gcm<B>
313where
314 B: BlockCipher + Zeroize + ZeroizeOnDrop,
315 B::Key: GcmKey,
316{
317 type Key = B::Key;
318 type Nonce = Nonce<12>; type Ciphertext = Ciphertext;
320 type EncryptOperation<'a>
321 = GcmEncryptOperation<'a, B>
322 where
323 Self: 'a;
324 type DecryptOperation<'a>
325 = GcmDecryptOperation<'a, B>
326 where
327 Self: 'a;
328
329 fn name() -> &'static str {
330 "GCM"
331 }
332
333 fn encrypt(&self) -> <Self as SymmetricCipher>::EncryptOperation<'_> {
334 GcmEncryptOperation {
335 cipher: self,
336 nonce: None,
337 aad: None,
338 }
339 }
340
341 fn decrypt(&self) -> <Self as SymmetricCipher>::DecryptOperation<'_> {
342 GcmDecryptOperation {
343 cipher: self,
344 nonce: None,
345 aad: None,
346 }
347 }
348
349 fn generate_key<R: rand::RngCore + rand::CryptoRng>(
350 rng: &mut R,
351 ) -> std::result::Result<<Self as SymmetricCipher>::Key, CoreError> {
352 Ok(B::generate_key(rng))
353 }
354
355 fn generate_nonce<R: rand::RngCore + rand::CryptoRng>(
356 rng: &mut R,
357 ) -> std::result::Result<<Self as SymmetricCipher>::Nonce, CoreError> {
358 let mut nonce_data = [0u8; 12];
359 rng.fill_bytes(&mut nonce_data);
360 Ok(Nonce::<12>::new(nonce_data)) }
362
363 fn derive_key_from_bytes(
364 bytes: &[u8],
365 ) -> std::result::Result<<Self as SymmetricCipher>::Key, CoreError> {
366 if bytes.len() != B::key_size() {
367 return Err(CoreError::InvalidLength {
368 context: "GCM key derivation",
369 expected: B::key_size(),
370 actual: bytes.len(),
371 });
372 }
373 B::Key::from_key_bytes(bytes)
374 }
375}
376
377impl<B> Operation<Ciphertext> for GcmEncryptOperation<'_, B>
379where
380 B: BlockCipher + Zeroize + ZeroizeOnDrop,
381 B::Key: GcmKey,
382{
383 fn execute(self) -> std::result::Result<Ciphertext, CoreError> {
384 let nonce = self.nonce.ok_or_else(|| CoreError::InvalidParameter {
385 context: "GCM encryption",
386 #[cfg(feature = "std")]
387 message: "Nonce is required for GCM encryption".to_string(),
388 })?;
389 let plaintext = b""; let ciphertext = self
392 .cipher
393 .internal_encrypt(nonce, plaintext, self.aad)
394 .map_err(CoreError::from)?;
395
396 Ok(Ciphertext::new(ciphertext))
397 }
398}
399
400impl<'a, B> EncryptOperation<'a, Gcm<B>> for GcmEncryptOperation<'a, B>
402where
403 B: BlockCipher + Zeroize + ZeroizeOnDrop,
404 B::Key: GcmKey,
405{
406 fn with_nonce(mut self, nonce: &'a <Gcm<B> as SymmetricCipher>::Nonce) -> Self {
407 self.nonce = Some(nonce);
408 self
409 }
410
411 fn with_aad(mut self, aad: &'a [u8]) -> Self {
412 self.aad = Some(aad);
413 self
414 }
415
416 fn encrypt(self, plaintext: &'a [u8]) -> std::result::Result<Ciphertext, CoreError> {
417 let nonce = self.nonce.ok_or_else(|| CoreError::InvalidParameter {
418 context: "GCM encryption",
419 #[cfg(feature = "std")]
420 message: "Nonce is required for GCM encryption".to_string(),
421 })?;
422
423 let ciphertext = self
424 .cipher
425 .internal_encrypt(nonce, plaintext, self.aad)
426 .map_err(CoreError::from)?;
427
428 Ok(Ciphertext::new(ciphertext))
429 }
430}
431
432impl<B> Operation<Vec<u8>> for GcmDecryptOperation<'_, B>
434where
435 B: BlockCipher + Zeroize + ZeroizeOnDrop,
436 B::Key: GcmKey,
437{
438 fn execute(self) -> std::result::Result<Vec<u8>, CoreError> {
439 Err(CoreError::InvalidParameter {
440 context: "GCM decryption",
441 #[cfg(feature = "std")]
442 message: "Use decrypt method instead".to_string(),
443 })
444 }
445}
446
447impl<'a, B> DecryptOperation<'a, Gcm<B>> for GcmDecryptOperation<'a, B>
449where
450 B: BlockCipher + Zeroize + ZeroizeOnDrop,
451 B::Key: GcmKey,
452{
453 fn with_nonce(mut self, nonce: &'a <Gcm<B> as SymmetricCipher>::Nonce) -> Self {
454 self.nonce = Some(nonce);
455 self
456 }
457
458 fn with_aad(mut self, aad: &'a [u8]) -> Self {
459 self.aad = Some(aad);
460 self
461 }
462
463 fn decrypt(
464 self,
465 ciphertext: &'a <Gcm<B> as SymmetricCipher>::Ciphertext,
466 ) -> std::result::Result<Vec<u8>, CoreError> {
467 let nonce = self.nonce.ok_or_else(|| CoreError::InvalidParameter {
468 context: "GCM decryption",
469 #[cfg(feature = "std")]
470 message: "Nonce is required for GCM decryption".to_string(),
471 })?;
472
473 self.cipher
474 .internal_decrypt(nonce, ciphertext.as_ref(), self.aad)
475 .map_err(CoreError::from)
476 }
477}
478
479#[cfg(test)]
480mod tests;