1use pdfrum_object::ObjRef;
25
26use crate::key::SmallKey;
27use crate::primitives::{BLOCK, aes_cbc_decrypt, aes_cbc_encrypt, md5};
28use crate::rc4::rc4;
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
40pub enum CryptClass {
41 Stream,
43 String,
45 Embedded,
47}
48
49#[derive(Debug, Clone, Copy, PartialEq, Eq)]
60pub struct Iv(pub [u8; BLOCK]);
61
62impl Iv {
63 #[must_use]
65 pub const fn bytes(&self) -> &[u8; BLOCK] {
66 &self.0
67 }
68}
69
70const AES_SALT: [u8; 4] = *b"sAlT";
72
73fn salted(key: &SmallKey, obj: ObjRef) -> ([u8; 48], usize) {
79 let mut scratch = [0u8; 48];
80 let key_len = key.len().min(scratch.len());
81 if let (Some(head), Some(from)) = (scratch.get_mut(..key_len), key.bytes().get(..key_len)) {
82 head.copy_from_slice(from);
83 }
84 let num = obj.num.to_le_bytes();
85 let generation = obj.generation.to_le_bytes();
86 for (offset, byte) in num
87 .iter()
88 .take(3)
89 .chain(generation.iter().take(2))
90 .enumerate()
91 {
92 if let Some(slot) = scratch.get_mut(key_len + offset) {
93 *slot = *byte;
94 }
95 }
96 (scratch, key_len)
97}
98
99#[must_use]
104fn rc4_object_key(key: &SmallKey, obj: ObjRef) -> Vec<u8> {
105 let (scratch, key_len) = salted(key, obj);
106 let digest = md5(scratch.get(..key_len + 5).unwrap_or(&scratch));
107 let len = (key_len + 5).min(digest.len());
108 digest.get(..len).unwrap_or(&digest).to_vec()
109}
110
111#[must_use]
118fn aes_v4_object_key(key: &SmallKey, obj: ObjRef) -> [u8; 16] {
119 let (mut scratch, key_len) = salted(key, obj);
120 for (offset, byte) in AES_SALT.iter().enumerate() {
121 if let Some(slot) = scratch.get_mut(key_len + 5 + offset) {
122 *slot = *byte;
123 }
124 }
125 md5(scratch.get(..key_len + 9).unwrap_or(&scratch))
126}
127
128#[must_use]
130pub(crate) fn decrypt_rc4(key: &SmallKey, obj: ObjRef, data: &[u8]) -> Vec<u8> {
131 rc4(&rc4_object_key(key, obj), data)
132}
133
134#[must_use]
136pub(crate) fn decrypt_aes_v4(key: &SmallKey, obj: ObjRef, data: &[u8]) -> Vec<u8> {
137 decrypt_aes_cbc(&aes_v4_object_key(key, obj), data)
138}
139
140#[must_use]
142pub(crate) fn decrypt_aes_v5(key: &[u8; 32], data: &[u8]) -> Vec<u8> {
143 decrypt_aes_cbc(key, data)
144}
145
146#[must_use]
150pub(crate) fn encrypt_rc4(key: &SmallKey, obj: ObjRef, data: &[u8]) -> Vec<u8> {
151 rc4(&rc4_object_key(key, obj), data)
152}
153
154#[must_use]
165pub(crate) fn encrypt_aes_v4(
166 key: &SmallKey,
167 obj: ObjRef,
168 iv: &[u8; BLOCK],
169 data: &[u8],
170) -> Vec<u8> {
171 encrypt_aes_cbc(&aes_v4_object_key(key, obj), iv, data)
172}
173
174#[must_use]
176pub(crate) fn encrypt_aes_v5(key: &[u8; 32], iv: &[u8; BLOCK], data: &[u8]) -> Vec<u8> {
177 encrypt_aes_cbc(key, iv, data)
178}
179
180#[must_use]
193fn encrypt_aes_cbc(key: &[u8], iv: &[u8; BLOCK], data: &[u8]) -> Vec<u8> {
194 let pad = BLOCK - data.len() % BLOCK;
195 let mut body = data.to_vec();
196 body.extend(std::iter::repeat_n(u8::try_from(pad).unwrap_or(0), pad));
198 if aes_cbc_encrypt(key, iv, &mut body).is_err() {
199 return Vec::new();
200 }
201 let mut out = Vec::with_capacity(BLOCK.saturating_add(body.len()));
202 out.extend_from_slice(iv);
203 out.append(&mut body);
204 out
205}
206
207#[must_use]
229fn decrypt_aes_cbc(key: &[u8], data: &[u8]) -> Vec<u8> {
230 let Some(iv) = data
231 .get(..BLOCK)
232 .and_then(|s| <[u8; BLOCK]>::try_from(s).ok())
233 else {
234 return Vec::new();
235 };
236 let body = data.get(BLOCK..).unwrap_or_default();
237 let whole = body.len() - body.len() % BLOCK;
238 let Some(mut out) = body.get(..whole).map(<[u8]>::to_vec) else {
239 return Vec::new();
240 };
241 if aes_cbc_decrypt(key, &iv, &mut out).is_err() {
242 return Vec::new();
243 }
244
245 if whole < body.len() {
246 return out;
249 }
250 let Some(pad) = out.last().copied() else {
253 return out;
254 };
255 if usize::from(pad) >= BLOCK {
256 out.truncate(out.len() - BLOCK);
257 } else {
258 out.truncate(out.len() - usize::from(pad));
259 }
260 out
261}
262
263#[cfg(test)]
264mod tests {
265 use super::{
266 BLOCK, CryptClass, Iv, aes_v4_object_key, decrypt_aes_cbc, decrypt_aes_v4, decrypt_aes_v5,
267 decrypt_rc4, encrypt_aes_cbc, encrypt_aes_v4, encrypt_aes_v5, encrypt_rc4, rc4_object_key,
268 };
269 use crate::key::SmallKey;
270 use crate::primitives::aes_cbc_encrypt;
271 use pdfrum_object::ObjRef;
272
273 fn key(len: usize) -> SmallKey {
274 SmallKey::from_prefix(&(0..32u8).collect::<Vec<_>>(), len)
275 }
276
277 #[test]
280 fn rc4_object_key_length_is_capped_at_sixteen() {
281 assert_eq!(rc4_object_key(&key(5), ObjRef::new(1, 0)).len(), 10);
282 assert_eq!(rc4_object_key(&key(10), ObjRef::new(1, 0)).len(), 15);
283 assert_eq!(rc4_object_key(&key(16), ObjRef::new(1, 0)).len(), 16);
284 }
285
286 #[test]
289 fn object_numbers_contribute_three_bytes() {
290 let k = key(16);
291 assert_eq!(
292 rc4_object_key(&k, ObjRef::new(1, 0)),
293 rc4_object_key(&k, ObjRef::new(0x0100_0001, 0))
294 );
295 assert_ne!(
296 rc4_object_key(&k, ObjRef::new(1, 0)),
297 rc4_object_key(&k, ObjRef::new(2, 0))
298 );
299 assert_ne!(
300 rc4_object_key(&k, ObjRef::new(1, 0)),
301 rc4_object_key(&k, ObjRef::new(1, 1))
302 );
303 }
304
305 #[test]
306 fn generation_contributes_two_bytes() {
307 let k = key(16);
308 assert_ne!(
309 rc4_object_key(&k, ObjRef::new(1, 0x0100)),
310 rc4_object_key(&k, ObjRef::new(1, 0))
311 );
312 }
313
314 #[test]
316 fn aes_v4_object_key_is_a_full_digest() {
317 let derived = aes_v4_object_key(&key(16), ObjRef::new(1, 0));
318 assert_eq!(derived.len(), 16);
319 let mut scratch = Vec::new();
322 scratch.extend_from_slice(key(16).bytes());
323 scratch.extend_from_slice(&[1, 0, 0, 0, 0]);
324 scratch.extend_from_slice(b"sAlT");
325 assert_eq!(scratch.len(), 25);
326 assert_eq!(derived, crate::primitives::md5(&scratch));
327 }
328
329 #[test]
331 fn aes_v5_uses_the_file_key_verbatim() {
332 let file_key = [7u8; 32];
333 let payload = encrypted(&file_key, b"hello");
334 let first = decrypt_aes_v5(&file_key, &payload);
335 assert_eq!(first, b"hello");
336 assert_eq!(decrypt_aes_v5(&file_key, &payload), first);
339 }
340
341 fn encrypted(key: &[u8], plaintext: &[u8]) -> Vec<u8> {
343 let iv = [0x5Au8; BLOCK];
344 let pad = BLOCK - plaintext.len() % BLOCK;
345 let mut body = plaintext.to_vec();
346 body.extend(std::iter::repeat_n(u8::try_from(pad).unwrap_or(0), pad));
347 aes_cbc_encrypt(key, &iv, &mut body).expect("valid key");
348 let mut out = iv.to_vec();
349 out.extend_from_slice(&body);
350 out
351 }
352
353 #[test]
355 fn aes_lengths_under_seventeen_bytes_yield_nothing() {
356 let k = [0u8; 16];
357 for len in 0..=BLOCK {
358 assert!(
359 decrypt_aes_cbc(&k, &vec![0xAA; len]).is_empty(),
360 "{len} bytes"
361 );
362 }
363 }
364
365 #[test]
366 fn a_round_trip_recovers_the_plaintext() {
367 let k = [3u8; 16];
368 for len in [0usize, 1, 15, 16, 17, 31, 32, 100] {
369 let plaintext: Vec<u8> = (0..len)
370 .map(|i| u8::try_from(i % 251).unwrap_or(0))
371 .collect();
372 assert_eq!(
373 decrypt_aes_cbc(&k, &encrypted(&k, &plaintext)),
374 plaintext,
375 "{len} bytes"
376 );
377 }
378 }
379
380 fn one_block_with_last(key: &[u8], last: u8) -> Vec<u8> {
382 let iv = [0u8; BLOCK];
383 let mut block = [0u8; BLOCK];
384 if let Some(slot) = block.last_mut() {
385 *slot = last;
386 }
387 let mut body = block.to_vec();
388 aes_cbc_encrypt(key, &iv, &mut body).expect("valid key");
389 let mut out = iv.to_vec();
390 out.extend_from_slice(&body);
391 out
392 }
393
394 #[test]
397 fn the_final_block_pad_byte_decides_how_much_survives() {
398 let k = [9u8; 16];
399 assert!(decrypt_aes_cbc(&k, &one_block_with_last(&k, 0x10)).is_empty());
400 assert!(decrypt_aes_cbc(&k, &one_block_with_last(&k, 0xFF)).is_empty());
401 assert_eq!(
402 decrypt_aes_cbc(&k, &one_block_with_last(&k, 0)).len(),
403 BLOCK
404 );
405 assert_eq!(decrypt_aes_cbc(&k, &one_block_with_last(&k, 1)).len(), 15);
406 assert_eq!(decrypt_aes_cbc(&k, &one_block_with_last(&k, 15)).len(), 1);
407 }
408
409 #[test]
411 fn inconsistent_padding_is_accepted() {
412 let k = [9u8; 16];
413 assert_eq!(decrypt_aes_cbc(&k, &one_block_with_last(&k, 4)).len(), 12);
415 }
416
417 #[test]
420 fn a_partial_tail_is_dropped_and_the_block_before_it_is_kept() {
421 let k = [4u8; 16];
422 let mut payload = one_block_with_last(&k, 3);
423 assert_eq!(decrypt_aes_cbc(&k, &payload).len(), 13);
424 payload.extend_from_slice(&[0xEE; 5]);
425 assert_eq!(decrypt_aes_cbc(&k, &payload).len(), BLOCK);
427 }
428
429 #[test]
430 fn two_blocks_plus_a_tail_keep_both_blocks() {
431 let k = [4u8; 16];
432 let iv = [0u8; BLOCK];
433 let mut body = vec![0u8; 2 * BLOCK];
434 aes_cbc_encrypt(&k, &iv, &mut body).expect("valid key");
435 let mut payload = iv.to_vec();
436 payload.extend_from_slice(&body);
437 payload.extend_from_slice(&[0x11; 7]);
438 assert_eq!(decrypt_aes_cbc(&k, &payload).len(), 2 * BLOCK);
439 }
440
441 #[test]
443 fn rc4_preserves_length_and_round_trips() {
444 let k = key(16);
445 let obj = ObjRef::new(5, 0);
446 assert!(decrypt_rc4(&k, obj, &[]).is_empty());
447 let data: Vec<u8> = (0..77u8).collect();
448 let once = decrypt_rc4(&k, obj, &data);
449 assert_eq!(once.len(), data.len());
450 assert_eq!(decrypt_rc4(&k, obj, &once), data);
451 }
452
453 #[test]
454 fn a_key_aes_cannot_accept_yields_empty_output_rather_than_a_panic() {
455 for len in [0usize, 1, 15, 17, 31, 33] {
456 let bad = vec![0u8; len];
457 assert!(
458 decrypt_aes_cbc(&bad, &[0xAA; 48]).is_empty(),
459 "{len}-byte key"
460 );
461 }
462 }
463
464 #[test]
469 fn aes_encryption_grows_a_payload_by_a_vector_and_a_pad() {
470 let k = [0x2Bu8; 16];
471 for (plain, expected) in [
472 (0usize, 32),
473 (1, 32),
474 (15, 32),
475 (16, 48),
476 (17, 48),
477 (31, 48),
478 ] {
479 let out = encrypt_aes_cbc(&k, &[0u8; BLOCK], &vec![0xA5; plain]);
480 assert_eq!(out.len(), expected, "{plain} bytes of plaintext");
481 }
482 }
483
484 #[test]
487 fn aes_round_trips_through_the_quirky_decoder_at_every_length() {
488 let k = [0x3Cu8; 16];
489 for len in 0..96usize {
490 let plaintext: Vec<u8> = (0..len)
491 .map(|i| u8::try_from(i % 251).unwrap_or(0))
492 .collect();
493 let iv = [u8::try_from(len % 256).unwrap_or(0); BLOCK];
494 let sealed = encrypt_aes_cbc(&k, &iv, &plaintext);
495 assert_eq!(
496 decrypt_aes_cbc(&k, &sealed),
497 plaintext,
498 "{len} bytes did not survive"
499 );
500 }
501 }
502
503 #[test]
506 fn the_vector_is_the_prefix_and_changes_every_block() {
507 let k = [0x11u8; 32];
508 let plaintext = vec![0u8; 3 * BLOCK];
509 let first = encrypt_aes_cbc(&k, &[1u8; BLOCK], &plaintext);
510 let second = encrypt_aes_cbc(&k, &[2u8; BLOCK], &plaintext);
511 assert_eq!(first.get(..BLOCK), Some(&[1u8; BLOCK][..]));
512 assert_eq!(second.get(..BLOCK), Some(&[2u8; BLOCK][..]));
513 assert_ne!(first.get(BLOCK..), second.get(BLOCK..));
514 }
515
516 #[test]
519 fn the_object_keyed_ciphers_round_trip_under_the_same_reference() {
520 let k = key(16);
521 let obj = ObjRef::new(12, 3);
522 let payload: Vec<u8> = (0..70u8).collect();
523
524 assert_eq!(
525 decrypt_rc4(&k, obj, &encrypt_rc4(&k, obj, &payload)),
526 payload
527 );
528 let sealed = encrypt_aes_v4(&k, obj, &Iv([9; BLOCK]).0, &payload);
529 assert_eq!(decrypt_aes_v4(&k, obj, &sealed), payload);
530 assert_ne!(decrypt_aes_v4(&k, ObjRef::new(13, 3), &sealed), payload);
532
533 let file_key = [0x5Au8; 32];
534 let sealed = encrypt_aes_v5(&file_key, &[3; BLOCK], &payload);
535 assert_eq!(decrypt_aes_v5(&file_key, &sealed), payload);
536 }
537
538 #[test]
541 fn rc4_encryption_is_its_own_inverse() {
542 let k = key(10);
543 let obj = ObjRef::new(3, 0);
544 let payload: Vec<u8> = (0..40u8).map(|i| i.wrapping_mul(7)).collect();
545 assert_eq!(
546 encrypt_rc4(&k, obj, &payload),
547 decrypt_rc4(&k, obj, &payload)
548 );
549 assert!(encrypt_rc4(&k, obj, &[]).is_empty());
550 }
551
552 #[test]
553 fn an_impossible_key_encrypts_to_nothing_rather_than_panicking() {
554 for len in [0usize, 1, 15, 17, 31, 33] {
555 assert!(encrypt_aes_cbc(&vec![0u8; len], &[0; BLOCK], b"payload").is_empty());
556 }
557 }
558
559 #[test]
560 fn crypt_classes_are_distinct_values() {
561 assert_ne!(CryptClass::Stream, CryptClass::String);
562 assert_ne!(CryptClass::String, CryptClass::Embedded);
563 }
564}