Skip to content

Commit

Permalink
Support AES encryption and revision 4 (#343)
Browse files Browse the repository at this point in the history
  • Loading branch information
Unpublished authored Nov 1, 2024
1 parent 3b2f227 commit 9f22915
Show file tree
Hide file tree
Showing 3 changed files with 57 additions and 12 deletions.
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ time = { version = "0.3", features = ["formatting", "parsing"] }
tokio = { version = "1", features = ["fs", "io-util"], optional = true }
weezl = "0.1"
rangemap = "1.5"
aes = "0.8.4"
cbc = "0.1.2"

[dev-dependencies]
clap = { version = "4.0", features = ["derive"] }
Expand Down
10 changes: 8 additions & 2 deletions src/document.rs
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,12 @@ impl Document {
.unwrap_or(true);

let key = encryption::get_encryption_key(self, &password, true)?;
let cfm = self.get_encrypted()?.get(b"CF")?.as_dict()?
.get(b"StdCF")?
.as_dict()?
.get(b"CFM")?
.as_name().unwrap_or_default();
let is_aes = cfm == b"AESV2";
for (&id, obj) in self.objects.iter_mut() {
// The encryption dictionary is not encrypted, leave it alone
if id == encryption_obj_id {
Expand All @@ -281,7 +287,7 @@ impl Document {
continue;
}

let decrypted = match encryption::decrypt_object(&key, id, &*obj) {
let decrypted = match encryption::decrypt_object(&key, id, &*obj, is_aes) {
Ok(content) => content,
Err(encryption::DecryptionError::NotDecryptable) => {
continue;
Expand All @@ -302,7 +308,7 @@ impl Document {
if let Ok(info_obj_id) = self.trailer.get(b"Info").and_then(Object::as_reference) {
if let Ok(info_dict) = self.get_object_mut(info_obj_id).and_then(Object::as_dict_mut) {
for (_, info_obj) in info_dict.iter_mut() {
if let Ok(content) = encryption::decrypt_object(&key, info_obj_id, &*info_obj) {
if let Ok(content) = encryption::decrypt_object(&key, info_obj_id, &*info_obj, is_aes) {
info_obj.as_str_mut()?.clear();
info_obj.as_str_mut()?.extend(content);
};
Expand Down
57 changes: 47 additions & 10 deletions src/encryption.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ use crate::rc4::Rc4;
use crate::{Document, Object, ObjectId};
use md5::{Digest as _, Md5};
use std::fmt;
use aes::cipher::{
block_padding::Pkcs7,
BlockDecryptMut,
KeyIvInit
};

#[derive(Debug)]
pub enum DecryptionError {
Expand Down Expand Up @@ -49,6 +54,8 @@ const PAD_BYTES: [u8; 32] = [
0xB6, 0xD0, 0x68, 0x3E, 0x80, 0x2F, 0x0C, 0xA9, 0xFE, 0x64, 0x53, 0x69, 0x7A,
];

type Aes128CbcDec = cbc::Decryptor<aes::Aes128>;

const DEFAULT_KEY_LEN: Object = Object::Integer(40);
const DEFAULT_ALGORITHM: Object = Object::Integer(0);

Expand Down Expand Up @@ -83,9 +90,11 @@ where
.unwrap_or(&DEFAULT_ALGORITHM)
.as_i64()
.map_err(|_| DecryptionError::InvalidType)?;
// Currently only support V = 1 or 2
if !(1..=2).contains(&algorithm) {
return Err(DecryptionError::UnsupportedEncryption);
// Currently only support V = 1, 2 or 4
match algorithm {
1..=2 => {}
4 => {}
_ => return Err(DecryptionError::UnsupportedEncryption),
}

// Revision number dictates hashing strategy
Expand All @@ -94,7 +103,7 @@ where
.map_err(|_| DecryptionError::MissingRevision)?
.as_i64()
.map_err(|_| DecryptionError::InvalidType)?;
if !(2..=3).contains(&revision) {
if !(2..=4).contains(&revision) {
return Err(DecryptionError::UnsupportedEncryption);
}

Expand Down Expand Up @@ -138,8 +147,14 @@ where
.map_err(|_| DecryptionError::InvalidType)?;
key.extend_from_slice(file_id_0);

let encrypt_metadata = encryption_dict
.get(b"EncryptMetadata")
.unwrap_or(&Object::Boolean(true))
.as_bool()
.map_err(|_| DecryptionError::InvalidType)?;

// 3.2.6 Revision >=4
if revision >= 4 {
if revision >= 4 && !encrypt_metadata {
key.extend_from_slice(&[0xFF_u8, 0xFF, 0xFF, 0xFF]);
}

Expand Down Expand Up @@ -208,20 +223,29 @@ where

/// Decrypts `obj` and returns the content of the string or stream.
/// If obj is not an decryptable type, returns the NotDecryptable error.
pub fn decrypt_object<Key>(key: Key, obj_id: ObjectId, obj: &Object) -> Result<Vec<u8>, DecryptionError>
pub fn decrypt_object<Key>(key: Key, obj_id: ObjectId, obj: &Object, aes: bool) -> Result<Vec<u8>, DecryptionError>
where
Key: AsRef<[u8]>,
{
let key = key.as_ref();
let mut builder = Vec::<u8>::with_capacity(key.len() + 5);
let len = if aes {
key.len() + 9
} else {
key.len() + 5
};
let mut builder = Vec::<u8>::with_capacity(len);
builder.extend_from_slice(key.as_ref());

// Extend the key with the lower 3 bytes of the object number
builder.extend_from_slice(&obj_id.0.to_le_bytes()[..3]);
// and the lower 2 bytes of the generation number
builder.extend_from_slice(&obj_id.1.to_le_bytes()[..2]);

// Now construct the rc4 key
if aes {
builder.append(&mut vec![0x73, 0x41, 0x6C, 0x54]);
}

// Now construct the rc4 key
let key_len = std::cmp::min(key.len() + 5, 16);
let rc4_key = &Md5::digest(builder)[..key_len];

Expand All @@ -233,8 +257,21 @@ where
}
};

// Decrypt using the rc4 algorithm
Ok(Rc4::new(rc4_key).decrypt(encrypted))
if aes {
let mut iv = [0x00u8; 16];
for (elem, i) in encrypted.iter().zip(0..16) {
iv[i] = *elem;
}
// Decrypt using the aes algorithm
let data = &mut encrypted[16..].to_vec();
let pt = Aes128CbcDec::new(rc4_key.into(), &iv.into())
.decrypt_padded_mut::<Pkcs7>(data)
.unwrap();
Ok(pt.to_vec())
} else {
// Decrypt using the rc4 algorithm
Ok(Rc4::new(rc4_key).decrypt(encrypted))
}
}

#[cfg(test)]
Expand Down

0 comments on commit 9f22915

Please sign in to comment.