envault_cipher/
aes.rs

1use crate::error::CipherError;
2use crate::pbkdf2::key_and_iv;
3use aes::cipher::{BlockDecryptMut, BlockEncryptMut, KeyIvInit};
4use base64::engine::general_purpose::STANDARD as base64;
5use base64::Engine;
6use cbc::cipher::block_padding::Pkcs7;
7use rand::distributions::Alphanumeric;
8use rand::Rng;
9
10const SALTED_MAGIC: &[u8] = b"Salted__";
11
12pub fn decrypt(ciphertext: &str, key: &str) -> Result<String, CipherError> {
13    base64
14        .decode(ciphertext.as_bytes())
15        .map_err(CipherError::InvalidBase64Encoding)
16        .and_then(|v| decrypt_bytes(&v, key))
17}
18
19pub fn decrypt_bytes(ciphertext: &[u8], key: &str) -> Result<String, CipherError> {
20    if ciphertext.len() < 16 {
21        return Err(CipherError::CipherTextIsTooShort());
22    }
23    let (salted, rest) = ciphertext.split_at(8);
24    if salted != SALTED_MAGIC {
25        return Err(CipherError::InvalidCipherHeader());
26    }
27    let (salt, rest) = rest.split_at(8);
28    let (key, iv) = key_and_iv(key.as_bytes(), salt)?;
29    let cipher = cbc::Decryptor::<aes::Aes256>::new_from_slices(&key, &iv)?;
30    let res = cipher
31        .decrypt_padded_vec_mut::<Pkcs7>(rest)
32        .map_err(CipherError::InvalidKey)?;
33    String::from_utf8(res).map_err(CipherError::InvalidUtf8Encoding)
34}
35
36pub fn encrypt<R: Rng>(plaintext: &str, key: &str, rng: &mut R) -> Result<String, CipherError> {
37    let message = encrypt_bytes(plaintext, key, rng)?;
38    Ok(base64.encode(message))
39}
40
41pub fn encrypt_bytes<R: Rng>(
42    plaintext: &str,
43    key: &str,
44    rng: &mut R,
45) -> Result<Vec<u8>, CipherError> {
46    let salt: String = rng
47        .sample_iter(&Alphanumeric)
48        .take(8)
49        .map(char::from)
50        .collect();
51
52    let (key, iv) = key_and_iv(key.as_bytes(), salt.as_bytes())?;
53    let cipher = cbc::Encryptor::<aes::Aes256>::new_from_slices(&key, &iv)?;
54    let plaintext = plaintext.as_bytes().to_vec();
55    let ciphertext = cipher.encrypt_padded_vec_mut::<Pkcs7>(&plaintext);
56    Ok([SALTED_MAGIC, salt.as_bytes(), &ciphertext].concat())
57}
58
59#[cfg(test)]
60mod test {
61    use super::*;
62    use rand::thread_rng;
63    use rand_chacha::rand_core::SeedableRng;
64
65    #[test]
66    fn test_encrypt_decrypt() {
67        let key = "password";
68        let plaintext = "Hello, world!";
69        let ciphertext = encrypt(plaintext, key, &mut thread_rng()).unwrap();
70        let decrypted = decrypt(&ciphertext, key).unwrap();
71        assert_eq!(plaintext, decrypted);
72    }
73
74    #[test]
75    fn test_encrypt() {
76        let password = "test";
77        let plaintext = "test";
78        let encrypted = encrypt(
79            plaintext,
80            password,
81            &mut rand_chacha::ChaCha8Rng::seed_from_u64(10),
82        )
83        .unwrap();
84        assert_eq!(encrypted, "U2FsdGVkX19Wak5BUmlqMxLr7IxaMTjyOObe/snFRY4=");
85    }
86
87    #[test]
88    fn test_decrypt() {
89        let password = "test";
90        // generate:
91        //   echo -n test | openssl aes-256-cbc -pbkdf2 -base64 -md sha256 -pass pass:test
92        // decrypt:
93        //   echo U2FsdGVkX19GqvuF+PkB8Pm7rCELUHdj/SxpgdBgwSU= | openssl aes-256-cbc -pbkdf2 -d -base64 -md sha256 -pass pass:test
94        let encrypted = "U2FsdGVkX18OtYlc5sMYSNdZ8zUWhACPqYSSwVuPSPA=";
95        let decrypted = decrypt(encrypted, password).unwrap();
96        assert_eq!(decrypted, "test");
97    }
98}