use crate::object::{PdfDict, PdfObject};
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum KeyLength {
Bits40,
Bits128,
}
impl KeyLength {
fn bytes(self) -> usize {
match self { KeyLength::Bits40 => 5, KeyLength::Bits128 => 16 }
}
fn revision(self) -> u8 {
match self { KeyLength::Bits40 => 2, KeyLength::Bits128 => 3 }
}
}
#[derive(Debug, Clone)]
pub struct Permissions {
pub allow_print: bool,
pub allow_modify: bool,
pub allow_copy: bool,
pub allow_annotations: bool,
pub allow_form_fill: bool,
pub allow_accessibility:bool,
pub allow_assemble: bool,
pub allow_print_hq: bool,
}
impl Default for Permissions {
fn default() -> Self {
Self {
allow_print: true,
allow_modify: true,
allow_copy: true,
allow_annotations: true,
allow_form_fill: true,
allow_accessibility: true,
allow_assemble: true,
allow_print_hq: true,
}
}
}
impl Permissions {
pub fn to_pdf_int(&self) -> i32 {
let mut p: u32 = 0xFFFFF0C0; if self.allow_print { p |= 1 << 2; }
if self.allow_modify { p |= 1 << 3; }
if self.allow_copy { p |= 1 << 4; }
if self.allow_annotations { p |= 1 << 5; }
if self.allow_form_fill { p |= 1 << 8; }
if self.allow_accessibility { p |= 1 << 9; }
if self.allow_assemble { p |= 1 << 10; }
if self.allow_print_hq { p |= 1 << 11; }
p as i32
}
}
#[derive(Debug, Clone)]
pub struct Encryption {
pub owner_password: String,
pub user_password: String,
pub key_length: KeyLength,
pub permissions: Permissions,
}
impl Encryption {
pub fn new(owner_password: impl Into<String>, user_password: impl Into<String>) -> Self {
Self {
owner_password: owner_password.into(),
user_password: user_password.into(),
key_length: KeyLength::Bits128,
permissions: Permissions::default(),
}
}
pub fn key_length(mut self, kl: KeyLength) -> Self { self.key_length = kl; self }
pub fn no_print(mut self) -> Self { self.permissions.allow_print = false; self }
pub fn no_modify(mut self) -> Self { self.permissions.allow_modify = false; self }
pub fn no_copy(mut self) -> Self { self.permissions.allow_copy = false; self }
pub fn no_annotations(mut self) -> Self { self.permissions.allow_annotations = false; self }
pub fn no_form_fill(mut self) -> Self { self.permissions.allow_form_fill = false; self }
pub fn view_only(mut self) -> Self {
self.permissions.allow_print = false;
self.permissions.allow_modify = false;
self.permissions.allow_copy = false;
self.permissions.allow_annotations = false;
self.permissions.allow_form_fill = false;
self.permissions.allow_assemble = false;
self
}
pub fn derive_file_key(&self, doc_id: &[u8]) -> Vec<u8> {
let key_len = self.key_length.bytes();
let rev = self.key_length.revision();
let p_bytes = (self.permissions.to_pdf_int() as u32).to_le_bytes();
let padded = pad_password(self.user_password.as_bytes());
let o_entry = self.compute_o_entry(key_len);
let mut data = Vec::new();
data.extend_from_slice(&padded);
data.extend_from_slice(&o_entry);
data.extend_from_slice(&p_bytes);
data.extend_from_slice(doc_id);
if rev >= 4 { data.push(0xFF); }
let mut hash = md5(&data);
if rev >= 3 {
for _ in 0..50 {
hash = md5(&hash[..key_len]);
}
}
hash[..key_len].to_vec()
}
fn compute_o_entry(&self, key_len: usize) -> Vec<u8> {
let rev = self.key_length.revision();
let padded_owner = pad_password(self.owner_password.as_bytes());
let mut hash = md5(&padded_owner);
if rev >= 3 {
for _ in 0..50 { hash = md5(&hash[..key_len]); }
}
let owner_key = &hash[..key_len];
let padded_user = pad_password(self.user_password.as_bytes());
let mut result = padded_user;
if rev == 2 {
rc4_crypt(&mut result, owner_key);
} else {
for i in 0u8..20 {
let mut key_i: Vec<u8> = owner_key.iter().map(|b| b ^ i).collect();
rc4_crypt(&mut result, &key_i);
key_i.iter_mut().for_each(|b| *b = 0); }
}
result.to_vec()
}
pub fn compute_u_entry(&self, file_key: &[u8]) -> Vec<u8> {
let rev = self.key_length.revision();
if rev == 2 {
let mut data = PDF_PADDING.to_vec();
rc4_crypt(&mut data, file_key);
data
} else {
let doc_id = vec![0u8; 16]; let mut data = Vec::new();
data.extend_from_slice(&PDF_PADDING);
data.extend_from_slice(&doc_id);
let mut hash = md5(&data)[..16].to_vec();
for i in 0u8..20 {
let key_i: Vec<u8> = file_key.iter().map(|b| b ^ i).collect();
rc4_crypt(&mut hash, &key_i);
}
hash.resize(32, 0);
hash
}
}
pub fn build_encrypt_dict(&self, doc_id: &[u8]) -> PdfDict {
let key_len = self.key_length.bytes();
let file_key = self.derive_file_key(doc_id);
let o_entry = self.compute_o_entry(key_len);
let u_entry = self.compute_u_entry(&file_key);
let p_val = self.permissions.to_pdf_int();
let rev = self.key_length.revision();
let mut d = PdfDict::new();
d.set("Filter", PdfObject::name("Standard"));
d.set("V", PdfObject::Integer(if rev == 2 { 1 } else { 2 }));
d.set("R", PdfObject::Integer(rev as i64));
d.set("Length", PdfObject::Integer((key_len * 8) as i64));
d.set("P", PdfObject::Integer(p_val as i64));
d.set("O", PdfObject::HexString(o_entry));
d.set("U", PdfObject::HexString(u_entry));
d
}
pub fn encrypt_bytes(&self, file_key: &[u8], data: &[u8], obj_id: u32, obj_gen: u16) -> Vec<u8> {
let mut key_data = file_key.to_vec();
key_data.push((obj_id & 0xFF) as u8);
key_data.push(((obj_id >> 8) & 0xFF) as u8);
key_data.push(((obj_id >> 16) & 0xFF) as u8);
key_data.push((obj_gen & 0xFF) as u8);
key_data.push(((obj_gen >> 8) & 0xFF) as u8);
let hash = md5(&key_data);
let obj_key_len = (file_key.len() + 5).min(16);
let obj_key = &hash[..obj_key_len];
let mut out = data.to_vec();
rc4_crypt(&mut out, obj_key);
out
}
}
fn rc4_crypt(data: &mut [u8], key: &[u8]) {
let mut s: [u8; 256] = core::array::from_fn(|i| i as u8);
let mut j: usize = 0;
for i in 0..256 {
j = (j + s[i] as usize + key[i % key.len()] as usize) % 256;
s.swap(i, j);
}
let mut i = 0usize;
let mut j = 0usize;
for byte in data.iter_mut() {
i = (i + 1) % 256;
j = (j + s[i] as usize) % 256;
s.swap(i, j);
*byte ^= s[(s[i] as usize + s[j] as usize) % 256];
}
}
fn md5(data: &[u8]) -> [u8; 16] {
const S: [u32; 64] = [
7,12,17,22, 7,12,17,22, 7,12,17,22, 7,12,17,22,
5, 9,14,20, 5, 9,14,20, 5, 9,14,20, 5, 9,14,20,
4,11,16,23, 4,11,16,23, 4,11,16,23, 4,11,16,23,
6,10,15,21, 6,10,15,21, 6,10,15,21, 6,10,15,21,
];
const K: [u32; 64] = [
0xd76aa478,0xe8c7b756,0x242070db,0xc1bdceee,
0xf57c0faf,0x4787c62a,0xa8304613,0xfd469501,
0x698098d8,0x8b44f7af,0xffff5bb1,0x895cd7be,
0x6b901122,0xfd987193,0xa679438e,0x49b40821,
0xf61e2562,0xc040b340,0x265e5a51,0xe9b6c7aa,
0xd62f105d,0x02441453,0xd8a1e681,0xe7d3fbc8,
0x21e1cde6,0xc33707d6,0xf4d50d87,0x455a14ed,
0xa9e3e905,0xfcefa3f8,0x676f02d9,0x8d2a4c8a,
0xfffa3942,0x8771f681,0x6d9d6122,0xfde5380c,
0xa4beea44,0x4bdecfa9,0xf6bb4b60,0xbebfbc70,
0x289b7ec6,0xeaa127fa,0xd4ef3085,0x04881d05,
0xd9d4d039,0xe6db99e5,0x1fa27cf8,0xc4ac5665,
0xf4292244,0x432aff97,0xab9423a7,0xfc93a039,
0x655b59c3,0x8f0ccc92,0xffeff47d,0x85845dd1,
0x6fa87e4f,0xfe2ce6e0,0xa3014314,0x4e0811a1,
0xf7537e82,0xbd3af235,0x2ad7d2bb,0xeb86d391,
];
let mut a0: u32 = 0x67452301;
let mut b0: u32 = 0xefcdab89;
let mut c0: u32 = 0x98badcfe;
let mut d0: u32 = 0x10325476;
let orig_len_bits = (data.len() as u64).wrapping_mul(8);
let mut msg = data.to_vec();
msg.push(0x80);
while msg.len() % 64 != 56 { msg.push(0); }
msg.extend_from_slice(&orig_len_bits.to_le_bytes());
for chunk in msg.chunks(64) {
let mut m = [0u32; 16];
for (i, word) in m.iter_mut().enumerate() {
*word = u32::from_le_bytes([chunk[i*4], chunk[i*4+1], chunk[i*4+2], chunk[i*4+3]]);
}
let (mut a, mut b, mut c, mut d) = (a0, b0, c0, d0);
for i in 0..64 {
let (f, g) = match i {
0..=15 => (( b & c) | (!b & d), i),
16..=31 => (( d & b) | (!d & c), (5*i+1)%16),
32..=47 => ( b ^ c ^ d, (3*i+5)%16),
_ => ( c ^ (b | !d), (7*i)%16),
};
let temp = d;
d = c;
c = b;
b = b.wrapping_add(
(a.wrapping_add(f).wrapping_add(K[i]).wrapping_add(m[g])).rotate_left(S[i])
);
a = temp;
}
a0 = a0.wrapping_add(a);
b0 = b0.wrapping_add(b);
c0 = c0.wrapping_add(c);
d0 = d0.wrapping_add(d);
}
let mut out = [0u8; 16];
out[0..4].copy_from_slice(&a0.to_le_bytes());
out[4..8].copy_from_slice(&b0.to_le_bytes());
out[8..12].copy_from_slice(&c0.to_le_bytes());
out[12..16].copy_from_slice(&d0.to_le_bytes());
out
}
const PDF_PADDING: [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,
];
fn pad_password(pw: &[u8]) -> [u8; 32] {
let mut out = [0u8; 32];
let take = pw.len().min(32);
out[..take].copy_from_slice(&pw[..take]);
if take < 32 {
out[take..].copy_from_slice(&PDF_PADDING[..32 - take]);
}
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_md5_empty() {
let h = md5(b"");
assert_eq!(h[0], 0xd4);
assert_eq!(h[1], 0x1d);
}
#[test]
fn test_md5_abc() {
let h = md5(b"abc");
assert_eq!(h[0], 0x90);
assert_eq!(h[1], 0x01);
}
#[test]
fn test_rc4_roundtrip() {
let key = b"secretkey";
let original = b"hello world".to_vec();
let mut data = original.clone();
rc4_crypt(&mut data, key);
rc4_crypt(&mut data, key); assert_eq!(data, original);
}
#[test]
fn test_permissions_all_default() {
let p = Permissions::default();
assert!(p.to_pdf_int() < 0); }
#[test]
fn test_encrypt_dict_has_required_keys() {
let enc = Encryption::new("owner", "user");
let doc_id = vec![0u8; 16];
let d = enc.build_encrypt_dict(&doc_id);
assert!(d.get("Filter").is_some());
assert!(d.get("O").is_some());
assert!(d.get("U").is_some());
assert!(d.get("P").is_some());
}
}