use crate::error::{LockzippyError, LockzippyResult};
use aes::Aes256;
use cbc::Decryptor;
use cipher::{block_padding::NoPadding, BlockDecryptMut, KeyIvInit};
use sha2::{Digest, Sha256};
#[derive(Debug, Clone)]
pub struct AesProperties {
pub num_cycles_power: u8,
pub salt: Vec<u8>,
pub iv: [u8; 16],
}
impl AesProperties {
pub fn parse(props: &[u8]) -> LockzippyResult<Self> {
if props.len() < 2 {
return Err(LockzippyError::InvalidProperties(props.len()));
}
let b0 = props[0];
let b1 = props[1];
let num_cycles_power = b0 & 0x3F;
let has_iv = (b0 >> 6) & 1 != 0;
let has_salt = (b0 >> 7) & 1 != 0;
let salt_size: usize = if has_salt { (((b1 >> 4) & 0x0F) + 1) as usize } else { 0 };
let iv_size: usize = if has_iv { ((b1 & 0x0F) + 1) as usize } else { 0 };
let required = 2 + salt_size + iv_size;
if props.len() < required {
return Err(LockzippyError::InvalidProperties(props.len()));
}
let salt = props[2..2 + salt_size].to_vec();
let iv_bytes = &props[2 + salt_size..2 + salt_size + iv_size];
let mut iv = [0u8; 16];
let copy_len = iv_bytes.len().min(16);
iv[..copy_len].copy_from_slice(&iv_bytes[..copy_len]);
Ok(AesProperties {
num_cycles_power,
salt,
iv,
})
}
}
pub fn derive_key(password: &str, salt: &[u8], num_cycles_power: u8) -> [u8; 32] {
let pw_utf16le: Vec<u8> = password
.encode_utf16()
.flat_map(|c| c.to_le_bytes())
.collect();
let num_rounds: u64 = 1u64 << num_cycles_power;
let mut hasher = Sha256::new();
for round in 0u64..num_rounds {
hasher.update(salt);
hasher.update(&pw_utf16le);
hasher.update(round.to_le_bytes());
}
hasher.finalize().into()
}
pub fn decrypt_aes256_cbc(
ciphertext: &[u8],
key: &[u8; 32],
iv: &[u8; 16],
) -> LockzippyResult<Vec<u8>> {
if ciphertext.len() % 16 != 0 {
return Err(LockzippyError::InvalidCiphertextLength(ciphertext.len()));
}
let mut buf = ciphertext.to_vec();
Decryptor::<Aes256>::new(key.into(), iv.into())
.decrypt_padded_mut::<NoPadding>(&mut buf)
.map_err(|e| LockzippyError::decrypt(e))?;
Ok(buf)
}
pub fn decrypt_7z(ciphertext: &[u8], props: &[u8], password: &str) -> LockzippyResult<Vec<u8>> {
let aes_props = AesProperties::parse(props)?;
let key = derive_key(password, &aes_props.salt, aes_props.num_cycles_power);
decrypt_aes256_cbc(ciphertext, &key, &aes_props.iv)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn aes_cbc_round_trip() {
use aes::Aes256;
use cbc::Encryptor;
use cipher::{block_padding::NoPadding, BlockEncryptMut, KeyIvInit};
let key = [0x42u8; 32];
let iv = [0x13u8; 16];
let plaintext = [0xABu8; 32];
let mut enc_buf = plaintext;
Encryptor::<Aes256>::new(&key.into(), &iv.into())
.encrypt_padded_mut::<NoPadding>(&mut enc_buf, 32)
.unwrap();
let decrypted = decrypt_aes256_cbc(&enc_buf, &key, &iv).unwrap();
assert_eq!(decrypted, plaintext);
}
#[test]
fn key_derivation_differs_by_password() {
let k1 = derive_key("password1", &[], 6);
let k2 = derive_key("password2", &[], 6);
assert_ne!(k1, k2);
}
#[test]
fn key_derivation_is_deterministic() {
let k1 = derive_key("test1234", &[], 19);
let k2 = derive_key("test1234", &[], 19);
assert_eq!(k1, k2);
}
#[test]
fn parse_aes_props_no_salt() {
let iv_bytes = [0xAAu8; 16];
let mut props = vec![0x53u8, 0x0Fu8];
props.extend_from_slice(&iv_bytes);
let p = AesProperties::parse(&props).unwrap();
assert_eq!(p.num_cycles_power, 19);
assert_eq!(p.salt, Vec::<u8>::new());
assert_eq!(p.iv, iv_bytes);
}
#[test]
fn parse_too_short_props() {
let result = AesProperties::parse(&[42u8]);
assert!(matches!(result, Err(LockzippyError::InvalidProperties(1))));
}
}