use aes::cipher::{BlockDecryptMut, KeyInit, KeyIvInit};
use md5::{Digest as Md5Digest, Md5};
use rc4::{KeyInit as Rc4KeyInit, Rc4, StreamCipher};
use sha2::{Digest as Sha2Digest, Sha256, Sha384, Sha512};
use subtle::ConstantTimeEq;
use crate::error::{Error, ParseError, Result};
use crate::object::{Dictionary, Object, ObjectId, Stream, StringFormat};
const PASSWORD_PAD: [u8; 32] = [
0x28, 0xBF, 0x4E, 0x5E, 0x4E, 0x75, 0x8A, 0x41, 0x64, 0x00, 0x4E, 0x56,
0xFF, 0xFA, 0x01, 0x08, 0x2E, 0x2E, 0x00, 0xB6, 0xD0, 0x68, 0x3E, 0x80,
0x2F, 0x0C, 0xA9, 0xFE, 0x64, 0x53, 0x69, 0x7A,
];
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Cipher {
Rc4,
Aes128,
Aes256,
Identity,
}
#[derive(Debug, Clone)]
pub struct Decryptor {
file_key: Vec<u8>,
stm_cipher: Cipher,
str_cipher: Cipher,
v: i64,
r: i64,
}
impl Decryptor {
pub fn from_trailer(
encrypt: &Dictionary,
file_id: &[u8],
password: &[u8],
) -> Result<Self> {
let filter = encrypt
.get_optional(b"Filter")
.and_then(|o| o.as_name().ok())
.map(|n| n.to_vec())
.unwrap_or_default();
if filter != b"Standard" {
return Err(ParseError::Other(format!(
"unsupported /Filter: {}",
String::from_utf8_lossy(&filter)
))
.into());
}
let v = encrypt
.get_optional(b"V")
.and_then(|o| o.as_i64().ok())
.unwrap_or(1);
let r = encrypt
.get_optional(b"R")
.and_then(|o| o.as_i64().ok())
.unwrap_or(2);
let length_bits = encrypt
.get_optional(b"Length")
.and_then(|o| o.as_i64().ok())
.unwrap_or(if v >= 2 { 128 } else { 40 });
let p = encrypt
.get_optional(b"P")
.and_then(|o| o.as_i64().ok())
.unwrap_or(0);
let o_entry = encrypt
.get_optional(b"O")
.and_then(|o| o.as_str().ok())
.ok_or_else(|| Error::from(ParseError::Other("/Encrypt missing /O".into())))?
.to_vec();
let u_entry = encrypt
.get_optional(b"U")
.and_then(|o| o.as_str().ok())
.ok_or_else(|| Error::from(ParseError::Other("/Encrypt missing /U".into())))?
.to_vec();
let (stm_cipher, str_cipher) = pick_ciphers(v, encrypt);
let (file_key, ok) = match r {
2 | 3 | 4 => {
let key_len = ((length_bits as usize) / 8).max(5);
let key = derive_file_key_r234(
password, &o_entry, p, file_id, r, key_len,
);
let ok = if r == 2 {
authenticate_user_r2(&key, &u_entry)
} else {
authenticate_user_r3plus(&key, &u_entry, file_id)
};
(key, ok)
}
5 | 6 => {
let oe = encrypt
.get_optional(b"OE")
.and_then(|o| o.as_str().ok())
.map(|s| s.to_vec())
.unwrap_or_default();
let ue = encrypt
.get_optional(b"UE")
.and_then(|o| o.as_str().ok())
.map(|s| s.to_vec())
.unwrap_or_default();
derive_file_key_r56(password, &o_entry, &oe, &u_entry, &ue, r)
.ok_or_else(|| Error::from(ParseError::Other(
"password did not authenticate (R5/R6)".into(),
)))
.map(|k| (k, true))?
}
other => {
return Err(ParseError::Other(format!(
"unsupported /R: {other}"
))
.into())
}
};
if !ok {
return Err(ParseError::Other("password did not authenticate".into()).into());
}
Ok(Self { file_key, stm_cipher, str_cipher, v, r })
}
pub fn decrypt_object(&self, obj: &mut Object, obj_id: ObjectId) {
match obj {
Object::String(bytes, _fmt) => {
self.decrypt_inplace(bytes, obj_id, self.str_cipher);
}
Object::Stream(stream) => {
for (_, v) in stream.dict.iter_mut() {
self.decrypt_object(v, obj_id);
}
}
Object::Array(items) => {
for item in items.iter_mut() {
self.decrypt_object(item, obj_id);
}
}
Object::Dictionary(d) => {
for (_, v) in d.iter_mut() {
self.decrypt_object(v, obj_id);
}
}
_ => {}
}
}
pub fn decrypt_stream_bytes(&self, bytes: &mut Vec<u8>, obj_id: ObjectId) {
self.decrypt_inplace(bytes, obj_id, self.stm_cipher);
}
fn decrypt_inplace(&self, bytes: &mut Vec<u8>, obj_id: ObjectId, cipher: Cipher) {
if cipher == Cipher::Identity || bytes.is_empty() {
return;
}
if self.v >= 5 {
match cipher {
Cipher::Aes256 => {
if let Some(decoded) = aes256_cbc_decrypt(&self.file_key, bytes) {
*bytes = decoded;
}
}
_ => {}
}
return;
}
let object_key = derive_object_key(&self.file_key, obj_id, cipher);
match cipher {
Cipher::Rc4 => {
rc4_apply_variable(&object_key, bytes);
}
Cipher::Aes128 => {
if let Some(decoded) = aes128_cbc_decrypt(&object_key, bytes) {
*bytes = decoded;
}
}
_ => {}
}
}
}
fn pick_ciphers(v: i64, encrypt: &Dictionary) -> (Cipher, Cipher) {
match v {
1 | 2 => (Cipher::Rc4, Cipher::Rc4),
4 => {
let (stm, strf) = (
read_filter_name(encrypt, b"StmF"),
read_filter_name(encrypt, b"StrF"),
);
let cf = encrypt.get_optional(b"CF").and_then(|o| match o {
Object::Dictionary(d) => Some(d.clone()),
_ => None,
});
let stm_c = cipher_from_cf(cf.as_ref(), &stm);
let str_c = cipher_from_cf(cf.as_ref(), &strf);
(stm_c, str_c)
}
5 => (Cipher::Aes256, Cipher::Aes256),
_ => (Cipher::Rc4, Cipher::Rc4),
}
}
fn cipher_from_cf(cf: Option<&Dictionary>, name: &[u8]) -> Cipher {
if name == b"Identity" || name.is_empty() {
return Cipher::Identity;
}
let Some(cf) = cf else {
return Cipher::Aes128;
};
let entry = cf.get_optional(name).and_then(|o| match o {
Object::Dictionary(d) => Some(d.clone()),
_ => None,
});
let Some(entry) = entry else {
return Cipher::Aes128;
};
match entry.get_optional(b"CFM").and_then(|o| o.as_name().ok()) {
Some(b"AESV2") => Cipher::Aes128,
Some(b"AESV3") => Cipher::Aes256,
Some(b"V2") => Cipher::Rc4,
_ => Cipher::Aes128,
}
}
fn read_filter_name(encrypt: &Dictionary, key: &[u8]) -> Vec<u8> {
encrypt
.get_optional(key)
.and_then(|o| o.as_name().ok())
.map(|n| n.to_vec())
.unwrap_or_else(|| b"Identity".to_vec())
}
fn derive_file_key_r234(
password: &[u8],
o: &[u8],
p: i64,
file_id: &[u8],
r: i64,
key_len: usize,
) -> Vec<u8> {
let mut padded = Vec::with_capacity(32);
padded.extend_from_slice(&password[..password.len().min(32)]);
debug_assert!(padded.len() <= 32);
padded.extend_from_slice(&PASSWORD_PAD[..32 - padded.len()]);
let mut hasher = Md5::new();
hasher.update(&padded);
hasher.update(o);
let p32 = (p as i32).to_le_bytes();
hasher.update(p32);
hasher.update(&file_id[..file_id.len().min(16)]);
let mut key = hasher.finalize_reset().to_vec();
if r >= 3 {
for _ in 0..50 {
hasher.update(&key[..key_len]);
key = hasher.finalize_reset().to_vec();
}
}
key.truncate(key_len);
key
}
fn authenticate_user_r2(file_key: &[u8], u: &[u8]) -> bool {
let mut buf = PASSWORD_PAD.to_vec();
rc4_apply_variable(file_key, &mut buf);
buf == u
}
fn authenticate_user_r3plus(file_key: &[u8], u: &[u8], file_id: &[u8]) -> bool {
let mut hasher = Md5::new();
hasher.update(PASSWORD_PAD);
hasher.update(&file_id[..file_id.len().min(16)]);
let mut buf = hasher.finalize().to_vec();
rc4_apply_variable(file_key, &mut buf);
for i in 1u8..=19 {
let xor_key: Vec<u8> = file_key.iter().map(|b| b ^ i).collect();
rc4_apply_variable(&xor_key, &mut buf);
}
bool::from(buf.ct_eq(&u[..16]))
}
fn derive_file_key_r56(
password: &[u8],
o: &[u8],
oe: &[u8],
u: &[u8],
ue: &[u8],
r: i64,
) -> Option<Vec<u8>> {
let pw = &password[..password.len().min(127)];
if u.len() >= 48 {
let u_hash = &u[..32];
let u_validation_salt = &u[32..40];
let u_key_salt = &u[40..48];
let h_user = if r == 5 {
sha256_concat(&[pw, u_validation_salt])
} else {
r6_hash(pw, u_validation_salt, &[])
};
if bool::from(h_user.ct_eq(u_hash)) {
let intermediate = if r == 5 {
sha256_concat(&[pw, u_key_salt])
} else {
r6_hash(pw, u_key_salt, &[])
};
return aes256_cbc_decrypt_no_padding(&intermediate, ue);
}
}
if o.len() >= 48 && u.len() >= 48 {
let o_hash = &o[..32];
let o_validation_salt = &o[32..40];
let o_key_salt = &o[40..48];
let h_owner = if r == 5 {
sha256_concat(&[pw, o_validation_salt, u])
} else {
r6_hash(pw, o_validation_salt, u)
};
if bool::from(h_owner.ct_eq(o_hash)) {
let intermediate = if r == 5 {
sha256_concat(&[pw, o_key_salt, u])
} else {
r6_hash(pw, o_key_salt, u)
};
return aes256_cbc_decrypt_no_padding(&intermediate, oe);
}
}
None
}
fn sha256_concat(parts: &[&[u8]]) -> Vec<u8> {
let mut h = Sha256::new();
for p in parts {
h.update(p);
}
h.finalize().to_vec()
}
fn r6_hash(password: &[u8], salt: &[u8], u: &[u8]) -> Vec<u8> {
let mut k: Vec<u8> = {
let mut h = Sha256::new();
h.update(password);
h.update(salt);
h.update(u);
h.finalize().to_vec()
};
let mut round_number: u32 = 0;
loop {
round_number += 1;
let block: Vec<u8> = password.iter().chain(k.iter()).chain(u.iter()).copied().collect();
let mut k1 = Vec::with_capacity(block.len() * 64);
for _ in 0..64 {
k1.extend_from_slice(&block);
}
let aes_key = &k[..16];
let aes_iv = &k[16..32];
let e = aes128_cbc_encrypt(aes_key, aes_iv, &k1);
let first16_sum: u32 = e.iter().take(16).map(|&b| b as u32).sum();
let next_hash = match first16_sum % 3 {
0 => Sha256::digest(&e).to_vec(),
1 => Sha384::digest(&e).to_vec(),
_ => Sha512::digest(&e).to_vec(),
};
k = next_hash;
if round_number >= 64 {
let last = *e.last().unwrap_or(&0) as u32;
if last <= round_number - 32 {
break;
}
}
}
k[..32].to_vec()
}
fn derive_object_key(file_key: &[u8], obj_id: ObjectId, cipher: Cipher) -> Vec<u8> {
debug_assert!(file_key.len() >= 5, "file_key must be at least 5 bytes");
let mut h = Md5::new();
h.update(file_key);
let obj_num = obj_id.0;
h.update(&obj_num.to_le_bytes()[..3]);
h.update(&obj_id.1.to_le_bytes()[..2]);
if matches!(cipher, Cipher::Aes128) {
h.update(b"sAlT");
}
let digest = h.finalize();
let want = (file_key.len().max(5) + 5).min(16);
digest[..want].to_vec()
}
fn aes128_cbc_decrypt(key: &[u8], data: &[u8]) -> Option<Vec<u8>> {
if data.len() < 16 || data.len() % 16 != 0 || key.len() < 16 {
return None;
}
type Aes128CbcDec = cbc::Decryptor<aes::Aes128>;
let iv = &data[..16];
let cipher_data = &data[16..];
let mut buf = cipher_data.to_vec();
let mut decryptor = Aes128CbcDec::new(key[..16].into(), iv.into());
let plain = decryptor
.decrypt_padded_mut::<cipher::block_padding::Pkcs7>(&mut buf)
.ok()?;
Some(plain.to_vec())
}
fn aes128_cbc_encrypt(key: &[u8], iv: &[u8], data: &[u8]) -> Vec<u8> {
use aes::cipher::BlockEncryptMut;
type Aes128CbcEnc = cbc::Encryptor<aes::Aes128>;
debug_assert!(data.len() % 16 == 0, "K1 must be a multiple of 16 bytes");
if key.len() < 16 || iv.len() < 16 || data.len() % 16 != 0 {
return Vec::new();
}
let mut enc = Aes128CbcEnc::new(key[..16].into(), iv[..16].into());
let mut buf = data.to_vec();
for chunk in buf.chunks_exact_mut(16) {
let mut block = aes::Block::clone_from_slice(chunk);
enc.encrypt_block_mut(&mut block);
chunk.copy_from_slice(&block);
}
buf
}
fn aes256_cbc_decrypt(key: &[u8], data: &[u8]) -> Option<Vec<u8>> {
if data.len() < 16 || data.len() % 16 != 0 || key.len() < 32 {
return None;
}
type Aes256CbcDec = cbc::Decryptor<aes::Aes256>;
let iv = &data[..16];
let cipher_data = &data[16..];
let mut buf = cipher_data.to_vec();
let mut decryptor = Aes256CbcDec::new(key[..32].into(), iv.into());
let plain = decryptor
.decrypt_padded_mut::<cipher::block_padding::Pkcs7>(&mut buf)
.ok()?;
Some(plain.to_vec())
}
fn aes256_cbc_decrypt_no_padding(key: &[u8], data: &[u8]) -> Option<Vec<u8>> {
if data.len() % 16 != 0 || key.len() < 32 {
return None;
}
type Aes256CbcDec = cbc::Decryptor<aes::Aes256>;
let iv = [0u8; 16];
let mut buf = data.to_vec();
let mut decryptor = Aes256CbcDec::new(key[..32].into(), (&iv).into());
for chunk in buf.chunks_exact_mut(16) {
let mut block = aes::Block::clone_from_slice(chunk);
decryptor.decrypt_block_mut(&mut block);
chunk.copy_from_slice(&block);
}
Some(buf)
}
fn rc4_apply_variable(key: &[u8], data: &mut [u8]) {
if key.is_empty() {
return;
}
let mut s: [u8; 256] = std::array::from_fn(|i| i as u8);
let mut j: u8 = 0;
for i in 0..256 {
j = j.wrapping_add(s[i]).wrapping_add(key[i % key.len()]);
s.swap(i, j as usize);
}
let (mut i, mut j) = (0u8, 0u8);
for byte in data.iter_mut() {
i = i.wrapping_add(1);
j = j.wrapping_add(s[i as usize]);
s.swap(i as usize, j as usize);
let k = s[(s[i as usize].wrapping_add(s[j as usize])) as usize];
*byte ^= k;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn rc4_known_vector() {
let key = b"Key";
let mut buf = b"Plaintext".to_vec();
rc4_apply_variable(key, &mut buf);
let expected = [0xBB, 0xF3, 0x16, 0xE8, 0xD9, 0x40, 0xAF, 0x0A, 0xD3];
assert_eq!(buf, expected, "RC4 known-answer mismatch");
rc4_apply_variable(key, &mut buf);
assert_eq!(&buf, b"Plaintext");
}
#[test]
fn password_pad_is_32_bytes() {
assert_eq!(PASSWORD_PAD.len(), 32);
}
}