askar_crypto/alg/aes/
cbc_hmac.rs

1//! AES-CBC-HMAC
2
3use core::marker::PhantomData;
4
5use aead::generic_array::ArrayLength;
6use aes_core::{Aes128, Aes256};
7use cbc::{Decryptor as CbcDec, Encryptor as CbcEnc};
8use cipher::{
9    block_padding::Pkcs7, BlockCipher, BlockDecryptMut, BlockEncryptMut, KeyInit, KeyIvInit,
10};
11use digest::{crypto_common::BlockSizeUser, Digest};
12use hmac::{Mac, SimpleHmac};
13use subtle::ConstantTimeEq;
14
15use super::{AesKey, AesType, NonceSize, TagSize};
16use crate::{
17    alg::AesTypes,
18    buffer::ResizeBuffer,
19    encrypt::{KeyAeadInPlace, KeyAeadMeta, KeyAeadParams},
20    error::Error,
21    generic_array::{
22        typenum::{consts, Unsigned},
23        GenericArray,
24    },
25};
26
27/// 128 bit AES-CBC with SHA-256 HMAC
28pub type A128CbcHs256 = AesCbcHmac<Aes128, sha2::Sha256>;
29
30impl AesType for A128CbcHs256 {
31    type KeySize = consts::U32;
32    const ALG_TYPE: AesTypes = AesTypes::A128CbcHs256;
33    const JWK_ALG: &'static str = "A128CBC-HS256";
34}
35
36/// 256 bit AES-CBC with SHA-512 HMAC
37pub type A256CbcHs512 = AesCbcHmac<Aes256, sha2::Sha512>;
38
39impl AesType for A256CbcHs512 {
40    type KeySize = consts::U64;
41    const ALG_TYPE: AesTypes = AesTypes::A256CbcHs512;
42    const JWK_ALG: &'static str = "A256CBC-HS512";
43}
44
45/// AES-CBC-HMAC implementation
46#[derive(Debug)]
47pub struct AesCbcHmac<C, D>(PhantomData<(C, D)>);
48
49impl<C, D> AesCbcHmac<C, D>
50where
51    C: BlockCipher,
52{
53    #[inline]
54    fn padding_length(len: usize) -> usize {
55        C::BlockSize::USIZE - (len % C::BlockSize::USIZE)
56    }
57}
58
59impl<C, D> KeyAeadMeta for AesKey<AesCbcHmac<C, D>>
60where
61    AesCbcHmac<C, D>: AesType,
62    C: BlockCipher + KeyInit,
63{
64    type NonceSize = C::BlockSize;
65    type TagSize = C::KeySize;
66}
67
68impl<C, D> KeyAeadInPlace for AesKey<AesCbcHmac<C, D>>
69where
70    AesCbcHmac<C, D>: AesType,
71    C: BlockCipher + KeyInit + BlockEncryptMut + BlockDecryptMut,
72    D: Digest + BlockSizeUser,
73    C::KeySize: core::ops::Shl<consts::B1>,
74    <C::KeySize as core::ops::Shl<consts::B1>>::Output: ArrayLength<u8>,
75{
76    fn encrypt_in_place(
77        &self,
78        buffer: &mut dyn ResizeBuffer,
79        nonce: &[u8],
80        aad: &[u8],
81    ) -> Result<usize, Error> {
82        if nonce.len() != NonceSize::<Self>::USIZE {
83            return Err(err_msg!(InvalidNonce));
84        }
85        // this should be optimized away except when the error is thrown
86        if TagSize::<Self>::USIZE > D::OutputSize::USIZE {
87            return Err(err_msg!(
88                Encryption,
89                "AES-CBC-HMAC tag size exceeds maximum supported"
90            ));
91        }
92        if aad.len() as u64 > u64::MAX / 8 {
93            return Err(err_msg!(
94                Encryption,
95                "AES-CBC-HMAC AAD size exceeds maximum supported"
96            ));
97        }
98
99        let msg_len = buffer.as_ref().len();
100        let pad_len = AesCbcHmac::<C, D>::padding_length(msg_len);
101        buffer.buffer_extend(pad_len + TagSize::<Self>::USIZE)?;
102        let enc_key = GenericArray::from_slice(&self.0[C::KeySize::USIZE..]);
103        <CbcEnc<C> as KeyIvInit>::new(enc_key, GenericArray::from_slice(nonce))
104            .encrypt_padded_mut::<Pkcs7>(buffer.as_mut(), msg_len)
105            .map_err(|_| err_msg!(Encryption, "AES-CBC encryption error"))?;
106        let ctext_end = msg_len + pad_len;
107
108        let mut hmac = <SimpleHmac<D> as Mac>::new_from_slice(&self.0[..C::KeySize::USIZE])
109            .expect("Incompatible HMAC key length");
110        hmac.update(aad);
111        hmac.update(nonce.as_ref());
112        hmac.update(&buffer.as_ref()[..ctext_end]);
113        hmac.update(&((aad.len() as u64) * 8).to_be_bytes());
114        let mac = hmac.finalize().into_bytes();
115        buffer.as_mut()[ctext_end..(ctext_end + TagSize::<Self>::USIZE)]
116            .copy_from_slice(&mac[..TagSize::<Self>::USIZE]);
117
118        Ok(ctext_end)
119    }
120
121    fn decrypt_in_place(
122        &self,
123        buffer: &mut dyn ResizeBuffer,
124        nonce: &[u8],
125        aad: &[u8],
126    ) -> Result<(), Error> {
127        if nonce.len() != NonceSize::<Self>::USIZE {
128            return Err(err_msg!(InvalidNonce));
129        }
130        if aad.len() as u64 > u64::MAX / 8 {
131            return Err(err_msg!(
132                Encryption,
133                "AES-CBC-HMAC AAD size exceeds maximum supported"
134            ));
135        }
136        let buf_len = buffer.as_ref().len();
137        if buf_len < TagSize::<Self>::USIZE {
138            return Err(err_msg!(Encryption, "Invalid size for encrypted data"));
139        }
140        let ctext_end = buf_len - TagSize::<Self>::USIZE;
141        let tag = GenericArray::<u8, TagSize<Self>>::from_slice(&buffer.as_ref()[ctext_end..]);
142
143        let mut hmac = <SimpleHmac<D> as Mac>::new_from_slice(&self.0[..C::KeySize::USIZE])
144            .expect("Incompatible HMAC key length");
145        hmac.update(aad);
146        hmac.update(nonce.as_ref());
147        hmac.update(&buffer.as_ref()[..ctext_end]);
148        hmac.update(&((aad.len() as u64) * 8).to_be_bytes());
149        let mac = hmac.finalize().into_bytes();
150        let tag_match = tag.as_ref().ct_eq(&mac[..TagSize::<Self>::USIZE]);
151
152        let enc_key = GenericArray::from_slice(&self.0[C::KeySize::USIZE..]);
153        let dec_len = <CbcDec<C> as KeyIvInit>::new(enc_key, GenericArray::from_slice(nonce))
154            .decrypt_padded_mut::<Pkcs7>(&mut buffer.as_mut()[..ctext_end])
155            .map_err(|_| err_msg!(Encryption, "AES-CBC decryption error"))?
156            .len();
157        buffer.buffer_resize(dec_len)?;
158
159        if tag_match.unwrap_u8() != 1 {
160            Err(err_msg!(Encryption, "AEAD decryption error"))
161        } else {
162            Ok(())
163        }
164    }
165
166    fn aead_params(&self) -> KeyAeadParams {
167        KeyAeadParams {
168            nonce_length: NonceSize::<Self>::USIZE,
169            tag_length: TagSize::<Self>::USIZE,
170        }
171    }
172
173    fn aead_padding(&self, msg_len: usize) -> usize {
174        AesCbcHmac::<C, D>::padding_length(msg_len)
175    }
176}
177
178#[cfg(test)]
179mod tests {
180    use base64::Engine;
181    use std::string::ToString;
182
183    use super::*;
184    use crate::buffer::SecretBytes;
185    use crate::repr::KeySecretBytes;
186
187    #[test]
188    fn encrypt_expected_cbc_128_hmac_256() {
189        let key_data = &hex!("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f");
190        let input = b"A cipher system must not be required to be secret, and it must be able to fall into the hands of the enemy without inconvenience";
191        let nonce = &hex!("1af38c2dc2b96ffdd86694092341bc04");
192        let aad = b"The second principle of Auguste Kerckhoffs";
193        let key = AesKey::<A128CbcHs256>::from_secret_bytes(key_data).unwrap();
194        let mut buffer = SecretBytes::from_slice(input);
195        key.encrypt_in_place(&mut buffer, &nonce[..], &aad[..])
196            .unwrap();
197
198        assert_eq!(
199            buffer.as_hex().to_string(),
200            "c80edfa32ddf39d5ef00c0b468834279a2e46a1b8049f792f76bfe54b903a9c9\
201            a94ac9b47ad2655c5f10f9aef71427e2fc6f9b3f399a221489f16362c7032336\
202            09d45ac69864e3321cf82935ac4096c86e133314c54019e8ca7980dfa4b9cf1b\
203            384c486f3a54c51078158ee5d79de59fbd34d848b3d69550a67646344427ade5\
204            4b8851ffb598f7f80074b9473c82e2db652c3fa36b0a7c5b3219fab3a30bc1c4"
205        );
206        key.decrypt_in_place(&mut buffer, &nonce[..], &aad[..])
207            .unwrap();
208        assert_eq!(buffer, &input[..]);
209    }
210
211    #[test]
212    fn encrypt_expected_cbc_256_hmac_512() {
213        let key_data = &hex!(
214            "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f
215            202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f"
216        );
217        let input = b"A cipher system must not be required to be secret, and it must be able to fall into the hands of the enemy without inconvenience";
218        let nonce = &hex!("1af38c2dc2b96ffdd86694092341bc04");
219        let aad = b"The second principle of Auguste Kerckhoffs";
220        let key = AesKey::<A256CbcHs512>::from_secret_bytes(key_data).unwrap();
221        let mut buffer = SecretBytes::from_slice(input);
222        key.encrypt_in_place(&mut buffer, &nonce[..], &aad[..])
223            .unwrap();
224
225        assert_eq!(
226            buffer.as_hex().to_string(),
227            "4affaaadb78c31c5da4b1b590d10ffbd3dd8d5d302423526912da037ecbcc7bd\
228            822c301dd67c373bccb584ad3e9279c2e6d12a1374b77f077553df829410446b\
229            36ebd97066296ae6427ea75c2e0846a11a09ccf5370dc80bfecbad28c73f09b3\
230            a3b75e662a2594410ae496b2e2e6609e31e6e02cc837f053d21f37ff4f51950b\
231            be2638d09dd7a4930930806d0703b1f64dd3b4c088a7f45c216839645b2012bf\
232            2e6269a8c56a816dbc1b267761955bc5"
233        );
234        key.decrypt_in_place(&mut buffer, &nonce[..], &aad[..])
235            .unwrap();
236        assert_eq!(buffer, &input[..]);
237    }
238
239    #[test]
240    fn encrypt_expected_ecdh_1pu_cbc_hmac() {
241        let key_data = &hex!(
242            "fffefdfcfbfaf9f8f7f6f5f4f3f2f1f0efeeedecebeae9e8e7e6e5e4e3e2e1e0
243            dfdedddcdbdad9d8d7d6d5d4d3d2d1d0cfcecdcccbcac9c8c7c6c5c4c3c2c1c0"
244        );
245        let nonce = &hex!("000102030405060708090a0b0c0d0e0f");
246        let protected = "{\"alg\":\"ECDH-1PU+A128KW\",\"enc\":\"A256CBC-HS512\",\
247            \"apu\":\"QWxpY2U\",\"apv\":\"Qm9iIGFuZCBDaGFybGll\",\"epk\":{\
248                \"kty\":\"OKP\",\"crv\":\"X25519\",\
249                \"x\":\"k9of_cpAajy0poW5gaixXGs9nHkwg1AFqUAFa39dyBc\"}}";
250        let aad = base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(protected);
251        let input = b"Three is a magic number.";
252        let key = AesKey::<A256CbcHs512>::from_secret_bytes(key_data).unwrap();
253        let mut buffer = SecretBytes::from_slice(input);
254        let ct_len = key
255            .encrypt_in_place(&mut buffer, &nonce[..], aad.as_bytes())
256            .unwrap();
257        let ctext =
258            base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(&buffer.as_ref()[..ct_len]);
259        let tag =
260            base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(&buffer.as_ref()[ct_len..]);
261        assert_eq!(ctext, "Az2IWsISEMDJvyc5XRL-3-d-RgNBOGolCsxFFoUXFYw");
262        assert_eq!(tag, "HLb4fTlm8spGmij3RyOs2gJ4DpHM4hhVRwdF_hGb3WQ");
263        key.decrypt_in_place(&mut buffer, &nonce[..], aad.as_bytes())
264            .unwrap();
265        assert_eq!(buffer, &input[..]);
266    }
267}