Skip to main content

aws_lc_rs/cipher/
padded.rs

1// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: Apache-2.0 OR ISC
3use crate::cipher;
4use crate::cipher::key::SymmetricCipherKey;
5use crate::cipher::{
6    Algorithm, DecryptionContext, EncryptionContext, OperatingMode, UnboundCipherKey,
7    MAX_CIPHER_BLOCK_LEN,
8};
9use crate::error::Unspecified;
10use core::fmt::Debug;
11
12/// The cipher block padding strategy.
13#[non_exhaustive]
14#[derive(Debug, PartialEq, Eq, Clone, Copy)]
15pub(crate) enum PaddingStrategy {
16    /// ISO 10126 padding. For compatibility purposes only. Applies non-random PKCS7 padding.
17    ISO10126,
18    /// PKCS#7 Padding. ([See RFC 5652](https://datatracker.ietf.org/doc/html/rfc5652#section-6.3))
19    PKCS7,
20}
21
22impl PaddingStrategy {
23    fn add_padding<InOut>(self, block_len: usize, in_out: &mut InOut) -> Result<(), Unspecified>
24    where
25        InOut: AsMut<[u8]> + for<'in_out> Extend<&'in_out u8>,
26    {
27        match self {
28            // PKCS7 padding can be unpadded as ISO 10126 padding
29            PaddingStrategy::ISO10126 | PaddingStrategy::PKCS7 => {
30                let mut padding_buffer = [0u8; MAX_CIPHER_BLOCK_LEN];
31
32                let in_out_len = in_out.as_mut().len();
33                // This implements PKCS#7 padding scheme, used by aws-lc if we were using EVP_CIPHER API's
34                let remainder = in_out_len % block_len;
35                let padding_size = block_len - remainder;
36                let v: u8 = padding_size.try_into().map_err(|_| Unspecified)?;
37                padding_buffer.fill(v);
38                // Possible heap allocation here :(
39                in_out.extend(padding_buffer[0..padding_size].iter());
40            }
41        }
42        Ok(())
43    }
44
45    fn remove_padding(self, block_len: usize, in_out: &mut [u8]) -> Result<&mut [u8], Unspecified> {
46        if in_out.is_empty() || in_out.len() < block_len {
47            return Err(Unspecified);
48        }
49        match self {
50            PaddingStrategy::ISO10126 => {
51                let padding: u8 = in_out[in_out.len() - 1];
52                if padding == 0 || padding as usize > block_len {
53                    return Err(Unspecified);
54                }
55
56                // ISO 10126 padding is a random padding scheme, so we cannot verify the padding bytes
57                let final_len = in_out.len() - padding as usize;
58                Ok(&mut in_out[0..final_len])
59            }
60            PaddingStrategy::PKCS7 => {
61                let block_size: u8 = block_len.try_into().map_err(|_| Unspecified)?;
62
63                let padding: u8 = in_out[in_out.len() - 1];
64                if padding == 0 || padding > block_size {
65                    return Err(Unspecified);
66                }
67
68                for item in in_out.iter().skip(in_out.len() - padding as usize) {
69                    if *item != padding {
70                        return Err(Unspecified);
71                    }
72                }
73
74                let final_len = in_out.len() - padding as usize;
75                Ok(&mut in_out[0..final_len])
76            }
77        }
78    }
79}
80
81/// A cipher encryption key that performs block padding.
82pub struct PaddedBlockEncryptingKey {
83    algorithm: &'static Algorithm,
84    key: SymmetricCipherKey,
85    mode: OperatingMode,
86    padding: PaddingStrategy,
87}
88
89impl PaddedBlockEncryptingKey {
90    /// Constructs a new `PaddedBlockEncryptingKey` cipher with chaining block cipher (CBC) mode.
91    /// Plaintext data is padded following the PKCS#7 scheme.
92    ///
93    // # FIPS
94    // Use this function with an `UnboundCipherKey` constructed with one of the following algorithms:
95    // * `AES_128`
96    // * `AES_256`
97    //
98    /// # Errors
99    /// * [`Unspecified`]: Returned if there is an error constructing a `PaddedBlockEncryptingKey`.
100    ///   With `legacy-des` enabled, also returned if `key` was constructed with
101    ///   `DES_FOR_LEGACY_USE_ONLY`, `DES_EDE_FOR_LEGACY_USE_ONLY` or
102    ///   `DES_EDE3_FOR_LEGACY_USE_ONLY` and the provided key material contains
103    ///   weak or semi-weak DES subkeys, or (for Triple DES) a degenerate subkey
104    ///   configuration (e.g. `K1 == K2` for 2TDEA, or any pairwise equality
105    ///   for 3TDEA).
106    pub fn cbc_pkcs7(key: UnboundCipherKey) -> Result<Self, Unspecified> {
107        Self::new(key, OperatingMode::CBC, PaddingStrategy::PKCS7)
108    }
109
110    /// Constructs a new `PaddedBlockEncryptingKey` cipher with electronic code book (ECB) mode.
111    /// Plaintext data is padded following the PKCS#7 scheme.
112    ///
113    /// # ☠️ ️️️DANGER ☠️
114    /// Offered for computability purposes only. This is an extremely dangerous mode, and
115    /// very likely not what you want to use.
116    ///
117    /// # Errors
118    /// * [`Unspecified`]: Returned if there is an error constructing a `PaddedBlockEncryptingKey`.
119    ///   With `legacy-des` enabled, also returned if `key` was constructed with
120    ///   `DES_FOR_LEGACY_USE_ONLY`, `DES_EDE_FOR_LEGACY_USE_ONLY` or
121    ///   `DES_EDE3_FOR_LEGACY_USE_ONLY` and the provided key material contains
122    ///   weak or semi-weak DES subkeys, or (for Triple DES) a degenerate subkey
123    ///   configuration (e.g. `K1 == K2` for 2TDEA, or any pairwise equality
124    ///   for 3TDEA).
125    pub fn ecb_pkcs7(key: UnboundCipherKey) -> Result<Self, Unspecified> {
126        Self::new(key, OperatingMode::ECB, PaddingStrategy::PKCS7)
127    }
128
129    fn new(
130        key: UnboundCipherKey,
131        mode: OperatingMode,
132        padding: PaddingStrategy,
133    ) -> Result<PaddedBlockEncryptingKey, Unspecified> {
134        let algorithm = key.algorithm();
135        if !algorithm.supports_mode(mode) {
136            return Err(Unspecified);
137        }
138        let key = key.try_into()?;
139        Ok(Self {
140            algorithm,
141            key,
142            mode,
143            padding,
144        })
145    }
146
147    /// Returns the cipher algorithm.
148    #[must_use]
149    pub fn algorithm(&self) -> &Algorithm {
150        self.algorithm
151    }
152
153    /// Returns the cipher operating mode.
154    #[must_use]
155    pub fn mode(&self) -> OperatingMode {
156        self.mode
157    }
158
159    /// Pads and encrypts data provided in `in_out` in-place.
160    /// Returns a references to the encrypted data.
161    ///
162    /// # Errors
163    /// * [`Unspecified`]: Returned if encryption fails.
164    pub fn encrypt<InOut>(&self, in_out: &mut InOut) -> Result<DecryptionContext, Unspecified>
165    where
166        InOut: AsMut<[u8]> + for<'a> Extend<&'a u8>,
167    {
168        let context = self.algorithm.new_encryption_context(self.mode)?;
169        self.less_safe_encrypt(in_out, context)
170    }
171
172    /// Pads and encrypts data provided in `in_out` in-place.
173    /// Returns a references to the encryted data.
174    ///
175    /// # Errors
176    /// * [`Unspecified`]: Returned if encryption fails.
177    pub fn less_safe_encrypt<InOut>(
178        &self,
179        in_out: &mut InOut,
180        context: EncryptionContext,
181    ) -> Result<DecryptionContext, Unspecified>
182    where
183        InOut: AsMut<[u8]> + for<'a> Extend<&'a u8>,
184    {
185        if !self
186            .algorithm()
187            .is_valid_encryption_context(self.mode, &context)
188        {
189            return Err(Unspecified);
190        }
191
192        self.padding
193            .add_padding(self.algorithm().block_len(), in_out)?;
194        cipher::encrypt(
195            self.algorithm(),
196            &self.key,
197            self.mode,
198            in_out.as_mut(),
199            context,
200        )
201    }
202}
203
204impl Debug for PaddedBlockEncryptingKey {
205    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
206        f.debug_struct("PaddedBlockEncryptingKey")
207            .field("algorithm", &self.algorithm)
208            .field("mode", &self.mode)
209            .field("padding", &self.padding)
210            .finish_non_exhaustive()
211    }
212}
213
214/// A cipher decryption key that performs block padding.
215pub struct PaddedBlockDecryptingKey {
216    algorithm: &'static Algorithm,
217    key: SymmetricCipherKey,
218    mode: OperatingMode,
219    padding: PaddingStrategy,
220}
221
222impl PaddedBlockDecryptingKey {
223    /// Constructs a new `PaddedBlockDecryptingKey` cipher with chaining block cipher (CBC) mode.
224    /// Decrypted data is unpadded following the PKCS#7 scheme.
225    ///
226    // # FIPS
227    // Use this function with an `UnboundCipherKey` constructed with one of the following algorithms:
228    // * `AES_128`
229    // * `AES_256`
230    //
231    /// # Errors
232    /// * [`Unspecified`]: Returned if there is an error constructing the `PaddedBlockDecryptingKey`.
233    ///   With `legacy-des` enabled, also returned if `key` was constructed with
234    ///   `DES_FOR_LEGACY_USE_ONLY`, `DES_EDE_FOR_LEGACY_USE_ONLY` or
235    ///   `DES_EDE3_FOR_LEGACY_USE_ONLY` and the provided key material contains
236    ///   weak or semi-weak DES subkeys, or (for Triple DES) a degenerate subkey
237    ///   configuration (e.g. `K1 == K2` for 2TDEA, or any pairwise equality
238    ///   for 3TDEA).
239    pub fn cbc_pkcs7(key: UnboundCipherKey) -> Result<Self, Unspecified> {
240        Self::new(key, OperatingMode::CBC, PaddingStrategy::PKCS7)
241    }
242
243    /// Constructs a new `PaddedBlockDecryptingKey` cipher with chaining block cipher (CBC) mode.
244    /// Decrypted data is unpadded following the ISO 10126 scheme
245    /// (compatible with PKCS#7 and ANSI X.923).
246    ///
247    /// Offered for computability purposes only.
248    ///
249    // # FIPS
250    // Use this function with an `UnboundCipherKey` constructed with one of the following algorithms:
251    // * `AES_128`
252    // * `AES_256`
253    //
254    /// # Errors
255    /// * [`Unspecified`]: Returned if there is an error constructing the `PaddedBlockDecryptingKey`.
256    ///   With `legacy-des` enabled, also returned if `key` was constructed with
257    ///   `DES_FOR_LEGACY_USE_ONLY`, `DES_EDE_FOR_LEGACY_USE_ONLY` or
258    ///   `DES_EDE3_FOR_LEGACY_USE_ONLY` and the provided key material contains
259    ///   weak or semi-weak DES subkeys, or (for Triple DES) a degenerate subkey
260    ///   configuration (e.g. `K1 == K2` for 2TDEA, or any pairwise equality
261    ///   for 3TDEA).
262    pub fn cbc_iso10126(key: UnboundCipherKey) -> Result<Self, Unspecified> {
263        Self::new(key, OperatingMode::CBC, PaddingStrategy::ISO10126)
264    }
265
266    /// Constructs a new `PaddedBlockDecryptingKey` cipher with electronic code book (ECB) mode.
267    /// Decrypted data is unpadded following the PKCS#7 scheme.
268    ///
269    /// # ☠️ ️️️DANGER ☠️
270    /// Offered for computability purposes only. This is an extremely dangerous mode, and
271    /// very likely not what you want to use.
272    ///
273    // # FIPS
274    // Use this function with an `UnboundCipherKey` constructed with one of the following algorithms:
275    // * `AES_128`
276    // * `AES_256`
277    //
278    /// # Errors
279    /// * [`Unspecified`]: Returned if there is an error constructing the `PaddedBlockDecryptingKey`.
280    ///   With `legacy-des` enabled, also returned if `key` was constructed with
281    ///   `DES_FOR_LEGACY_USE_ONLY`, `DES_EDE_FOR_LEGACY_USE_ONLY` or
282    ///   `DES_EDE3_FOR_LEGACY_USE_ONLY` and the provided key material contains
283    ///   weak or semi-weak DES subkeys, or (for Triple DES) a degenerate subkey
284    ///   configuration (e.g. `K1 == K2` for 2TDEA, or any pairwise equality
285    ///   for 3TDEA).
286    pub fn ecb_pkcs7(key: UnboundCipherKey) -> Result<Self, Unspecified> {
287        Self::new(key, OperatingMode::ECB, PaddingStrategy::PKCS7)
288    }
289
290    fn new(
291        key: UnboundCipherKey,
292        mode: OperatingMode,
293        padding: PaddingStrategy,
294    ) -> Result<PaddedBlockDecryptingKey, Unspecified> {
295        let algorithm = key.algorithm();
296        if !algorithm.supports_mode(mode) {
297            return Err(Unspecified);
298        }
299        let key = key.try_into()?;
300        Ok(PaddedBlockDecryptingKey {
301            algorithm,
302            key,
303            mode,
304            padding,
305        })
306    }
307
308    /// Returns the cipher algorithm.
309    #[must_use]
310    pub fn algorithm(&self) -> &Algorithm {
311        self.algorithm
312    }
313
314    /// Returns the cipher operating mode.
315    #[must_use]
316    pub fn mode(&self) -> OperatingMode {
317        self.mode
318    }
319
320    /// Decrypts and unpads data provided in `in_out` in-place.
321    /// Returns a references to the decrypted data.
322    ///
323    /// # Errors
324    /// * [`Unspecified`]: Returned if decryption fails.
325    pub fn decrypt<'in_out>(
326        &self,
327        in_out: &'in_out mut [u8],
328        context: DecryptionContext,
329    ) -> Result<&'in_out mut [u8], Unspecified> {
330        if !self
331            .algorithm()
332            .is_valid_decryption_context(self.mode, &context)
333        {
334            return Err(Unspecified);
335        }
336
337        let block_len = self.algorithm().block_len();
338        let padding = self.padding;
339        let mut in_out = cipher::decrypt(self.algorithm, &self.key, self.mode, in_out, context)?;
340        in_out = padding.remove_padding(block_len, in_out)?;
341        Ok(in_out)
342    }
343}
344
345impl Debug for PaddedBlockDecryptingKey {
346    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
347        f.debug_struct("PaddedBlockDecryptingKey")
348            .field("algorithm", &self.algorithm)
349            .field("mode", &self.mode)
350            .field("padding", &self.padding)
351            .finish_non_exhaustive()
352    }
353}
354
355#[cfg(test)]
356mod tests {
357    use crate::cipher::padded::PaddingStrategy;
358    use crate::cipher::{
359        Algorithm, EncryptionContext, OperatingMode, PaddedBlockDecryptingKey,
360        PaddedBlockEncryptingKey, UnboundCipherKey, AES_128, AES_256,
361    };
362    use crate::iv::FixedLength;
363    use crate::test::from_hex;
364
365    fn helper_test_padded_cipher_n_bytes(
366        key: &[u8],
367        alg: &'static Algorithm,
368        mode: OperatingMode,
369        padding: PaddingStrategy,
370        n: usize,
371    ) {
372        let mut input: Vec<u8> = Vec::with_capacity(n);
373        for i in 0..n {
374            let byte: u8 = i.try_into().unwrap();
375            input.push(byte);
376        }
377
378        let cipher_key = UnboundCipherKey::new(alg, key).unwrap();
379        let encrypting_key = PaddedBlockEncryptingKey::new(cipher_key, mode, padding).unwrap();
380
381        let mut in_out = input.clone();
382        let decrypt_iv = encrypting_key.encrypt(&mut in_out).unwrap();
383
384        if n > 5 {
385            // There's no more than a 1 in 2^48 chance that this will fail randomly
386            assert_ne!(input.as_slice(), in_out);
387        }
388
389        let cipher_key2 = UnboundCipherKey::new(alg, key).unwrap();
390        let decrypting_key = PaddedBlockDecryptingKey::new(cipher_key2, mode, padding).unwrap();
391
392        let plaintext = decrypting_key.decrypt(&mut in_out, decrypt_iv).unwrap();
393        assert_eq!(input.as_slice(), plaintext);
394    }
395
396    #[test]
397    fn test_unpad_iso10126() {
398        let mut input = from_hex("01020304050607fedcba9805").unwrap();
399        let padding = PaddingStrategy::ISO10126;
400        let block_len = 8;
401
402        let unpadded = padding.remove_padding(block_len, &mut input).unwrap();
403        assert_eq!(unpadded, &mut [1, 2, 3, 4, 5, 6, 7]);
404    }
405
406    #[test]
407    fn test_aes_128_cbc() {
408        let key = from_hex("000102030405060708090a0b0c0d0e0f").unwrap();
409        for i in 0..=50 {
410            helper_test_padded_cipher_n_bytes(
411                key.as_slice(),
412                &AES_128,
413                OperatingMode::CBC,
414                PaddingStrategy::PKCS7,
415                i,
416            );
417        }
418    }
419
420    #[test]
421    fn test_aes_256_cbc() {
422        let key =
423            from_hex("000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f").unwrap();
424        for i in 0..=50 {
425            helper_test_padded_cipher_n_bytes(
426                key.as_slice(),
427                &AES_256,
428                OperatingMode::CBC,
429                PaddingStrategy::PKCS7,
430                i,
431            );
432        }
433    }
434
435    macro_rules! padded_cipher_kat {
436        ($name:ident, $alg:expr, $mode:expr, $padding:expr, $key:literal, $iv: literal, $plaintext:literal, $ciphertext:literal) => {
437            #[test]
438            fn $name() {
439                let key = from_hex($key).unwrap();
440                let input = from_hex($plaintext).unwrap();
441                let expected_ciphertext = from_hex($ciphertext).unwrap();
442                let mut iv = from_hex($iv).unwrap();
443                let iv = {
444                    let slice = iv.as_mut_slice();
445                    let mut iv = [0u8; $iv.len() / 2];
446                    {
447                        let x = iv.as_mut_slice();
448                        x.copy_from_slice(slice);
449                    }
450                    iv
451                };
452
453                let ec = EncryptionContext::Iv128(FixedLength::from(iv));
454
455                let alg = $alg;
456
457                let unbound_key = UnboundCipherKey::new(alg, &key).unwrap();
458
459                let encrypting_key =
460                    PaddedBlockEncryptingKey::new(unbound_key, $mode, $padding).unwrap();
461
462                let mut in_out = input.clone();
463
464                let context = encrypting_key.less_safe_encrypt(&mut in_out, ec).unwrap();
465
466                if ($padding == PaddingStrategy::ISO10126) {
467                    // This padding scheme is technically non-deterministic in nature if the padding is more then one
468                    // byte. So just validate the input length of in_out is no longer the plaintext.
469                    assert_ne!(input, in_out[..input.len()]);
470                } else {
471                    assert_eq!(expected_ciphertext, in_out);
472                }
473
474                let unbound_key2 = UnboundCipherKey::new(alg, &key).unwrap();
475                let decrypting_key =
476                    PaddedBlockDecryptingKey::new(unbound_key2, $mode, $padding).unwrap();
477
478                let plaintext = decrypting_key.decrypt(&mut in_out, context).unwrap();
479                assert_eq!(input.as_slice(), plaintext);
480            }
481        };
482    }
483
484    padded_cipher_kat!(
485        test_iv_aes_128_cbc_16_bytes,
486        &AES_128,
487        OperatingMode::CBC,
488        PaddingStrategy::PKCS7,
489        "000102030405060708090a0b0c0d0e0f",
490        "00000000000000000000000000000000",
491        "00112233445566778899aabbccddeeff",
492        "69c4e0d86a7b0430d8cdb78070b4c55a9e978e6d16b086570ef794ef97984232"
493    );
494
495    padded_cipher_kat!(
496        test_iv_aes_256_cbc_15_bytes,
497        &AES_256,
498        OperatingMode::CBC,
499        PaddingStrategy::PKCS7,
500        "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
501        "00000000000000000000000000000000",
502        "00112233445566778899aabbccddee",
503        "2ddfb635a651a43f582997966840ca0c"
504    );
505
506    padded_cipher_kat!(
507        test_openssl_aes_128_cbc_15_bytes,
508        &AES_128,
509        OperatingMode::CBC,
510        PaddingStrategy::PKCS7,
511        "053304bb3899e1d99db9d29343ea782d",
512        "b5313560244a4822c46c2a0c9d0cf7fd",
513        "a3e4c990356c01f320043c3d8d6f43",
514        "ad96993f248bd6a29760ec7ccda95ee1"
515    );
516
517    padded_cipher_kat!(
518        test_openssl_aes_128_cbc_iso10126_15_bytes,
519        &AES_128,
520        OperatingMode::CBC,
521        PaddingStrategy::ISO10126,
522        "053304bb3899e1d99db9d29343ea782d",
523        "b5313560244a4822c46c2a0c9d0cf7fd",
524        "a3e4c990356c01f320043c3d8d6f43",
525        "ad96993f248bd6a29760ec7ccda95ee1"
526    );
527
528    padded_cipher_kat!(
529        test_openssl_aes_128_cbc_iso10126_16_bytes,
530        &AES_128,
531        OperatingMode::CBC,
532        PaddingStrategy::ISO10126,
533        "053304bb3899e1d99db9d29343ea782d",
534        "b83452fc9c80215a6ecdc505b5154c90",
535        "736e65616b7920726163636f6f6e7321",
536        "44563399c6bb2133e013161dc5bd4fa8ce83ef997ddb04bbbbe3632b68e9cde0"
537    );
538
539    padded_cipher_kat!(
540        test_openssl_aes_128_cbc_16_bytes,
541        &AES_128,
542        OperatingMode::CBC,
543        PaddingStrategy::PKCS7,
544        "95af71f1c63e4a1d0b0b1a27fb978283",
545        "89e40797dca70197ff87d3dbb0ef2802",
546        "aece7b5e3c3df1ffc9802d2dfe296dc7",
547        "301b5dab49fb11e919d0d39970d06739301919743304f23f3cbc67d28564b25b"
548    );
549
550    padded_cipher_kat!(
551        test_openssl_aes_256_cbc_15_bytes,
552        &AES_256,
553        OperatingMode::CBC,
554        PaddingStrategy::PKCS7,
555        "d369e03e9752784917cc7bac1db7399598d9555e691861d9dd7b3292a693ef57",
556        "1399bb66b2f6ad99a7f064140eaaa885",
557        "7385f5784b85bf0a97768ddd896d6d",
558        "4351082bac9b4593ae8848cc9dfb5a01"
559    );
560
561    padded_cipher_kat!(
562        test_openssl_aes_256_cbc_16_bytes,
563        &AES_256,
564        OperatingMode::CBC,
565        PaddingStrategy::PKCS7,
566        "d4a8206dcae01242f9db79a4ecfe277d0f7bb8ccbafd8f9809adb39f35aa9b41",
567        "24f6076548fb9d93c8f7ed9f6e661ef9",
568        "a39c1fdf77ea3e1f18178c0ec237c70a",
569        "f1af484830a149ee0387b854d65fe87ca0e62efc1c8e6909d4b9ab8666470453"
570    );
571}