use aes::Aes256;
use cbc::{Encryptor, Decryptor};
use cbc::cipher::{BlockModeEncrypt, BlockModeDecrypt, KeyIvInit};
use block_padding::Pkcs7;
use super::key::{prepare_key, KEY_LENGTH};
use super::md5::md5_hex;
const IV_SIZE: usize = 16;
const ZERO_IV: [u8; IV_SIZE] = [0u8; IV_SIZE];
const MD5_HEX_LENGTH: usize = 32;
type Aes256CbcEnc = Encryptor<Aes256>;
type Aes256CbcDec = Decryptor<Aes256>;
pub fn encrypt(
plaintext: &str,
password: &str,
re_encryption_count: u32,
hash: Option<&str>,
) -> Result<Vec<u8>, String> {
let key = prepare_key(password, hash, re_encryption_count);
let md5_checksum = md5_hex(plaintext);
let full_text = format!("{}{}", md5_checksum, plaintext);
let data = full_text.as_bytes();
let block_size = 16;
let padded_len = ((data.len() / block_size) + 1) * block_size;
let mut buffer = vec![0u8; padded_len];
buffer[..data.len()].copy_from_slice(data);
let encryptor = Aes256CbcEnc::new(&key.into(), &ZERO_IV.into());
let encrypted = encryptor
.encrypt_padded::<Pkcs7>(&mut buffer, data.len())
.map_err(|e| format!("Encryption failed: {:?}", e))?;
Ok(encrypted.to_vec())
}
pub fn decrypt(
ciphertext: &[u8],
password: &str,
re_encryption_count: u32,
hash: Option<&str>,
) -> Result<String, String> {
let key = prepare_key(password, hash, re_encryption_count);
match decrypt_with_key(ciphertext, &key) {
Ok(plaintext) => Ok(plaintext),
Err(_) => {
let mut ios_key = key;
ios_key[0] = 0;
decrypt_with_key(ciphertext, &ios_key)
}
}
}
fn decrypt_with_key(ciphertext: &[u8], key: &[u8; KEY_LENGTH]) -> Result<String, String> {
if ciphertext.is_empty() {
return Err("Empty ciphertext".to_string());
}
let mut buffer = ciphertext.to_vec();
let decryptor = Aes256CbcDec::new(key.into(), &ZERO_IV.into());
let decrypted = decryptor
.decrypt_padded::<Pkcs7>(&mut buffer)
.map_err(|e| format!("Decryption failed: {:?}", e))?;
let full_text = String::from_utf8(decrypted.to_vec())
.map_err(|e| format!("Invalid UTF-8: {}", e))?;
if full_text.len() < MD5_HEX_LENGTH {
return Err("Decrypted text too short".to_string());
}
let (checksum, plaintext) = full_text.split_at(MD5_HEX_LENGTH);
let computed_checksum = md5_hex(plaintext);
if checksum != computed_checksum {
return Err("MD5 checksum mismatch".to_string());
}
Ok(plaintext.to_string())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_encrypt_vector() {
let password = "Sun001!";
let plaintext = "Test Item";
let expected: [u8; 48] = [
0x03, 0xde, 0xd5, 0x8a, 0x00, 0xcf, 0x22, 0x15,
0x76, 0x6b, 0x57, 0x5d, 0xbe, 0xdb, 0xf2, 0xd2,
0x0f, 0x84, 0xec, 0x9b, 0x68, 0x41, 0x59, 0xb3,
0x05, 0x6f, 0x75, 0x45, 0xe7, 0x1b, 0xe4, 0x9d,
0x1d, 0xef, 0xa5, 0xb2, 0x9d, 0xcd, 0x4a, 0x06,
0xa1, 0x18, 0xa8, 0xa6, 0x91, 0x29, 0x13, 0x00,
];
let result = encrypt(plaintext, password, 0, None).unwrap();
assert_eq!(result, expected.to_vec());
}
#[test]
fn test_decrypt_vector() {
let password = "Sun001!";
let encrypted: [u8; 112] = [
0x53, 0xda, 0x92, 0xa5, 0xf5, 0x48, 0x90, 0xc5,
0xd5, 0xb4, 0x13, 0xbb, 0xad, 0x51, 0xc5, 0xf6,
0xfb, 0xf2, 0xa3, 0x0a, 0x27, 0x98, 0x7c, 0xed,
0xad, 0x9e, 0xea, 0xed, 0x08, 0xa7, 0xd8, 0xa1,
0x42, 0x0c, 0xe1, 0xe4, 0xf4, 0xf5, 0x16, 0x03,
0x96, 0x55, 0x80, 0xc6, 0x88, 0x39, 0x16, 0x85,
0x2e, 0xce, 0x48, 0xf9, 0x8d, 0x5e, 0x6e, 0xb4,
0x53, 0x36, 0x51, 0x2e, 0x86, 0x1f, 0xff, 0xb6,
0x89, 0xf2, 0xbb, 0x5b, 0x49, 0x2c, 0x7d, 0x92,
0xae, 0x23, 0xac, 0xbb, 0x3f, 0xd2, 0x91, 0x21,
0x98, 0x20, 0x18, 0x45, 0xad, 0xa0, 0x9a, 0x14,
0x18, 0x99, 0xec, 0x2d, 0xed, 0x29, 0xb9, 0xd0,
0x2f, 0xed, 0xe9, 0xe4, 0xd2, 0xf3, 0x3d, 0x87,
0x89, 0xf5, 0xd3, 0xda, 0xb7, 0x6d, 0xda, 0x55,
];
let expected = "nhh86c4uQKXu8rTjp4sL8rr2fxRMmnhWWhan8LiaVb4ZhTdF4RTlX4xcHYjwsfDu";
let result = decrypt(&encrypted, password, 0, None).unwrap();
assert_eq!(result, expected);
}
#[test]
fn test_encrypt_decrypt_roundtrip() {
let password = "TestPassword123!";
let plaintext = "Hello, World! This is a test message.";
let encrypted = encrypt(plaintext, password, 0, None).unwrap();
let decrypted = decrypt(&encrypted, password, 0, None).unwrap();
assert_eq!(decrypted, plaintext);
}
#[test]
fn test_encrypt_decrypt_utf8() {
let password = "TestPassword";
let plaintext = "Привет мир! 你好世界! مرحبا بالعالم";
let encrypted = encrypt(plaintext, password, 0, None).unwrap();
let decrypted = decrypt(&encrypted, password, 0, None).unwrap();
assert_eq!(decrypted, plaintext);
}
#[test]
fn test_encrypt_decrypt_empty() {
let password = "TestPassword";
let plaintext = "";
let encrypted = encrypt(plaintext, password, 0, None).unwrap();
let decrypted = decrypt(&encrypted, password, 0, None).unwrap();
assert_eq!(decrypted, plaintext);
}
#[test]
fn test_wrong_password_fails() {
let plaintext = "Secret message";
let encrypted = encrypt(plaintext, "correct_password", 0, None).unwrap();
let result = decrypt(&encrypted, "wrong_password", 0, None);
assert!(result.is_err());
}
#[test]
fn test_with_reencryption_count() {
let password = "TestPassword";
let plaintext = "Test with reencryption";
let encrypted = encrypt(plaintext, password, 2, None).unwrap();
let decrypted = decrypt(&encrypted, password, 2, None).unwrap();
assert_eq!(decrypted, plaintext);
let result = decrypt(&encrypted, password, 0, None);
assert!(result.is_err());
}
}