dcrypt-algorithms 4.0.1

Cryptographic primitives for the dcrypt library
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
//! Galois/Counter Mode (GCM) for authenticated encryption
//!
//! GCM is an authenticated encryption with associated data (AEAD) mode
//! that provides both confidentiality and authenticity. It combines the
//! Counter (CTR) mode with the GHASH authentication function.
//!
//! ## Implementation Note
//!
//! This implementation is tested against the repository's byte-bound
//! CAVP/ACVP-format known-answer data. The local fixtures' upstream acquisition
//! provenance is unverified; vector tests are not a FIPS validation or
//! certification claim.
//!
//! ## Timing behavior
//!
//! Authentication tag bytes are compared with dcrypt's owned constant-time trait after
//! checking the public length. Input lengths, state errors, and authentication
//! results use ordinary branching. No blanket side-channel guarantee is made
//! for every backend, compiler, or target.

// Conditionally import Vec based on available features
#[cfg(not(feature = "std"))]
#[cfg(feature = "alloc")]
use alloc::vec::Vec;

#[cfg(feature = "std")]
use std::vec::Vec;

use dcrypt_internal::constant_time::ConstantTimeEq;
use dcrypt_internal::random::{try_fill_bytes_zeroing_on_error, CryptoRng, RngCore};
use dcrypt_internal::zeroing::{
    boxed_bytes_zeroed, Zeroize, ZeroizeOnDrop, Zeroizing, ZeroizingBytes,
};

// Import security types from dcrypt-core - FIXED PATH
use dcrypt_common::security::SecretBuffer;

// Fix import paths by using crate:: for internal modules
use crate::block::BlockCipher;
use dcrypt_api::traits::symmetric::{DecryptOperation, EncryptOperation, Operation};
use dcrypt_api::traits::AuthenticatedCipher;
use dcrypt_api::traits::SymmetricCipher;

use crate::error::{validate, Error, Result};
use crate::types::nonce::AesGcmCompatible; // Import the AesGcmCompatible trait
use crate::types::Nonce; // Using generic Nonce type
use crate::types::SecretBytes;
use dcrypt_api::error::Error as CoreError;
use dcrypt_api::types::Ciphertext;

// Import the GHASH module
mod ghash;
use ghash::{process_ghash, GHash};

// GCM constants
const GCM_BLOCK_SIZE: usize = 16;
const GCM_TAG_SIZE: usize = 16;

/// GCM mode implementation
#[derive(Clone)]
pub struct Gcm<B: BlockCipher + Zeroize + ZeroizeOnDrop> {
    cipher: B,
    h: SecretBuffer<GCM_BLOCK_SIZE>, // GHASH key (encrypted all-zero block) - now secured
    tag_len: usize,                  // desired tag length in bytes
}

impl<B: BlockCipher + Zeroize + ZeroizeOnDrop> Zeroize for Gcm<B> {
    fn zeroize(&mut self) {
        self.cipher.zeroize();
        self.h.zeroize();
        self.tag_len.zeroize();
    }
}

impl<B: BlockCipher + Zeroize + ZeroizeOnDrop> Drop for Gcm<B> {
    fn drop(&mut self) {
        self.zeroize();
    }
}

impl<B: BlockCipher + Zeroize + ZeroizeOnDrop> ZeroizeOnDrop for Gcm<B> {}

/// Key construction needed by the generic [`SymmetricCipher`] adapter.
pub trait GcmKey: AsRef<[u8]> + AsMut<[u8]> + Clone + Zeroize {
    /// Construct an exactly-sized block-cipher key from caller-provided bytes.
    fn from_key_bytes(bytes: &[u8]) -> core::result::Result<Self, CoreError>;
}

impl<const N: usize> GcmKey for SecretBytes<N> {
    fn from_key_bytes(bytes: &[u8]) -> core::result::Result<Self, CoreError> {
        SecretBytes::<N>::from_slice(bytes)
    }
}

fn gcm_block_count(data_len: usize) -> Result<usize> {
    let num_blocks = data_len.div_ceil(GCM_BLOCK_SIZE);
    validate::parameter(
        (num_blocks as u128) <= u128::from(u32::MAX - 1),
        "message_length",
        "GCM message exceeds the 2^32-2 block construction limit",
    )?;
    Ok(num_blocks)
}

/// Operation for GCM encryption operations
pub struct GcmEncryptOperation<'a, B: BlockCipher + Zeroize + ZeroizeOnDrop> {
    cipher: &'a Gcm<B>,
    nonce: Option<&'a Nonce<12>>, // Using generic Nonce<12> instead of Nonce12
    aad: Option<&'a [u8]>,
}

/// Operation for GCM decryption operations
pub struct GcmDecryptOperation<'a, B: BlockCipher + Zeroize + ZeroizeOnDrop> {
    cipher: &'a Gcm<B>,
    nonce: Option<&'a Nonce<12>>, // Using generic Nonce<12> instead of Nonce12
    aad: Option<&'a [u8]>,
}

impl<B: BlockCipher + Zeroize + ZeroizeOnDrop> Gcm<B> {
    /// Creates a key-only GCM instance with a fixed 16-byte tag.
    ///
    /// The nonce is deliberately supplied to each encrypt/decrypt operation;
    /// retaining it in this reusable object made accidental nonce reuse easy.
    pub fn new(cipher: B) -> Result<Self> {
        Self::new_with_tag_len(cipher, GCM_TAG_SIZE)
    }

    /// Creates a new GCM mode instance with specified tag length (in bytes).
    ///
    /// Truncated tags are supported only from 12 through 16 bytes.
    pub fn new_with_tag_len(cipher: B, tag_len: usize) -> Result<Self> {
        // Ensure block size
        validate::parameter(
            B::block_size() == GCM_BLOCK_SIZE,
            "block_size",
            "GCM only works with 128-bit block ciphers",
        )?;

        validate::parameter(
            (12..=GCM_TAG_SIZE).contains(&tag_len),
            "tag_length",
            "GCM tag length must be between 12 and 16 bytes",
        )?;

        // Generate GHASH key H (encrypt all-zero block)
        let mut h_bytes = Zeroizing::new([0u8; GCM_BLOCK_SIZE]);
        cipher.encrypt_block(h_bytes.as_mut())?;

        // Wrap the GHASH key in SecretBuffer for secure storage
        let h = SecretBuffer::new(*h_bytes);

        Ok(Self { cipher, h, tag_len })
    }

    /// Generate initial counter value J0
    fn generate_j0<const N: usize>(
        &self,
        nonce: &Nonce<N>,
    ) -> Result<Zeroizing<[u8; GCM_BLOCK_SIZE]>>
    where
        Nonce<N>: AesGcmCompatible,
    {
        validate::parameter(
            !nonce.is_empty() && nonce.len() <= 16,
            "nonce_length",
            "GCM nonce must be between 1 and 16 bytes",
        )?;
        let mut j0 = Zeroizing::new([0u8; GCM_BLOCK_SIZE]);
        if nonce.len() == 12 {
            j0[..12].copy_from_slice(nonce.as_ref());
            j0[15] = 1;
        } else {
            // Convert SecretBuffer reference to array reference
            let h_array: &[u8; GCM_BLOCK_SIZE] = self
                .h
                .as_ref()
                .try_into()
                .expect("SecretBuffer has correct size");

            let mut g = GHash::new(h_array);
            // GHash::update already pads its final partial block. Adding a
            // second explicit padding update here produced a non-standard J0.
            g.update(nonce.as_ref())?;
            g.update_lengths(0, nonce.len() as u64)?;
            j0 = g.finalize_protected();
        }
        Ok(j0)
    }

    /// Generate encryption keystream for CTR mode
    fn generate_keystream(
        &self,
        j0: &[u8; GCM_BLOCK_SIZE],
        data_len: usize,
    ) -> Result<ZeroizingBytes> {
        // Validate the construction limit before allocating output. This also
        // makes the limit directly testable without constructing a huge slice.
        let num_blocks = gcm_block_count(data_len)?;
        let mut keystream = Zeroizing::new(boxed_bytes_zeroed(num_blocks * GCM_BLOCK_SIZE));
        let mut keystream_offset = 0usize;

        let mut counter = Zeroizing::new(*j0);
        let mut ctr_val =
            u32::from_be_bytes(counter[12..16].try_into().expect("four bytes")).wrapping_add(1);
        counter[12..16].copy_from_slice(&ctr_val.to_be_bytes());

        for _ in 0..num_blocks {
            let mut block = Zeroizing::new(*counter);
            self.cipher.encrypt_block(block.as_mut())?;
            keystream[keystream_offset..keystream_offset + GCM_BLOCK_SIZE]
                .copy_from_slice(block.as_ref());
            keystream_offset += GCM_BLOCK_SIZE;
            ctr_val = ctr_val.wrapping_add(1);
            counter[12..16].copy_from_slice(&ctr_val.to_be_bytes());
        }

        Ok(keystream)
    }

    /// Generate authentication tag (full 16 bytes)
    fn generate_tag(
        &self,
        j0: &[u8; GCM_BLOCK_SIZE],
        aad: &[u8],
        ciphertext: &[u8],
    ) -> Result<[u8; GCM_TAG_SIZE]> {
        // Convert SecretBuffer reference to array reference
        let h_array: &[u8; GCM_BLOCK_SIZE] = self
            .h
            .as_ref()
            .try_into()
            .expect("SecretBuffer has correct size");

        // Process the AAD and ciphertext with GHASH
        let mut tag = process_ghash(h_array, aad, ciphertext)?;

        // Encrypt the initial counter block
        let mut j0_copy = Zeroizing::new(*j0);
        self.cipher.encrypt_block(j0_copy.as_mut())?;

        // XOR the encrypted counter with the GHASH result
        for i in 0..GCM_TAG_SIZE {
            tag[i] ^= j0_copy[i];
        }

        Ok(tag)
    }

    /// Internal encrypt method - exposed for testing
    pub fn internal_encrypt<const N: usize>(
        &self,
        nonce: &Nonce<N>,
        plaintext: &[u8],
        associated_data: Option<&[u8]>,
    ) -> Result<Vec<u8>>
    where
        Nonce<N>: AesGcmCompatible,
    {
        let aad = associated_data.unwrap_or(&[]);
        let j0 = self.generate_j0(nonce)?;

        let keystream = if plaintext.is_empty() {
            None
        } else {
            Some(self.generate_keystream(&*j0, plaintext.len())?)
        };
        let output_len = plaintext
            .len()
            .checked_add(self.tag_len)
            .ok_or(Error::Processing {
                operation: "GCM encryption",
                details: "ciphertext length overflow",
            })?;
        let mut ciphertext = Vec::with_capacity(output_len);
        if let Some(keystream) = keystream {
            for i in 0..plaintext.len() {
                ciphertext.push(plaintext[i] ^ keystream[i]);
            }
        }

        let full_tag = self.generate_tag(&*j0, aad, &ciphertext)?;
        ciphertext.extend_from_slice(&full_tag[..self.tag_len]);
        Ok(ciphertext)
    }

    /// Internal decrypt method; exposed for testing.
    pub fn internal_decrypt<const N: usize>(
        &self,
        nonce: &Nonce<N>,
        ciphertext: &[u8],
        associated_data: Option<&[u8]>,
    ) -> Result<Vec<u8>>
    where
        Nonce<N>: AesGcmCompatible,
    {
        Ok(self
            .internal_decrypt_protected(nonce, ciphertext, associated_data)?
            .into_inner()
            .into_vec())
    }

    /// Decrypt into exact-size storage that clears itself on all internal
    /// error and drop paths. Callers should use this for intermediate
    /// plaintext that has not yet crossed a public output boundary.
    pub fn internal_decrypt_protected<const N: usize>(
        &self,
        nonce: &Nonce<N>,
        ciphertext: &[u8],
        associated_data: Option<&[u8]>,
    ) -> Result<ZeroizingBytes>
    where
        Nonce<N>: AesGcmCompatible,
    {
        // Length check is not a secret-dependent branch
        validate::min_length("GCM ciphertext", ciphertext.len(), self.tag_len)?;

        let aad = associated_data.unwrap_or(&[]);
        let ciphertext_len = ciphertext.len() - self.tag_len;
        let (ciphertext_data, received_tag) = ciphertext.split_at(ciphertext_len);

        // Generate initial counter and expected tag
        let j0 = self.generate_j0(nonce)?;
        let full_expected = self.generate_tag(&*j0, aad, ciphertext_data)?;
        let expected_tag = &full_expected[..self.tag_len];

        // Generate keystream and decrypt data
        let keystream = self.generate_keystream(&*j0, ciphertext_len)?;
        let mut plaintext = Zeroizing::new(boxed_bytes_zeroed(ciphertext_len));
        for i in 0..ciphertext_len {
            plaintext[i] = ciphertext_data[i] ^ keystream[i];
        }

        // Compare all tag bytes without a value-dependent early exit.
        let tag_matches = expected_tag.ct_eq(received_tag);

        // The tag bytes are compared without a value-dependent early exit. The
        // public result/error branch is not a blanket constant-time claim for
        // the complete decrypt operation.
        if tag_matches.unwrap_u8() == 0 {
            Err(Error::Authentication { algorithm: "GCM" })
        } else {
            Ok(plaintext)
        }
    }
}

// Implement the marker trait AuthenticatedCipher
impl<B: BlockCipher + Zeroize + ZeroizeOnDrop> AuthenticatedCipher for Gcm<B> {
    const TAG_SIZE: usize = GCM_TAG_SIZE;
    const ALGORITHM_ID: &'static str = "GCM";
}

// Implement SymmetricCipher trait
impl<B> SymmetricCipher for Gcm<B>
where
    B: BlockCipher + Zeroize + ZeroizeOnDrop,
    B::Key: GcmKey,
{
    type Key = B::Key;
    type Nonce = Nonce<12>; // Using generic Nonce<12> instead of Nonce12
    type Ciphertext = Ciphertext;
    type EncryptOperation<'a>
        = GcmEncryptOperation<'a, B>
    where
        Self: 'a;
    type DecryptOperation<'a>
        = GcmDecryptOperation<'a, B>
    where
        Self: 'a;

    fn name() -> &'static str {
        "GCM"
    }

    fn encrypt(&self) -> <Self as SymmetricCipher>::EncryptOperation<'_> {
        GcmEncryptOperation {
            cipher: self,
            nonce: None,
            aad: None,
        }
    }

    fn decrypt(&self) -> <Self as SymmetricCipher>::DecryptOperation<'_> {
        GcmDecryptOperation {
            cipher: self,
            nonce: None,
            aad: None,
        }
    }

    fn generate_key<R: RngCore + CryptoRng>(
        rng: &mut R,
    ) -> core::result::Result<<Self as SymmetricCipher>::Key, CoreError> {
        B::generate_key(rng).map_err(CoreError::from)
    }

    fn generate_nonce<R: RngCore + CryptoRng>(
        rng: &mut R,
    ) -> core::result::Result<<Self as SymmetricCipher>::Nonce, CoreError> {
        let mut nonce_data = [0u8; 12];
        try_fill_bytes_zeroing_on_error(rng, &mut nonce_data).map_err(|_| CoreError::Other {
            context: "randomness",
            #[cfg(feature = "std")]
            message: "caller-provided randomness source failed".to_string(),
        })?;
        Ok(Nonce::<12>::new(nonce_data)) // Using generic Nonce::<12> instead of Nonce12
    }

    fn derive_key_from_bytes(
        bytes: &[u8],
    ) -> core::result::Result<<Self as SymmetricCipher>::Key, CoreError> {
        if bytes.len() != B::key_size() {
            return Err(CoreError::InvalidLength {
                context: "GCM key derivation",
                expected: B::key_size(),
                actual: bytes.len(),
            });
        }
        B::Key::from_key_bytes(bytes)
    }
}

// Implement Operation for GcmEncryptOperation
impl<B> Operation<Ciphertext> for GcmEncryptOperation<'_, B>
where
    B: BlockCipher + Zeroize + ZeroizeOnDrop,
    B::Key: GcmKey,
{
    fn execute(self) -> core::result::Result<Ciphertext, CoreError> {
        let nonce = self.nonce.ok_or_else(|| CoreError::InvalidParameter {
            context: "GCM encryption",
            #[cfg(feature = "std")]
            message: "Nonce is required for GCM encryption".to_string(),
        })?;
        let plaintext = b""; // Default empty plaintext

        let ciphertext = self
            .cipher
            .internal_encrypt(nonce, plaintext, self.aad)
            .map_err(CoreError::from)?;

        Ok(Ciphertext::new(ciphertext))
    }
}

// Implement EncryptOperation for GcmEncryptOperation
impl<'a, B> EncryptOperation<'a, Gcm<B>> for GcmEncryptOperation<'a, B>
where
    B: BlockCipher + Zeroize + ZeroizeOnDrop,
    B::Key: GcmKey,
{
    fn with_nonce(mut self, nonce: &'a <Gcm<B> as SymmetricCipher>::Nonce) -> Self {
        self.nonce = Some(nonce);
        self
    }

    fn with_aad(mut self, aad: &'a [u8]) -> Self {
        self.aad = Some(aad);
        self
    }

    fn encrypt(self, plaintext: &'a [u8]) -> core::result::Result<Ciphertext, CoreError> {
        let nonce = self.nonce.ok_or_else(|| CoreError::InvalidParameter {
            context: "GCM encryption",
            #[cfg(feature = "std")]
            message: "Nonce is required for GCM encryption".to_string(),
        })?;

        let ciphertext = self
            .cipher
            .internal_encrypt(nonce, plaintext, self.aad)
            .map_err(CoreError::from)?;

        Ok(Ciphertext::new(ciphertext))
    }
}

// Implement Operation for GcmDecryptOperation
impl<B> Operation<Vec<u8>> for GcmDecryptOperation<'_, B>
where
    B: BlockCipher + Zeroize + ZeroizeOnDrop,
    B::Key: GcmKey,
{
    fn execute(self) -> core::result::Result<Vec<u8>, CoreError> {
        Err(CoreError::InvalidParameter {
            context: "GCM decryption",
            #[cfg(feature = "std")]
            message: "Use decrypt method instead".to_string(),
        })
    }
}

// Implement DecryptOperation for GcmDecryptOperation
impl<'a, B> DecryptOperation<'a, Gcm<B>> for GcmDecryptOperation<'a, B>
where
    B: BlockCipher + Zeroize + ZeroizeOnDrop,
    B::Key: GcmKey,
{
    fn with_nonce(mut self, nonce: &'a <Gcm<B> as SymmetricCipher>::Nonce) -> Self {
        self.nonce = Some(nonce);
        self
    }

    fn with_aad(mut self, aad: &'a [u8]) -> Self {
        self.aad = Some(aad);
        self
    }

    fn decrypt(
        self,
        ciphertext: &'a <Gcm<B> as SymmetricCipher>::Ciphertext,
    ) -> core::result::Result<Vec<u8>, CoreError> {
        let nonce = self.nonce.ok_or_else(|| CoreError::InvalidParameter {
            context: "GCM decryption",
            #[cfg(feature = "std")]
            message: "Nonce is required for GCM decryption".to_string(),
        })?;

        self.cipher
            .internal_decrypt(nonce, ciphertext.as_ref(), self.aad)
            .map_err(CoreError::from)
    }
}

#[cfg(test)]
mod tests;