Skip to main content

bitwarden_crypto/
aes.rs

1//! # AES operations
2//!
3//! Contains low level AES operations used by the rest of the library.
4//!
5//! In most cases you should use the [EncString][crate::EncString] with
6//! [KeyEncryptable][crate::KeyEncryptable] & [KeyDecryptable][crate::KeyDecryptable] instead.
7
8use aes::cipher::{BlockModeDecrypt, BlockModeEncrypt, KeyIvInit, block_padding::Pkcs7};
9use hmac::{KeyInit, Mac};
10use hybrid_array::Array;
11use subtle::ConstantTimeEq;
12use typenum::U32;
13
14use crate::{
15    error::Result,
16    util::{PBKDF_SHA256_HMAC_OUT_SIZE, PbkdfSha256Hmac},
17};
18
19/// An aes operation failed either due to invalid padding or due to an invalid MAC.
20#[derive(Debug)]
21pub(crate) struct DecryptError {}
22
23/// Decrypt using AES-256 in CBC mode.
24///
25/// Behaves similar to [decrypt_aes256_hmac], but does not validate the MAC.
26pub(crate) fn decrypt_aes256(
27    iv: &[u8; 16],
28    data: Vec<u8>,
29    key: &Array<u8, U32>,
30) -> Result<Vec<u8>, DecryptError> {
31    // Decrypt data
32    let mut data = data;
33    let decrypted_key_slice = cbc::Decryptor::<aes::Aes256>::new(key, iv.into())
34        .decrypt_padded::<Pkcs7>(&mut data)
35        .map_err(|_| DecryptError {})?;
36
37    // Data is decrypted in place and returns a subslice of the original Vec, to avoid cloning it,
38    // we truncate to the subslice length
39    let decrypted_len = decrypted_key_slice.len();
40    data.truncate(decrypted_len);
41
42    Ok(data)
43}
44
45/// Decrypt using AES-256 in CBC mode with MAC.
46///
47/// Behaves similar to [decrypt_aes256], but also validates the MAC.
48pub(crate) fn decrypt_aes256_hmac(
49    iv: &[u8; 16],
50    mac: &[u8; 32],
51    data: Vec<u8>,
52    mac_key: &Array<u8, U32>,
53    key: &Array<u8, U32>,
54) -> Result<Vec<u8>, DecryptError> {
55    let res = generate_mac(mac_key, iv, &data);
56    if res.ct_ne(mac).into() {
57        return Err(DecryptError {});
58    }
59    decrypt_aes256(iv, data, key)
60}
61
62/// Encrypt using AES-256 in CBC mode with MAC.
63///
64/// ## Returns
65///
66/// A Aes256Cbc_HmacSha256_B64 EncString
67pub(crate) fn encrypt_aes256_hmac(
68    data_dec: &[u8],
69    mac_key: &Array<u8, U32>,
70    key: &Array<u8, U32>,
71) -> Result<([u8; 16], [u8; 32], Vec<u8>)> {
72    let rng = rand::rng();
73    let (iv, data) = encrypt_aes256_internal(rng, data_dec, key);
74    let mac = generate_mac(mac_key, &iv, &data);
75
76    Ok((iv, mac, data))
77}
78
79/// Encrypt using AES-256 in CBC mode.
80///
81/// Used internally by:
82/// - [encrypt_aes256_hmac]
83fn encrypt_aes256_internal(
84    mut rng: impl rand::Rng,
85    data_dec: &[u8],
86    key: &Array<u8, U32>,
87) -> ([u8; 16], Vec<u8>) {
88    let mut iv = [0u8; 16];
89    rng.fill_bytes(&mut iv);
90    let data =
91        cbc::Encryptor::<aes::Aes256>::new(key, &iv.into()).encrypt_padded_vec::<Pkcs7>(data_dec);
92
93    (iv, data)
94}
95
96/// Generate a MAC using HMAC-SHA256.
97fn generate_mac(mac_key: &[u8], iv: &[u8], data: &[u8]) -> [u8; 32] {
98    let mut hmac =
99        PbkdfSha256Hmac::new_from_slice(mac_key).expect("hmac new_from_slice should not fail");
100    hmac.update(iv);
101    hmac.update(data);
102    let mac: [u8; PBKDF_SHA256_HMAC_OUT_SIZE] = (*hmac.finalize().into_bytes())
103        .try_into()
104        // This is safe because Pbkdf2Sha256Hmac output size is always 32 bytes
105        .expect("HMAC output size to be correct");
106    mac
107}
108
109#[cfg(test)]
110mod tests {
111    use bitwarden_encoding::B64;
112    use hybrid_array::ArraySize;
113    use rand::SeedableRng;
114
115    use super::*;
116
117    /// Helper function for generating an `Array` of size N with each element being
118    /// a multiple of a given increment, starting from a given offset.
119    fn generate_array<N: ArraySize>(offset: u8, increment: u8) -> Array<u8, N> {
120        Array::from_fn(|i| offset + i as u8 * increment)
121    }
122
123    /// Helper function for generating a vector of a given size with each element being
124    /// a multiple of a given increment, starting from a given offset.
125    fn generate_vec(length: usize, offset: u8, increment: u8) -> Vec<u8> {
126        (0..length).map(|i| offset + i as u8 * increment).collect()
127    }
128
129    #[test]
130    fn test_encrypt_aes256_internal() {
131        let key = generate_array(0, 1);
132
133        let rng = rand_chacha::ChaCha8Rng::from_seed([0u8; 32]);
134        let result = encrypt_aes256_internal(rng, "EncryptMe!".as_bytes(), &key);
135        assert_eq!(
136            result,
137            (
138                [
139                    62, 0, 239, 47, 137, 95, 64, 214, 127, 91, 184, 232, 31, 9, 165, 161
140                ],
141                vec![
142                    214, 76, 187, 97, 58, 146, 212, 140, 95, 164, 177, 204, 179, 133, 172, 148
143                ]
144            )
145        );
146    }
147
148    #[test]
149    fn test_generate_mac() {
150        let mac_key = generate_vec(16, 0, 16);
151
152        let iv = generate_vec(16, 0, 16);
153        let data = generate_vec(16, 0, 16);
154
155        let mac = generate_mac(&mac_key, &iv, &data);
156        assert!(mac.iter().any(|&b| b != 0));
157    }
158
159    #[test]
160    fn test_decrypt_aes256() {
161        let iv = generate_vec(16, 0, 1);
162        let iv: &[u8; 16] = iv.as_slice().try_into().unwrap();
163        let key = generate_array(0, 1);
164        let data: B64 = ("ByUF8vhyX4ddU9gcooznwA==").parse().unwrap();
165
166        let decrypted = decrypt_aes256(iv, data.into(), &key).unwrap();
167
168        assert_eq!(String::from_utf8(decrypted).unwrap(), "EncryptMe!");
169    }
170}