Skip to main content

bitwarden_crypto/hazmat/symmetric_encryption/
aes256_cbc.rs

1//! # Legacy AES-256-CBC operations (unauthenticated)
2//!
3//! Contains the legacy AES-256-CBC primitive used by the type 0 `EncString` format. The data is
4//! **not authenticated**, so this must only be used to decrypt existing data, never to encrypt
5//! new data.
6//!
7//! In most cases you should use the [EncString][crate::EncString] with
8//! [KeyDecryptable][crate::KeyDecryptable] instead.
9
10use aes::cipher::{BlockModeDecrypt, KeyIvInit, block_padding::Pkcs7};
11
12use super::SymmetricEncryptionError;
13
14pub(crate) const IV_SIZE: usize = 16;
15pub(crate) const KEY_SIZE: usize = 32;
16
17/// The unauthenticated legacy AES-256-CBC cipher.
18pub(crate) struct Aes256Cbc;
19
20impl Aes256Cbc {
21    /// Decrypt using AES-256 in CBC mode.
22    pub(crate) fn decrypt(
23        iv: &[u8; IV_SIZE],
24        ciphertext: &[u8],
25        key: &[u8; KEY_SIZE],
26    ) -> Result<Vec<u8>, SymmetricEncryptionError> {
27        // Decrypt data in place in a copy of the ciphertext
28        let mut data = ciphertext.to_vec();
29        let decrypted_key_slice = cbc::Decryptor::<aes::Aes256>::new(key.into(), iv.into())
30            .decrypt_padded::<Pkcs7>(&mut data)
31            .map_err(|_| SymmetricEncryptionError::FormatWrong)?;
32
33        // Decryption returns a subslice of the buffer; truncate to the subslice length instead of
34        // cloning it
35        let decrypted_len = decrypted_key_slice.len();
36        data.truncate(decrypted_len);
37
38        Ok(data)
39    }
40
41    /// Encrypt using AES-256 in CBC mode.
42    #[cfg(test)]
43    fn encrypt(iv: &[u8; IV_SIZE], plaintext: &[u8], key: &[u8; KEY_SIZE]) -> Vec<u8> {
44        use aes::cipher::BlockModeEncrypt;
45
46        cbc::Encryptor::<aes::Aes256>::new(key.into(), iv.into())
47            .encrypt_padded_vec::<Pkcs7>(plaintext)
48    }
49}
50
51#[cfg(test)]
52mod tests {
53    use bitwarden_encoding::B64;
54
55    use super::*;
56
57    const TESTVECTOR_PLAINTEXT: &[u8] = b"Bitwarden SDK test vector";
58    const TESTVECTOR_CIPHERTEXT: &[u8] = &[
59        111, 16, 33, 143, 207, 253, 106, 89, 58, 52, 145, 240, 140, 213, 149, 83, 168, 188, 122,
60        57, 130, 28, 35, 182, 114, 227, 215, 250, 175, 182, 169, 102,
61    ];
62    const TESTVECTOR_KEY: [u8; KEY_SIZE] = [
63        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,
64        25, 26, 27, 28, 29, 30, 31,
65    ];
66    const TESTVECTOR_IV: [u8; IV_SIZE] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
67
68    #[test]
69    #[ignore = "Generates test vectors; run manually"]
70    fn generate_test_vectors() {
71        let ciphertext = Aes256Cbc::encrypt(&TESTVECTOR_IV, TESTVECTOR_PLAINTEXT, &TESTVECTOR_KEY);
72        let decrypted = Aes256Cbc::decrypt(&TESTVECTOR_IV, &ciphertext, &TESTVECTOR_KEY).unwrap();
73        assert_eq!(decrypted, TESTVECTOR_PLAINTEXT);
74
75        println!("const TESTVECTOR_CIPHERTEXT: &[u8] = &{ciphertext:?};");
76    }
77
78    /// Locks in the serialized format of the type 0 ciphertext; must never break, or existing
79    /// data will no longer decrypt.
80    #[test]
81    fn test_decrypt_testvector() {
82        let decrypted =
83            Aes256Cbc::decrypt(&TESTVECTOR_IV, TESTVECTOR_CIPHERTEXT, &TESTVECTOR_KEY).unwrap();
84
85        assert_eq!(decrypted, TESTVECTOR_PLAINTEXT);
86    }
87
88    #[test]
89    fn test_encrypt_decrypt_roundtrip() {
90        let plaintext = b"EncryptMe!";
91
92        let encrypted = Aes256Cbc::encrypt(&TESTVECTOR_IV, plaintext, &TESTVECTOR_KEY);
93        let decrypted = Aes256Cbc::decrypt(&TESTVECTOR_IV, &encrypted, &TESTVECTOR_KEY).unwrap();
94
95        assert_eq!(decrypted, plaintext);
96    }
97
98    #[test]
99    fn test_decrypt_aes256() {
100        let data: B64 = ("ByUF8vhyX4ddU9gcooznwA==").parse().unwrap();
101
102        let decrypted =
103            Aes256Cbc::decrypt(&TESTVECTOR_IV, data.as_bytes(), &TESTVECTOR_KEY).unwrap();
104
105        assert_eq!(String::from_utf8(decrypted).unwrap(), "EncryptMe!");
106    }
107
108    #[test]
109    fn test_decrypt_aes256_fails_on_invalid_padding() {
110        let iv = [0u8; IV_SIZE];
111
112        // Random block that will not decrypt to valid PKCS7 padding under this key/IV.
113        let result = Aes256Cbc::decrypt(&iv, &[0u8; 16], &TESTVECTOR_KEY);
114        assert_eq!(result, Err(SymmetricEncryptionError::FormatWrong));
115    }
116}