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::{BlockDecryptMut, BlockEncryptMut, KeyIvInit, block_padding::Pkcs7};
9use generic_array::GenericArray;
10use hmac::Mac;
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: &GenericArray<u8, U32>,
30) -> Result<Vec<u8>, DecryptError> {
31    // Decrypt data
32    let iv = GenericArray::from_slice(iv);
33    let mut data = data;
34    let decrypted_key_slice = cbc::Decryptor::<aes::Aes256>::new(key, iv)
35        .decrypt_padded_mut::<Pkcs7>(&mut data)
36        .map_err(|_| DecryptError {})?;
37
38    // Data is decrypted in place and returns a subslice of the original Vec, to avoid cloning it,
39    // we truncate to the subslice length
40    let decrypted_len = decrypted_key_slice.len();
41    data.truncate(decrypted_len);
42
43    Ok(data)
44}
45
46/// Decrypt using AES-256 in CBC mode with MAC.
47///
48/// Behaves similar to [decrypt_aes256], but also validates the MAC.
49pub(crate) fn decrypt_aes256_hmac(
50    iv: &[u8; 16],
51    mac: &[u8; 32],
52    data: Vec<u8>,
53    mac_key: &GenericArray<u8, U32>,
54    key: &GenericArray<u8, U32>,
55) -> Result<Vec<u8>, DecryptError> {
56    let res = generate_mac(mac_key, iv, &data);
57    if res.ct_ne(mac).into() {
58        return Err(DecryptError {});
59    }
60    decrypt_aes256(iv, data, key)
61}
62
63/// Encrypt using AES-256 in CBC mode with MAC.
64///
65/// ## Returns
66///
67/// A Aes256Cbc_HmacSha256_B64 EncString
68pub(crate) fn encrypt_aes256_hmac(
69    data_dec: &[u8],
70    mac_key: &GenericArray<u8, U32>,
71    key: &GenericArray<u8, U32>,
72) -> Result<([u8; 16], [u8; 32], Vec<u8>)> {
73    let rng = rand::thread_rng();
74    let (iv, data) = encrypt_aes256_internal(rng, data_dec, key);
75    let mac = generate_mac(mac_key, &iv, &data);
76
77    Ok((iv, mac, data))
78}
79
80/// Encrypt using AES-256 in CBC mode.
81///
82/// Used internally by:
83/// - [encrypt_aes256_hmac]
84fn encrypt_aes256_internal(
85    mut rng: impl rand::RngCore,
86    data_dec: &[u8],
87    key: &GenericArray<u8, U32>,
88) -> ([u8; 16], Vec<u8>) {
89    let mut iv = [0u8; 16];
90    rng.fill_bytes(&mut iv);
91    let data = cbc::Encryptor::<aes::Aes256>::new(key, &iv.into())
92        .encrypt_padded_vec_mut::<Pkcs7>(data_dec);
93
94    (iv, data)
95}
96
97/// Generate a MAC using HMAC-SHA256.
98fn generate_mac(mac_key: &[u8], iv: &[u8], data: &[u8]) -> [u8; 32] {
99    let mut hmac =
100        PbkdfSha256Hmac::new_from_slice(mac_key).expect("hmac new_from_slice should not fail");
101    hmac.update(iv);
102    hmac.update(data);
103    let mac: [u8; PBKDF_SHA256_HMAC_OUT_SIZE] = (*hmac.finalize().into_bytes())
104        .try_into()
105        // This is safe because Pbkdf2Sha256Hmac output size is always 32 bytes
106        .expect("HMAC output size to be correct");
107    mac
108}
109
110#[cfg(test)]
111mod tests {
112    use bitwarden_encoding::B64;
113    use generic_array::{ArrayLength, sequence::GenericSequence};
114    use rand::SeedableRng;
115
116    use super::*;
117
118    /// Helper function for generating a `GenericArray` of size 32 with each element being
119    /// a multiple of a given increment, starting from a given offset.
120    fn generate_generic_array<N: ArrayLength<u8>>(
121        offset: u8,
122        increment: u8,
123    ) -> GenericArray<u8, N> {
124        GenericArray::generate(|i| offset + i as u8 * increment)
125    }
126
127    /// Helper function for generating a vector of a given size with each element being
128    /// a multiple of a given increment, starting from a given offset.
129    fn generate_vec(length: usize, offset: u8, increment: u8) -> Vec<u8> {
130        (0..length).map(|i| offset + i as u8 * increment).collect()
131    }
132
133    #[test]
134    fn test_encrypt_aes256_internal() {
135        let key = generate_generic_array(0, 1);
136
137        let rng = rand_chacha::ChaCha8Rng::from_seed([0u8; 32]);
138        let result = encrypt_aes256_internal(rng, "EncryptMe!".as_bytes(), &key);
139        assert_eq!(
140            result,
141            (
142                [
143                    62, 0, 239, 47, 137, 95, 64, 214, 127, 91, 184, 232, 31, 9, 165, 161
144                ],
145                vec![
146                    214, 76, 187, 97, 58, 146, 212, 140, 95, 164, 177, 204, 179, 133, 172, 148
147                ]
148            )
149        );
150    }
151
152    #[test]
153    fn test_generate_mac() {
154        let mac_key = generate_vec(16, 0, 16);
155
156        let iv = generate_vec(16, 0, 16);
157        let data = generate_vec(16, 0, 16);
158
159        let mac = generate_mac(&mac_key, &iv, &data);
160        assert!(mac.iter().any(|&b| b != 0));
161    }
162
163    #[test]
164    fn test_decrypt_aes256() {
165        let iv = generate_vec(16, 0, 1);
166        let iv: &[u8; 16] = iv.as_slice().try_into().unwrap();
167        let key = generate_generic_array(0, 1);
168        let data: B64 = ("ByUF8vhyX4ddU9gcooznwA==").parse().unwrap();
169
170        let decrypted = decrypt_aes256(iv, data.into(), &key).unwrap();
171
172        assert_eq!(String::from_utf8(decrypted).unwrap(), "EncryptMe!");
173    }
174}