Skip to main content

bitwarden_crypto/hazmat/symmetric_encryption/
aes256_cbc_hmac_sha256_ae.rs

1//! # Legacy AES-256-CBC-HMAC-SHA256 operations
2//!
3//! Aes256CbcHmacSha256 is the construct used in type 2 EncStrings. It is authenticated encryption
4//! (AE) via Encrypt-then-MAC, but has no support for associated data (it is not AEAD).
5
6use aes::cipher::{BlockModeDecrypt, BlockModeEncrypt, KeyIvInit, block_padding::Pkcs7};
7use hmac::{KeyInit, Mac};
8use subtle::ConstantTimeEq;
9
10use super::SymmetricEncryptionError;
11
12type HmacSha256 = hmac::Hmac<sha2::Sha256>;
13
14pub(crate) const IV_SIZE: usize = 16;
15pub(crate) const ENC_KEY_SIZE: usize = 32;
16pub(crate) const MAC_KEY_SIZE: usize = 32;
17pub(crate) const KEY_SIZE: usize = ENC_KEY_SIZE + MAC_KEY_SIZE;
18pub(crate) const MAC_SIZE: usize = 32;
19
20/// The legacy AES-256-CBC-HMAC-SHA256 Encrypt-then-MAC cipher. The 64-byte composite key is the
21/// AES-256-CBC encryption sub-key (first 32 bytes) followed by the HMAC-SHA256 authentication
22/// sub-key (last 32 bytes).
23pub(crate) struct Aes256CbcHmacSha256;
24
25impl Aes256CbcHmacSha256 {
26    /// Encrypt using AES-256 in CBC mode, returning the MAC over the IV and ciphertext along
27    /// with the ciphertext.
28    ///
29    /// A fresh, cryptographically random IV must be supplied for every message encrypted under a
30    /// given key; generating it is the caller's responsibility.
31    pub(crate) fn encrypt(
32        iv: &[u8; IV_SIZE],
33        plaintext: &[u8],
34        key: &[u8; KEY_SIZE],
35    ) -> ([u8; MAC_SIZE], Vec<u8>) {
36        let (enc_key, mac_key) = split_to_subkeys(key);
37
38        let data = cbc::Encryptor::<aes::Aes256>::new(enc_key.into(), iv.into())
39            .encrypt_padded_vec::<Pkcs7>(plaintext);
40        let mac = calculate_mac(iv, &data, mac_key);
41
42        (mac, data)
43    }
44
45    /// Decrypt using AES-256 in CBC mode, validating the MAC over the IV and ciphertext.
46    pub(crate) fn decrypt(
47        iv: &[u8; IV_SIZE],
48        ciphertext: &[u8],
49        mac: &[u8; MAC_SIZE],
50        key: &[u8; KEY_SIZE],
51    ) -> Result<Vec<u8>, SymmetricEncryptionError> {
52        let (enc_key, mac_key) = split_to_subkeys(key);
53
54        let expected_mac = calculate_mac(iv, ciphertext, mac_key);
55        if expected_mac.ct_ne(mac).into() {
56            return Err(SymmetricEncryptionError::IntegrityCheckFailed);
57        }
58
59        // Decrypt data in place in a copy of the ciphertext
60        let mut data = ciphertext.to_vec();
61        let decrypted_slice = cbc::Decryptor::<aes::Aes256>::new(enc_key.into(), iv.into())
62            .decrypt_padded::<Pkcs7>(&mut data)
63            .map_err(|_| SymmetricEncryptionError::FormatWrong)?;
64
65        // Decryption returns a subslice of the buffer; truncate to the subslice length instead of
66        // cloning it
67        let decrypted_len = decrypted_slice.len();
68        data.truncate(decrypted_len);
69
70        Ok(data)
71    }
72}
73
74/// Splits the 64-byte composite key into zero-copy views over its encryption and MAC halves.
75fn split_to_subkeys(key: &[u8; KEY_SIZE]) -> (&[u8; ENC_KEY_SIZE], &[u8; MAC_KEY_SIZE]) {
76    let (enc_key, mac_key) = key.split_at(ENC_KEY_SIZE);
77    let enc_key = enc_key
78        .try_into()
79        .expect("first half of a 64-byte key is always 32 bytes");
80    let mac_key = mac_key
81        .try_into()
82        .expect("second half of a 64-byte key is always 32 bytes");
83    (enc_key, mac_key)
84}
85
86/// Generate a MAC using HMAC-SHA256 over the IV and ciphertext, without length prefixes or
87/// associated data.
88fn calculate_mac(iv: &[u8], data: &[u8], mac_key: &[u8; MAC_KEY_SIZE]) -> [u8; MAC_SIZE] {
89    let mut hmac =
90        HmacSha256::new_from_slice(mac_key).expect("hmac new_from_slice should not fail");
91    hmac.update(iv);
92    hmac.update(data);
93    let mac: [u8; MAC_SIZE] = (*hmac.finalize().into_bytes())
94        .try_into()
95        // This is safe because HMAC-SHA256 output size is always 32 bytes
96        .expect("HMAC output size to be correct");
97    mac
98}
99
100#[cfg(test)]
101mod tests {
102    use super::*;
103
104    const TESTVECTOR_PLAINTEXT: &[u8] = b"Bitwarden SDK test vector";
105    const TESTVECTOR_IV: [u8; IV_SIZE] = [
106        216, 218, 36, 0, 196, 186, 150, 85, 49, 147, 110, 168, 185, 227, 42, 172,
107    ];
108    const TESTVECTOR_MAC: [u8; MAC_SIZE] = [
109        60, 78, 44, 111, 72, 233, 3, 6, 86, 250, 217, 242, 62, 229, 184, 221, 231, 150, 189, 44,
110        99, 189, 220, 55, 196, 194, 101, 60, 102, 195, 149, 130,
111    ];
112    const TESTVECTOR_DATA: &[u8] = &[
113        234, 77, 16, 15, 189, 82, 36, 188, 182, 88, 64, 67, 145, 94, 30, 178, 36, 235, 130, 67,
114        255, 207, 183, 168, 73, 231, 82, 122, 193, 139, 25, 129,
115    ];
116
117    const TESTVECTOR_KEY: [u8; KEY_SIZE] = [
118        0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
119        25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
120        48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63,
121    ];
122
123    #[test]
124    #[ignore = "Generates test vectors; run manually"]
125    fn generate_test_vectors() {
126        let (mac, data) =
127            Aes256CbcHmacSha256::encrypt(&TESTVECTOR_IV, TESTVECTOR_PLAINTEXT, &TESTVECTOR_KEY);
128        let decrypted =
129            Aes256CbcHmacSha256::decrypt(&TESTVECTOR_IV, &data, &mac, &TESTVECTOR_KEY).unwrap();
130        assert_eq!(decrypted, TESTVECTOR_PLAINTEXT);
131
132        println!("const TESTVECTOR_MAC: [u8; MAC_SIZE] = {mac:?};");
133        println!("const TESTVECTOR_DATA: &[u8] = &{data:?};");
134    }
135
136    /// Locks in the serialized format of the type 2 (iv, mac, ciphertext) triple; must never
137    /// break, or existing data will no longer decrypt.
138    #[test]
139    fn test_decrypt_testvector() {
140        let decrypted = Aes256CbcHmacSha256::decrypt(
141            &TESTVECTOR_IV,
142            TESTVECTOR_DATA,
143            &TESTVECTOR_MAC,
144            &TESTVECTOR_KEY,
145        )
146        .unwrap();
147
148        assert_eq!(decrypted, TESTVECTOR_PLAINTEXT);
149    }
150
151    #[test]
152    fn test_encrypt_is_deterministic() {
153        const IV: [u8; IV_SIZE] = [
154            62, 0, 239, 47, 137, 95, 64, 214, 127, 91, 184, 232, 31, 9, 165, 161,
155        ];
156
157        let (_mac, data) =
158            Aes256CbcHmacSha256::encrypt(&IV, "EncryptMe!".as_bytes(), &TESTVECTOR_KEY);
159        assert_eq!(
160            data,
161            vec![
162                214, 76, 187, 97, 58, 146, 212, 140, 95, 164, 177, 204, 179, 133, 172, 148
163            ]
164        );
165    }
166
167    #[test]
168    fn test_calculate_mac() {
169        const MAC_KEY: [u8; MAC_KEY_SIZE] = [
170            0, 8, 16, 24, 32, 40, 48, 56, 64, 72, 80, 88, 96, 104, 112, 120, 128, 136, 144, 152,
171            160, 168, 176, 184, 192, 200, 208, 216, 224, 232, 240, 248,
172        ];
173        const IV: [u8; IV_SIZE] = [
174            0, 16, 32, 48, 64, 80, 96, 112, 128, 144, 160, 176, 192, 208, 224, 240,
175        ];
176        const DATA: [u8; 16] = [
177            0, 16, 32, 48, 64, 80, 96, 112, 128, 144, 160, 176, 192, 208, 224, 240,
178        ];
179
180        let mac = calculate_mac(&IV, &DATA, &MAC_KEY);
181        assert!(mac.iter().any(|&b| b != 0));
182    }
183
184    #[test]
185    fn test_encrypt_decrypt_roundtrip() {
186        let plaintext = b"EncryptMe!";
187
188        let (mac, data) = Aes256CbcHmacSha256::encrypt(&TESTVECTOR_IV, plaintext, &TESTVECTOR_KEY);
189        let decrypted =
190            Aes256CbcHmacSha256::decrypt(&TESTVECTOR_IV, &data, &mac, &TESTVECTOR_KEY).unwrap();
191
192        assert_eq!(decrypted, plaintext);
193    }
194
195    #[test]
196    fn test_decrypt_fails_when_mac_changed() {
197        let plaintext = b"EncryptMe!";
198
199        let (mut mac, data) =
200            Aes256CbcHmacSha256::encrypt(&TESTVECTOR_IV, plaintext, &TESTVECTOR_KEY);
201        mac[0] = mac[0].wrapping_add(1);
202        let result = Aes256CbcHmacSha256::decrypt(&TESTVECTOR_IV, &data, &mac, &TESTVECTOR_KEY);
203
204        assert_eq!(result, Err(SymmetricEncryptionError::IntegrityCheckFailed));
205    }
206}