1use aes::cipher::{generic_array::GenericArray, BlockDecryptMut, BlockEncryptMut, KeyIvInit};
26use sha2::Digest;
27use std::sync::Arc;
28use zpdf_core::{ObjectId, PdfDict, PdfObject, PdfString};
29
30const PAD: [u8; 32] = [
32 0x28, 0xBF, 0x4E, 0x5E, 0x4E, 0x75, 0x8A, 0x41, 0x64, 0x00, 0x4E, 0x56, 0xFF, 0xFA, 0x01, 0x08,
33 0x2E, 0x2E, 0x00, 0xB6, 0xD0, 0x68, 0x3E, 0x80, 0x2F, 0x0C, 0xA9, 0xFE, 0x64, 0x53, 0x69, 0x7A,
34];
35
36#[derive(Clone, Copy, PartialEq, Eq, Debug)]
37enum Algo {
38 Identity,
41 Rc4,
43 AesV2,
45 AesV3,
47}
48
49pub enum BuildResult {
51 Decryptor(Decryptor),
54 Degrade,
57 WrongPassword,
60}
61
62pub struct Decryptor {
64 key: Vec<u8>,
66 stm_algo: Algo,
68 str_algo: Algo,
70 encrypt_id: Option<ObjectId>,
74 encrypt_metadata: bool,
78}
79
80impl Decryptor {
81 pub fn from_encrypt_dict(
90 dict: &PdfDict,
91 id_first: &[u8],
92 encrypt_id: Option<ObjectId>,
93 password: &[u8],
94 ) -> BuildResult {
95 let filter = dict.get_name("Filter").unwrap_or("");
96 if filter != "Standard" {
97 tracing::warn!(
98 "unsupported security handler /Filter {filter}; document will not decrypt"
99 );
100 return BuildResult::Degrade;
101 }
102
103 let v = dict.get_i64("V").unwrap_or(0);
104 let r = dict.get_i64("R").unwrap_or(0);
105
106 let (stm_algo, str_algo) = if v >= 4 {
111 (
112 algo_for_filter(dict, dict.get_name("StmF").unwrap_or("Identity")),
113 algo_for_filter(dict, dict.get_name("StrF").unwrap_or("Identity")),
114 )
115 } else {
116 (Algo::Rc4, Algo::Rc4)
117 };
118
119 let encrypt_metadata = match dict.get("EncryptMetadata") {
121 Some(PdfObject::Bool(b)) => *b,
122 _ => true,
123 };
124
125 let key = if v >= 5 {
126 match compute_key_v5(dict, r, password) {
130 Some(k) => k,
131 None if password.is_empty() => return BuildResult::Degrade,
132 None => return BuildResult::WrongPassword,
133 }
134 } else {
135 let o = string_bytes(dict, "O");
136 let u = string_bytes(dict, "U");
137 let p = match dict.get("P") {
139 Some(PdfObject::Integer(n)) => *n as i32,
140 Some(PdfObject::Real(n)) => *n as i32,
141 _ => 0,
142 };
143 let length_bits = if stm_algo == Algo::AesV2 || str_algo == Algo::AesV2 {
145 128
146 } else {
147 key_length_bits(dict, v)
148 };
149
150 match authenticate_rc4(
151 password,
152 &o,
153 &u,
154 p,
155 id_first,
156 r,
157 length_bits,
158 encrypt_metadata,
159 ) {
160 Ok(key) => key,
161 Err(_) if !password.is_empty() && !u.is_empty() => {
167 return BuildResult::WrongPassword;
168 }
169 Err(best_effort_key) => {
170 if u.is_empty() && !password.is_empty() {
171 tracing::warn!(
172 "encrypted document has no /U to authenticate the password against \
173 (V={v} R={r}); proceeding with the supplied password unverified"
174 );
175 } else {
176 tracing::warn!(
177 "encryption key did not validate against /U (V={v} R={r}); the PDF \
178 may require a password — decrypted content may be garbage"
179 );
180 }
181 best_effort_key
182 }
183 }
184 };
185
186 BuildResult::Decryptor(Self {
187 key,
188 stm_algo,
189 str_algo,
190 encrypt_id,
191 encrypt_metadata,
192 })
193 }
194
195 pub fn decrypt_object(&self, obj: &mut PdfObject, id: ObjectId) {
199 if Some(id) == self.encrypt_id {
200 return;
201 }
202 self.walk(obj, id);
203 }
204
205 pub fn decrypt_stream_bytes(&self, id: ObjectId, data: &[u8]) -> Vec<u8> {
209 self.decrypt(id, data, self.stm_algo)
210 }
211
212 pub fn encrypt_stream_bytes(&self, id: ObjectId, data: &[u8]) -> Vec<u8> {
216 self.encrypt(id, data, self.stm_algo)
217 }
218
219 pub fn encrypt_string_bytes(&self, id: ObjectId, data: &[u8]) -> Vec<u8> {
221 self.encrypt(id, data, self.str_algo)
222 }
223
224 pub fn encrypt_object_strings(&self, obj: &mut PdfObject, id: ObjectId) {
229 match obj {
230 PdfObject::String(s) if self.str_algo != Algo::Identity => {
231 *s = PdfString(self.encrypt(id, &s.0, self.str_algo));
232 }
233 PdfObject::String(_) => {}
234 PdfObject::Array(a) => {
235 for o in a.iter_mut() {
236 self.encrypt_object_strings(o, id);
237 }
238 }
239 PdfObject::Dict(d) => {
240 for v in d.0.values_mut() {
241 self.encrypt_object_strings(v, id);
242 }
243 }
244 PdfObject::Stream(s) => {
245 for v in s.dict.0.values_mut() {
246 self.encrypt_object_strings(v, id);
247 }
248 }
249 _ => {}
250 }
251 }
252
253 fn encrypt(&self, id: ObjectId, data: &[u8], algo: Algo) -> Vec<u8> {
255 match algo {
256 Algo::Identity => data.to_vec(),
257 Algo::Rc4 => rc4(&self.object_key(id, algo), data),
259 Algo::AesV2 | Algo::AesV3 => aes_cbc_encrypt(&self.object_key(id, algo), data),
260 }
261 }
262
263 fn walk(&self, obj: &mut PdfObject, id: ObjectId) {
264 match obj {
265 PdfObject::String(s) if self.str_algo != Algo::Identity => {
266 *s = PdfString(self.decrypt(id, &s.0, self.str_algo));
267 }
268 PdfObject::String(_) => {}
269 PdfObject::Array(a) => {
270 for o in a.iter_mut() {
271 self.walk(o, id);
272 }
273 }
274 PdfObject::Dict(d) => {
275 for v in d.0.values_mut() {
276 self.walk(v, id);
277 }
278 }
279 PdfObject::Stream(s) => {
280 let typ = s.dict.get_name("Type").unwrap_or("");
281 let is_xref = typ == "XRef";
283 let plain_meta = !self.encrypt_metadata && typ == "Metadata";
289 if !is_xref && !plain_meta && self.stm_algo != Algo::Identity {
290 let dec = self.decrypt(id, &s.data, self.stm_algo);
291 s.data = Arc::from(dec);
292 }
293 for v in s.dict.0.values_mut() {
294 self.walk(v, id);
295 }
296 }
297 _ => {}
299 }
300 }
301
302 fn decrypt(&self, id: ObjectId, data: &[u8], algo: Algo) -> Vec<u8> {
304 match algo {
305 Algo::Identity => data.to_vec(),
306 Algo::Rc4 => rc4(&self.object_key(id, algo), data),
307 Algo::AesV2 | Algo::AesV3 => aes_cbc_decrypt(&self.object_key(id, algo), data),
308 }
309 }
310
311 fn object_key(&self, id: ObjectId, algo: Algo) -> Vec<u8> {
315 if algo == Algo::AesV3 {
316 return self.key.clone();
317 }
318 let mut input = Vec::with_capacity(self.key.len() + 9);
319 input.extend_from_slice(&self.key);
320 let num = id.0.to_le_bytes();
321 input.extend_from_slice(&num[..3]);
322 let gen = id.1.to_le_bytes();
323 input.extend_from_slice(&gen[..2]);
324 if algo == Algo::AesV2 {
325 input.extend_from_slice(b"sAlT");
326 }
327 let hash = md5(&input);
328 let n = (self.key.len() + 5).min(16);
329 hash[..n].to_vec()
330 }
331}
332
333fn key_length_bits(dict: &PdfDict, v: i64) -> i64 {
339 if v >= 4 {
340 let stmf = dict.get_name("StmF").unwrap_or("Identity");
341 if stmf != "Identity" {
342 if let Some(len) = dict
343 .get_dict("CF")
344 .ok()
345 .and_then(|cf| cf.get_dict(stmf).ok())
346 .and_then(|f| f.get_i64("Length").ok())
347 {
348 return if len <= 32 { len * 8 } else { len };
351 }
352 }
353 }
354 dict.get_i64("Length").unwrap_or(40)
355}
356
357fn validate_user_password(key: &[u8], u: &[u8], id_first: &[u8], r: i64) -> bool {
363 if u.is_empty() {
364 return false;
365 }
366 if r == 2 {
367 return rc4(key, &PAD) == u;
368 }
369 let mut input = Vec::with_capacity(PAD.len() + id_first.len());
372 input.extend_from_slice(&PAD);
373 input.extend_from_slice(id_first);
374 let mut x = rc4(key, &md5(&input));
375 for i in 1u8..=19 {
376 let step_key: Vec<u8> = key.iter().map(|b| b ^ i).collect();
377 x = rc4(&step_key, &x);
378 }
379 u.len() >= 16 && x.len() >= 16 && x[..16] == u[..16]
381}
382
383fn algo_for_filter(dict: &PdfDict, filter_name: &str) -> Algo {
388 if filter_name == "Identity" {
389 return Algo::Identity;
390 }
391 let cfm = dict
392 .get_dict("CF")
393 .ok()
394 .and_then(|cf| cf.get_dict(filter_name).ok())
395 .and_then(|f| f.get_name("CFM").ok())
396 .unwrap_or("V2");
397 match cfm {
398 "AESV2" => Algo::AesV2,
399 "AESV3" => Algo::AesV3,
400 "None" => Algo::Identity,
401 _ => Algo::Rc4,
402 }
403}
404
405fn pad_password(password: &[u8]) -> [u8; 32] {
408 let mut out = [0u8; 32];
409 let take = password.len().min(32);
410 out[..take].copy_from_slice(&password[..take]);
411 out[take..].copy_from_slice(&PAD[..32 - take]);
412 out
413}
414
415fn rc4_key_len(r: i64, length_bits: i64) -> usize {
417 if r == 2 {
418 5
419 } else {
420 (length_bits / 8).clamp(5, 16) as usize
421 }
422}
423
424#[allow(clippy::too_many_arguments)]
430fn authenticate_rc4(
431 password: &[u8],
432 o: &[u8],
433 u: &[u8],
434 p: i32,
435 id_first: &[u8],
436 r: i64,
437 length_bits: i64,
438 encrypt_metadata: bool,
439) -> std::result::Result<Vec<u8>, Vec<u8>> {
440 let key = compute_key_rc4(password, o, p, id_first, r, length_bits, encrypt_metadata);
442 if validate_user_password(&key, u, id_first, r) {
443 return Ok(key);
444 }
445 let recovered = recover_user_password_rc4(password, o, r, length_bits);
448 let owner_key = compute_key_rc4(&recovered, o, p, id_first, r, length_bits, encrypt_metadata);
449 if validate_user_password(&owner_key, u, id_first, r) {
450 return Ok(owner_key);
451 }
452 Err(key)
453}
454
455fn recover_user_password_rc4(owner_password: &[u8], o: &[u8], r: i64, length_bits: i64) -> Vec<u8> {
459 let n = rc4_key_len(r, length_bits);
460 let mut hash = md5(&pad_password(owner_password));
461 if r >= 3 {
462 for _ in 0..50 {
463 hash = md5(&hash[..n]);
464 }
465 }
466 let owner_key = &hash[..n];
467
468 let mut user = o.to_vec();
469 if r == 2 {
470 user = rc4(owner_key, &user);
471 } else {
472 for i in (0..=19u8).rev() {
473 let step_key: Vec<u8> = owner_key.iter().map(|b| b ^ i).collect();
474 user = rc4(&step_key, &user);
475 }
476 }
477 user
478}
479
480fn compute_key_rc4(
483 password: &[u8],
484 o: &[u8],
485 p: i32,
486 id_first: &[u8],
487 r: i64,
488 length_bits: i64,
489 encrypt_metadata: bool,
490) -> Vec<u8> {
491 let n = rc4_key_len(r, length_bits);
492
493 let mut input = Vec::with_capacity(32 + 32 + 4 + id_first.len() + 4);
494 input.extend_from_slice(&pad_password(password));
496 let mut o32 = [0u8; 32];
498 let take = o.len().min(32);
499 o32[..take].copy_from_slice(&o[..take]);
500 input.extend_from_slice(&o32);
501 input.extend_from_slice(&(p as u32).to_le_bytes());
503 input.extend_from_slice(id_first);
505 if r >= 4 && !encrypt_metadata {
507 input.extend_from_slice(&[0xff, 0xff, 0xff, 0xff]);
508 }
509
510 let mut hash = md5(&input);
511 if r >= 3 {
513 for _ in 0..50 {
514 hash = md5(&hash[..n]);
515 }
516 }
517 hash[..n].to_vec()
518}
519
520fn compute_key_v5(dict: &PdfDict, r: i64, password: &[u8]) -> Option<Vec<u8>> {
530 let o = string_bytes(dict, "O");
531 let u = string_bytes(dict, "U");
532 let oe = string_bytes(dict, "OE");
533 let ue = string_bytes(dict, "UE");
534 let password = &password[..password.len().min(127)];
539
540 if u.len() >= 48 {
543 let (vsalt, ksalt) = (&u[32..40], &u[40..48]);
544 if hash_v5(r, password, vsalt, &[])[..] == u[..32] {
545 let ik = hash_v5(r, password, ksalt, &[]);
546 if let Some(key) = decrypt_file_key(&ik, &ue, "UE") {
547 return Some(key);
548 }
549 }
550 }
551 if o.len() >= 48 && u.len() >= 48 {
553 let u48 = &u[..48];
554 let (vsalt, ksalt) = (&o[32..40], &o[40..48]);
555 if hash_v5(r, password, vsalt, u48)[..] == o[..32] {
556 let ik = hash_v5(r, password, ksalt, u48);
557 if let Some(key) = decrypt_file_key(&ik, &oe, "OE") {
558 return Some(key);
559 }
560 }
561 }
562 tracing::warn!(
563 "V5/R{r} password validation failed (the PDF likely requires a password); \
564 document will not decrypt"
565 );
566 None
567}
568
569fn decrypt_file_key(intermediate: &[u8; 32], encrypted: &[u8], which: &str) -> Option<Vec<u8>> {
572 if encrypted.len() != 32 {
573 tracing::warn!(
574 "/{which} must be 32 bytes, got {}; document will not decrypt",
575 encrypted.len()
576 );
577 return None;
578 }
579 let mut buf = encrypted.to_vec();
580 if !cbc_decrypt_in_place(intermediate, &[0u8; 16], &mut buf) {
581 return None;
582 }
583 Some(buf)
584}
585
586fn hash_v5(r: i64, password: &[u8], salt: &[u8], udata: &[u8]) -> [u8; 32] {
589 let mut input = Vec::with_capacity(password.len() + salt.len() + udata.len());
590 input.extend_from_slice(password);
591 input.extend_from_slice(salt);
592 input.extend_from_slice(udata);
593 let initial: [u8; 32] = sha2::Sha256::digest(&input).into();
594 if r >= 6 {
595 hash_r6(initial, password, udata)
596 } else {
597 initial
598 }
599}
600
601fn hash_r6(initial: [u8; 32], password: &[u8], udata: &[u8]) -> [u8; 32] {
606 let mut k: Vec<u8> = initial.to_vec();
607 let mut e_last: u8 = 0;
608 let mut round: i64 = 0;
609 while round < 64 || i64::from(e_last) > round - 32 {
610 let mut k1 = Vec::with_capacity(64 * (password.len() + k.len() + udata.len()));
613 for _ in 0..64 {
614 k1.extend_from_slice(password);
615 k1.extend_from_slice(&k);
616 k1.extend_from_slice(udata);
617 }
618 let e = aes128_cbc_encrypt_nopad(&k[..16], &k[16..32], &k1);
619 e_last = *e.last().unwrap_or(&0);
620 let m = e[..16].iter().map(|&b| u32::from(b)).sum::<u32>() % 3;
621 k = match m {
622 0 => sha2::Sha256::digest(&e).to_vec(),
623 1 => sha2::Sha384::digest(&e).to_vec(),
624 _ => sha2::Sha512::digest(&e).to_vec(),
625 };
626 round += 1;
627 }
628 let mut out = [0u8; 32];
629 out.copy_from_slice(&k[..32]);
630 out
631}
632
633fn string_bytes(dict: &PdfDict, key: &str) -> Vec<u8> {
635 match dict.get(key) {
636 Some(PdfObject::String(s)) => s.0.clone(),
637 _ => Vec::new(),
638 }
639}
640
641fn aes_cbc_decrypt(key: &[u8], data: &[u8]) -> Vec<u8> {
651 if data.is_empty() {
652 return Vec::new();
653 }
654 if data.len() < 16 || !(data.len() - 16).is_multiple_of(16) {
655 tracing::warn!(
656 "AES-CBC payload length {} is not 16+16k; leaving data unmodified",
657 data.len()
658 );
659 return data.to_vec();
660 }
661 let (iv, ct) = data.split_at(16);
662 let mut buf = ct.to_vec();
663 if !cbc_decrypt_in_place(key, iv, &mut buf) {
664 tracing::warn!(
665 "invalid AES key length {}; leaving data unmodified",
666 key.len()
667 );
668 return data.to_vec();
669 }
670 strip_pkcs5_padding(buf)
671}
672
673fn strip_pkcs5_padding(mut buf: Vec<u8>) -> Vec<u8> {
675 let Some(&last) = buf.last() else { return buf };
676 let pad = last as usize;
677 if (1..=16).contains(&pad)
678 && pad <= buf.len()
679 && buf[buf.len() - pad..].iter().all(|&b| b == last)
680 {
681 buf.truncate(buf.len() - pad);
682 } else {
683 tracing::warn!("invalid PKCS#5 padding byte {last}; keeping unpadded data");
684 }
685 buf
686}
687
688fn cbc_decrypt_in_place(key: &[u8], iv: &[u8], buf: &mut [u8]) -> bool {
691 debug_assert_eq!(buf.len() % 16, 0);
692 match key.len() {
693 16 => {
694 let Ok(mut dec) = cbc::Decryptor::<aes::Aes128>::new_from_slices(key, iv) else {
695 return false;
696 };
697 for block in buf.chunks_exact_mut(16) {
698 dec.decrypt_block_mut(GenericArray::from_mut_slice(block));
699 }
700 true
701 }
702 32 => {
703 let Ok(mut dec) = cbc::Decryptor::<aes::Aes256>::new_from_slices(key, iv) else {
704 return false;
705 };
706 for block in buf.chunks_exact_mut(16) {
707 dec.decrypt_block_mut(GenericArray::from_mut_slice(block));
708 }
709 true
710 }
711 _ => false,
712 }
713}
714
715fn aes128_cbc_encrypt_nopad(key: &[u8], iv: &[u8], data: &[u8]) -> Vec<u8> {
718 debug_assert_eq!(data.len() % 16, 0);
719 let mut buf = data.to_vec();
720 let Ok(mut enc) = cbc::Encryptor::<aes::Aes128>::new_from_slices(key, iv) else {
721 return buf; };
723 for block in buf.chunks_exact_mut(16) {
724 enc.encrypt_block_mut(GenericArray::from_mut_slice(block));
725 }
726 buf
727}
728
729fn aes_cbc_encrypt(key: &[u8], data: &[u8]) -> Vec<u8> {
734 let mut iv = [0u8; 16];
735 use std::sync::atomic::{AtomicU64, Ordering};
740 static COUNTER: AtomicU64 = AtomicU64::new(0);
741 let nonce = COUNTER.fetch_add(1, Ordering::Relaxed);
742 let mut seed = Vec::with_capacity(data.len().min(256) + 16);
743 seed.extend_from_slice(&nonce.to_le_bytes());
744 seed.extend_from_slice(&(data.len() as u64).to_le_bytes());
745 seed.extend_from_slice(&data[..data.len().min(240)]);
746 iv.copy_from_slice(&md5(&seed));
747
748 let pad = 16 - (data.len() % 16);
749 let mut buf = Vec::with_capacity(data.len() + pad);
750 buf.extend_from_slice(data);
751 buf.extend(std::iter::repeat_n(pad as u8, pad));
752
753 let ok = match key.len() {
754 16 => {
755 if let Ok(mut enc) = cbc::Encryptor::<aes::Aes128>::new_from_slices(key, &iv) {
756 for block in buf.chunks_exact_mut(16) {
757 enc.encrypt_block_mut(GenericArray::from_mut_slice(block));
758 }
759 true
760 } else {
761 false
762 }
763 }
764 32 => {
765 if let Ok(mut enc) = cbc::Encryptor::<aes::Aes256>::new_from_slices(key, &iv) {
766 for block in buf.chunks_exact_mut(16) {
767 enc.encrypt_block_mut(GenericArray::from_mut_slice(block));
768 }
769 true
770 } else {
771 false
772 }
773 }
774 _ => false,
775 };
776 if !ok {
777 tracing::warn!(
778 "invalid AES key length {}; data left unencrypted",
779 key.len()
780 );
781 return data.to_vec();
782 }
783
784 let mut out = Vec::with_capacity(16 + buf.len());
785 out.extend_from_slice(&iv);
786 out.extend_from_slice(&buf);
787 out
788}
789
790fn rc4(key: &[u8], data: &[u8]) -> Vec<u8> {
797 if key.is_empty() {
798 return data.to_vec();
799 }
800 let mut s: [u8; 256] = [0; 256];
801 for (i, b) in s.iter_mut().enumerate() {
802 *b = i as u8;
803 }
804 let mut j: u8 = 0;
805 for i in 0..256 {
806 j = j.wrapping_add(s[i]).wrapping_add(key[i % key.len()]);
807 s.swap(i, j as usize);
808 }
809
810 let mut out = Vec::with_capacity(data.len());
811 let mut i: u8 = 0;
812 let mut j: u8 = 0;
813 for &byte in data {
814 i = i.wrapping_add(1);
815 j = j.wrapping_add(s[i as usize]);
816 s.swap(i as usize, j as usize);
817 let k = s[(s[i as usize].wrapping_add(s[j as usize])) as usize];
818 out.push(byte ^ k);
819 }
820 out
821}
822
823const MD5_S: [u32; 64] = [
829 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,
830 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,
831 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21,
832];
833
834const MD5_K: [u32; 64] = [
836 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501,
837 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821,
838 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8,
839 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a,
840 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70,
841 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05, 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665,
842 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1,
843 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391,
844];
845
846pub fn md5(data: &[u8]) -> [u8; 16] {
848 let mut a0: u32 = 0x67452301;
849 let mut b0: u32 = 0xefcdab89;
850 let mut c0: u32 = 0x98badcfe;
851 let mut d0: u32 = 0x10325476;
852
853 let bit_len = (data.len() as u64).wrapping_mul(8);
855 let mut msg = data.to_vec();
856 msg.push(0x80);
857 while msg.len() % 64 != 56 {
858 msg.push(0);
859 }
860 msg.extend_from_slice(&bit_len.to_le_bytes());
861
862 for chunk in msg.chunks_exact(64) {
863 let mut m = [0u32; 16];
864 for (i, word) in m.iter_mut().enumerate() {
865 *word = u32::from_le_bytes([
866 chunk[i * 4],
867 chunk[i * 4 + 1],
868 chunk[i * 4 + 2],
869 chunk[i * 4 + 3],
870 ]);
871 }
872
873 let (mut a, mut b, mut c, mut d) = (a0, b0, c0, d0);
874 for i in 0..64 {
875 let (f, g) = match i {
876 0..=15 => ((b & c) | (!b & d), i),
877 16..=31 => ((d & b) | (!d & c), (5 * i + 1) % 16),
878 32..=47 => (b ^ c ^ d, (3 * i + 5) % 16),
879 _ => (c ^ (b | !d), (7 * i) % 16),
880 };
881 let f = f.wrapping_add(a).wrapping_add(MD5_K[i]).wrapping_add(m[g]);
882 a = d;
883 d = c;
884 c = b;
885 b = b.wrapping_add(f.rotate_left(MD5_S[i]));
886 }
887
888 a0 = a0.wrapping_add(a);
889 b0 = b0.wrapping_add(b);
890 c0 = c0.wrapping_add(c);
891 d0 = d0.wrapping_add(d);
892 }
893
894 let mut out = [0u8; 16];
895 out[0..4].copy_from_slice(&a0.to_le_bytes());
896 out[4..8].copy_from_slice(&b0.to_le_bytes());
897 out[8..12].copy_from_slice(&c0.to_le_bytes());
898 out[12..16].copy_from_slice(&d0.to_le_bytes());
899 out
900}
901
902#[cfg(test)]
903mod tests {
904 use super::*;
905 use zpdf_core::{PdfName, PdfStream};
906
907 fn hex(bytes: &[u8]) -> String {
908 bytes.iter().map(|b| format!("{b:02x}")).collect()
909 }
910
911 fn unhex(s: &str) -> Vec<u8> {
912 (0..s.len())
913 .step_by(2)
914 .map(|i| u8::from_str_radix(&s[i..i + 2], 16).unwrap())
915 .collect()
916 }
917
918 fn aes256_cbc_encrypt_nopad(key: &[u8], iv: &[u8], data: &[u8]) -> Vec<u8> {
921 let mut buf = data.to_vec();
922 let mut enc = cbc::Encryptor::<aes::Aes256>::new_from_slices(key, iv).unwrap();
923 for block in buf.chunks_exact_mut(16) {
924 enc.encrypt_block_mut(GenericArray::from_mut_slice(block));
925 }
926 buf
927 }
928
929 fn content_stream_bytes(file: &crate::PdfFile) -> Vec<u8> {
931 let root = file.trailer.get_ref("Root").expect("trailer /Root");
932 let cat = file.resolve(root).expect("resolve catalog");
933 let pages_ref = cat.as_dict().unwrap().get_ref("Pages").unwrap();
934 let pages = file.resolve(pages_ref).expect("resolve pages");
935 let kids = pages.as_dict().unwrap().get_array("Kids").unwrap().to_vec();
936 let PdfObject::Ref(page_ref) = kids[0] else {
937 panic!("Kids[0] is not a reference")
938 };
939 let page = file.resolve(page_ref).expect("resolve page");
940 let contents_ref = page.as_dict().unwrap().get_ref("Contents").unwrap();
941 file.resolve_stream_data(contents_ref)
942 .expect("decode content stream")
943 }
944
945 #[test]
949 fn test4_rc4_key_derivation_oracle() {
950 let o = unhex("c5e5cd078ac4b56637f8a5d03a1ecd261ecf59fdcd8b50944ba1bb0e9e95ebfb");
951 let u = unhex("ffe4a8e86d2951800946f19d21089e1a71ca3d813608e586339bab72aa28206a");
952 let id0 = unhex("1a6dd6c3b3c1957a915bb98dbf691ce0");
953 let p: i32 = -64;
954
955 let key = compute_key_rc4(b"", &o, p, &id0, 2, 40, true);
957 assert_eq!(hex(&key), "b374aaeaf4", "file key (Algorithm 2)");
958
959 assert_eq!(rc4(&key, &PAD), u, "user-password validation (Algorithm 4)");
961 assert!(
962 validate_user_password(&key, &u, &id0, 2),
963 "validate_user_password should accept the correct R2 key"
964 );
965 let wrong = compute_key_rc4(b"", &o, p, &[], 2, 40, true);
967 assert!(
968 !validate_user_password(&wrong, &u, &id0, 2),
969 "validate_user_password should reject a wrong key"
970 );
971
972 let dec = Decryptor {
974 key,
975 stm_algo: Algo::Rc4,
976 str_algo: Algo::Rc4,
977 encrypt_id: None,
978 encrypt_metadata: true,
979 };
980 let objkey = dec.object_key(ObjectId(1652, 0), Algo::Rc4);
981 assert_eq!(
982 hex(&objkey),
983 "30dadd6463d5f9765abc",
984 "per-object key (Algorithm 1)"
985 );
986 }
987
988 #[test]
989 fn md5_known_answers() {
990 assert_eq!(hex(&md5(b"")), "d41d8cd98f00b204e9800998ecf8427e");
992 assert_eq!(hex(&md5(b"a")), "0cc175b9c0f1b6a831c399e269772661");
993 assert_eq!(hex(&md5(b"abc")), "900150983cd24fb0d6963f7d28e17f72");
994 assert_eq!(
995 hex(&md5(b"message digest")),
996 "f96b697d7cb7938d525a2f31aaf161d0"
997 );
998 assert_eq!(
999 hex(&md5(b"abcdefghijklmnopqrstuvwxyz")),
1000 "c3fcd3d76192e4007dfb496cca67e13b"
1001 );
1002 assert_eq!(
1004 hex(&md5(
1005 b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
1006 )),
1007 "d174ab98d277d9f5a5611c2c9f419d9f"
1008 );
1009 }
1010
1011 #[test]
1012 fn v4_rc4_key_length_from_crypt_filter() {
1013 let mut stdcf = PdfDict::new();
1016 stdcf.insert(PdfName::new("CFM"), PdfObject::Name(PdfName::new("V2")));
1017 stdcf.insert(PdfName::new("Length"), PdfObject::Integer(16)); let mut cf = PdfDict::new();
1019 cf.insert(PdfName::new("StdCF"), PdfObject::Dict(stdcf));
1020 let mut dict = PdfDict::new();
1021 dict.insert(PdfName::new("CF"), PdfObject::Dict(cf));
1022 dict.insert(PdfName::new("StmF"), PdfObject::Name(PdfName::new("StdCF")));
1023 dict.insert(PdfName::new("V"), PdfObject::Integer(4));
1024
1025 assert_eq!(key_length_bits(&dict, 4), 128);
1027 assert_eq!(algo_for_filter(&dict, "StdCF"), Algo::Rc4);
1029 assert_eq!(algo_for_filter(&dict, "Identity"), Algo::Identity);
1031 }
1032
1033 #[test]
1034 fn rc4_known_answers() {
1035 let ct = rc4(b"Key", b"Plaintext");
1037 assert_eq!(hex(&ct), "bbf316e8d940af0ad3");
1038 assert_eq!(rc4(b"Key", &ct), b"Plaintext");
1040
1041 let ct = rc4(b"Wiki", b"pedia");
1042 assert_eq!(hex(&ct), "1021bf0420");
1043
1044 let ct = rc4(b"Secret", b"Attack at dawn");
1045 assert_eq!(hex(&ct), "45a01f645fc35b383552544b9bf5");
1046 }
1047
1048 #[test]
1049 fn aes_cbc_known_answers() {
1050 let key = unhex("2b7e151628aed2a6abf7158809cf4f3c");
1052 let iv = unhex("000102030405060708090a0b0c0d0e0f");
1053 let pt = unhex("6bc1bee22e409f96e93d7e117393172a");
1054 let ct = aes128_cbc_encrypt_nopad(&key, &iv, &pt);
1055 assert_eq!(hex(&ct), "7649abac8119b246cee98e9b12e9197d");
1056 let mut buf = ct.clone();
1058 assert!(cbc_decrypt_in_place(&key, &iv, &mut buf));
1059 assert_eq!(buf, pt);
1060
1061 let key = unhex("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4");
1063 let ct256 = aes256_cbc_encrypt_nopad(&key, &iv, &pt);
1064 assert_eq!(hex(&ct256), "f58c4c04d6e5f1ba779eabfb5f7bfbd6");
1065 let mut buf = ct256.clone();
1066 assert!(cbc_decrypt_in_place(&key, &iv, &mut buf));
1067 assert_eq!(buf, pt);
1068 }
1069
1070 #[test]
1071 fn aes_iv_prefix_and_padding() {
1072 let key = unhex("000102030405060708090a0b0c0d0e0f");
1073 let iv = [0x42u8; 16];
1074 let plaintext = b"attack at dawn".to_vec(); let mut padded = plaintext.clone();
1078 padded.extend_from_slice(&[2, 2]);
1079 let mut payload = iv.to_vec();
1080 payload.extend_from_slice(&aes128_cbc_encrypt_nopad(&key, &iv, &padded));
1081 assert_eq!(aes_cbc_decrypt(&key, &payload), plaintext);
1082
1083 let mut bad = plaintext.clone();
1085 bad.extend_from_slice(&[2, 0]);
1086 let mut payload = iv.to_vec();
1087 payload.extend_from_slice(&aes128_cbc_encrypt_nopad(&key, &iv, &bad));
1088 assert_eq!(aes_cbc_decrypt(&key, &payload), bad);
1089
1090 assert_eq!(aes_cbc_decrypt(&key, &[1, 2, 3]), vec![1, 2, 3]);
1092 assert_eq!(
1093 aes_cbc_decrypt(&key, &payload[..17]),
1094 payload[..17].to_vec()
1095 );
1096 assert_eq!(aes_cbc_decrypt(&key, b""), Vec::<u8>::new());
1097 assert_eq!(aes_cbc_decrypt(&key, &iv), Vec::<u8>::new());
1099 }
1100
1101 #[test]
1105 fn v5_key_derivation_roundtrip() {
1106 for r in [5i64, 6] {
1107 let file_key = [0xA5u8; 32];
1108 let (uvsalt, uksalt) = ([0x11u8; 8], [0x22u8; 8]);
1109
1110 let mut u = hash_v5(r, b"", &uvsalt, &[]).to_vec();
1112 u.extend_from_slice(&uvsalt);
1113 u.extend_from_slice(&uksalt);
1114 let ik = hash_v5(r, b"", &uksalt, &[]);
1115 let ue = aes256_cbc_encrypt_nopad(&ik, &[0u8; 16], &file_key);
1116
1117 let (ovsalt, oksalt) = ([0x33u8; 8], [0x44u8; 8]);
1119 let mut o = hash_v5(r, b"", &ovsalt, &u[..48]).to_vec();
1120 o.extend_from_slice(&ovsalt);
1121 o.extend_from_slice(&oksalt);
1122 let oik = hash_v5(r, b"", &oksalt, &u[..48]);
1123 let oe = aes256_cbc_encrypt_nopad(&oik, &[0u8; 16], &file_key);
1124
1125 let mut dict = PdfDict::new();
1126 dict.insert(PdfName::new("U"), PdfObject::String(PdfString(u.clone())));
1127 dict.insert(PdfName::new("UE"), PdfObject::String(PdfString(ue)));
1128 dict.insert(PdfName::new("O"), PdfObject::String(PdfString(o.clone())));
1129 dict.insert(PdfName::new("OE"), PdfObject::String(PdfString(oe.clone())));
1130 assert_eq!(
1131 compute_key_v5(&dict, r, b"").as_deref(),
1132 Some(&file_key[..]),
1133 "user-password path, R{r}"
1134 );
1135
1136 let mut dict2 = PdfDict::new();
1141 dict2.insert(PdfName::new("U"), PdfObject::String(PdfString(u)));
1142 dict2.insert(PdfName::new("O"), PdfObject::String(PdfString(o)));
1143 dict2.insert(PdfName::new("OE"), PdfObject::String(PdfString(oe)));
1144 assert_eq!(
1145 compute_key_v5(&dict2, r, b"").as_deref(),
1146 Some(&file_key[..]),
1147 "owner-password fallback, R{r}"
1148 );
1149 }
1150 }
1151
1152 #[test]
1154 fn v4_identity_stream_filter_leaves_streams_alone() {
1155 let mut stdcf = PdfDict::new();
1156 stdcf.insert(PdfName::new("CFM"), PdfObject::Name(PdfName::new("V2")));
1157 stdcf.insert(PdfName::new("Length"), PdfObject::Integer(16));
1158 let mut cf = PdfDict::new();
1159 cf.insert(PdfName::new("StdCF"), PdfObject::Dict(stdcf));
1160 let mut dict = PdfDict::new();
1161 dict.insert(
1162 PdfName::new("Filter"),
1163 PdfObject::Name(PdfName::new("Standard")),
1164 );
1165 dict.insert(PdfName::new("V"), PdfObject::Integer(4));
1166 dict.insert(PdfName::new("R"), PdfObject::Integer(4));
1167 dict.insert(PdfName::new("CF"), PdfObject::Dict(cf));
1168 dict.insert(
1169 PdfName::new("StmF"),
1170 PdfObject::Name(PdfName::new("Identity")),
1171 );
1172 dict.insert(PdfName::new("StrF"), PdfObject::Name(PdfName::new("StdCF")));
1173
1174 let dec = match Decryptor::from_encrypt_dict(&dict, &[], None, b"") {
1175 BuildResult::Decryptor(d) => d,
1176 _ => panic!("decryptor"),
1177 };
1178 assert_eq!(dec.stm_algo, Algo::Identity);
1179 assert_eq!(dec.str_algo, Algo::Rc4);
1180
1181 let stream_data = b"stream payload".to_vec();
1182 let string_data = b"string payload".to_vec();
1183 let mut arr = PdfObject::Array(vec![
1184 PdfObject::Stream(PdfStream::new(PdfDict::new(), stream_data.clone())),
1185 PdfObject::String(PdfString(string_data.clone())),
1186 ]);
1187 dec.decrypt_object(&mut arr, ObjectId(9, 0));
1188 let PdfObject::Array(items) = &arr else {
1189 unreachable!()
1190 };
1191 let PdfObject::Stream(s) = &items[0] else {
1192 unreachable!()
1193 };
1194 assert_eq!(
1195 &s.data[..],
1196 &stream_data[..],
1197 "Identity /StmF must not touch streams"
1198 );
1199 let PdfObject::String(st) = &items[1] else {
1200 unreachable!()
1201 };
1202 assert_ne!(st.0, string_data, "/StrF StdCF must decrypt strings");
1203 assert_eq!(
1205 dec.decrypt_stream_bytes(ObjectId(9, 0), b"objstm"),
1206 b"objstm"
1207 );
1208 }
1209
1210 #[test]
1212 fn encrypt_metadata_false_skips_metadata_stream() {
1213 let dec = Decryptor {
1214 key: vec![1, 2, 3, 4, 5],
1215 stm_algo: Algo::Rc4,
1216 str_algo: Algo::Rc4,
1217 encrypt_id: None,
1218 encrypt_metadata: false,
1219 };
1220 let xmp = b"<x:xmpmeta/>".to_vec();
1221 let mut meta_dict = PdfDict::new();
1222 meta_dict.insert(
1223 PdfName::new("Type"),
1224 PdfObject::Name(PdfName::new("Metadata")),
1225 );
1226 let mut meta = PdfObject::Stream(PdfStream::new(meta_dict, xmp.clone()));
1227 dec.decrypt_object(&mut meta, ObjectId(7, 0));
1228 let PdfObject::Stream(s) = &meta else {
1229 unreachable!()
1230 };
1231 assert_eq!(
1232 &s.data[..],
1233 &xmp[..],
1234 "plaintext metadata must not be corrupted"
1235 );
1236
1237 let mut other = PdfObject::Stream(PdfStream::new(PdfDict::new(), xmp.clone()));
1239 dec.decrypt_object(&mut other, ObjectId(7, 0));
1240 let PdfObject::Stream(s) = &other else {
1241 unreachable!()
1242 };
1243 assert_ne!(&s.data[..], &xmp[..]);
1244 }
1245
1246 const AES_MARKER: &[u8] = b"(Hello AES zpdf fixture) Tj";
1252
1253 fn assert_fixture_decrypts(bytes: &[u8]) {
1254 let file = crate::PdfFile::parse(bytes.to_vec()).expect("parse encrypted fixture");
1255 let content = content_stream_bytes(&file);
1256 assert!(
1257 content.windows(AES_MARKER.len()).any(|w| w == AES_MARKER),
1258 "decrypted content stream should contain the known marker, got: {:?}",
1259 String::from_utf8_lossy(&content)
1260 );
1261 }
1262
1263 #[test]
1265 fn aesv2_r4_decrypts_end_to_end() {
1266 assert_fixture_decrypts(include_bytes!("../tests/fixtures/aesv2_r4.pdf"));
1267 }
1268
1269 #[test]
1271 fn aesv3_r5_decrypts_end_to_end() {
1272 assert_fixture_decrypts(include_bytes!("../tests/fixtures/aesv3_r5.pdf"));
1273 }
1274
1275 #[test]
1277 fn aesv3_r6_decrypts_end_to_end() {
1278 assert_fixture_decrypts(include_bytes!("../tests/fixtures/aesv3_r6.pdf"));
1279 }
1280
1281 fn hexstr(b: &[u8]) -> String {
1286 b.iter().map(|x| format!("{x:02x}")).collect()
1287 }
1288
1289 fn build_rc4_direct_encrypt_pdf(content_plain: &[u8]) -> Vec<u8> {
1292 let okey = md5(&PAD);
1295 let o = rc4(&okey[..5], &PAD);
1296 let id0: Vec<u8> = (0u8..16).collect();
1297 let p: i32 = -1;
1298 let key = compute_key_rc4(b"", &o, p, &id0, 2, 40, true);
1299 let u = rc4(&key, &PAD); let enc = Decryptor {
1303 key,
1304 stm_algo: Algo::Rc4,
1305 str_algo: Algo::Rc4,
1306 encrypt_id: None,
1307 encrypt_metadata: true,
1308 };
1309 let content_enc = enc.decrypt_stream_bytes(ObjectId(5, 0), content_plain);
1310
1311 let mut stream_obj = format!("<< /Length {} >>\nstream\n", content_enc.len()).into_bytes();
1312 stream_obj.extend_from_slice(&content_enc);
1313 stream_obj.extend_from_slice(b"\nendstream");
1314 let bodies: Vec<Vec<u8>> = vec![
1315 b"<< /Type /Catalog /Pages 2 0 R >>".to_vec(),
1316 b"<< /Type /Pages /Kids [3 0 R] /Count 1 >>".to_vec(),
1317 b"<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] /Contents 5 0 R >>".to_vec(),
1318 b"<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>".to_vec(),
1319 stream_obj,
1320 ];
1321
1322 let mut out: Vec<u8> = b"%PDF-1.4\n".to_vec();
1323 let mut offsets = Vec::new();
1324 for (i, body) in bodies.iter().enumerate() {
1325 offsets.push(out.len());
1326 out.extend_from_slice(format!("{} 0 obj\n", i + 1).as_bytes());
1327 out.extend_from_slice(body);
1328 out.extend_from_slice(b"\nendobj\n");
1329 }
1330 let xref_pos = out.len();
1331 out.extend_from_slice(b"xref\n0 6\n0000000000 65535 f \n");
1332 for off in &offsets {
1333 out.extend_from_slice(format!("{off:010} 00000 n \n").as_bytes());
1334 }
1335 out.extend_from_slice(
1336 format!(
1337 "trailer\n<< /Size 6 /Root 1 0 R /ID [<{id}> <{id}>] /Encrypt << /Filter \
1338 /Standard /V 1 /R 2 /Length 40 /O <{o}> /U <{u}> /P {p} >> >>\nstartxref\n\
1339 {xref_pos}\n%%EOF\n",
1340 id = hexstr(&id0),
1341 o = hexstr(&o),
1342 u = hexstr(&u),
1343 )
1344 .as_bytes(),
1345 );
1346 out
1347 }
1348
1349 #[test]
1352 fn rc4_direct_encrypt_dict_in_trailer() {
1353 let plain = b"BT /F1 12 Tf (direct encrypt dict) Tj ET";
1354 let pdf = build_rc4_direct_encrypt_pdf(plain);
1355 let file = crate::PdfFile::parse(pdf).expect("parse hand-built encrypted PDF");
1356 assert_eq!(content_stream_bytes(&file), plain);
1357 }
1358
1359 fn build_rc4_password_pdf(
1367 user_pw: &[u8],
1368 owner_pw: &[u8],
1369 content_plain: &[u8],
1370 omit_u: bool,
1371 ) -> Vec<u8> {
1372 let (r, bits, n) = (3i64, 128i64, 16usize);
1373 let id0: Vec<u8> = (0u8..16).collect();
1374 let p: i32 = -44;
1375
1376 let mut okey = md5(&pad_password(owner_pw));
1378 for _ in 0..50 {
1379 okey = md5(&okey[..n]);
1380 }
1381 let owner_key = &okey[..n];
1382 let mut o = pad_password(user_pw).to_vec();
1383 for i in 0..=19u8 {
1384 let step_key: Vec<u8> = owner_key.iter().map(|b| b ^ i).collect();
1385 o = rc4(&step_key, &o);
1386 }
1387
1388 let key = compute_key_rc4(user_pw, &o, p, &id0, r, bits, true);
1390
1391 let mut u_input = Vec::new();
1394 u_input.extend_from_slice(&PAD);
1395 u_input.extend_from_slice(&id0);
1396 let mut x = rc4(&key, &md5(&u_input));
1397 for i in 1..=19u8 {
1398 let step_key: Vec<u8> = key.iter().map(|b| b ^ i).collect();
1399 x = rc4(&step_key, &x);
1400 }
1401 let mut u = x;
1402 u.extend_from_slice(&[0u8; 16]);
1403
1404 let enc = Decryptor {
1405 key,
1406 stm_algo: Algo::Rc4,
1407 str_algo: Algo::Rc4,
1408 encrypt_id: None,
1409 encrypt_metadata: true,
1410 };
1411 let content_enc = enc.decrypt_stream_bytes(ObjectId(5, 0), content_plain);
1412
1413 let mut stream_obj = format!("<< /Length {} >>\nstream\n", content_enc.len()).into_bytes();
1414 stream_obj.extend_from_slice(&content_enc);
1415 stream_obj.extend_from_slice(b"\nendstream");
1416 let bodies: Vec<Vec<u8>> = vec![
1417 b"<< /Type /Catalog /Pages 2 0 R >>".to_vec(),
1418 b"<< /Type /Pages /Kids [3 0 R] /Count 1 >>".to_vec(),
1419 b"<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] /Contents 5 0 R >>".to_vec(),
1420 b"<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>".to_vec(),
1421 stream_obj,
1422 ];
1423
1424 let mut out: Vec<u8> = b"%PDF-1.6\n".to_vec();
1425 let mut offsets = Vec::new();
1426 for (i, body) in bodies.iter().enumerate() {
1427 offsets.push(out.len());
1428 out.extend_from_slice(format!("{} 0 obj\n", i + 1).as_bytes());
1429 out.extend_from_slice(body);
1430 out.extend_from_slice(b"\nendobj\n");
1431 }
1432 let xref_pos = out.len();
1433 out.extend_from_slice(b"xref\n0 6\n0000000000 65535 f \n");
1434 for off in &offsets {
1435 out.extend_from_slice(format!("{off:010} 00000 n \n").as_bytes());
1436 }
1437 let u_entry = if omit_u {
1438 String::new()
1439 } else {
1440 format!("/U <{}> ", hexstr(&u))
1441 };
1442 out.extend_from_slice(
1443 format!(
1444 "trailer\n<< /Size 6 /Root 1 0 R /ID [<{id}> <{id}>] /Encrypt << /Filter \
1445 /Standard /V 2 /R 3 /Length 128 /O <{o}> {u_entry}/P {p} >> >>\nstartxref\n\
1446 {xref_pos}\n%%EOF\n",
1447 id = hexstr(&id0),
1448 o = hexstr(&o),
1449 )
1450 .as_bytes(),
1451 );
1452 out
1453 }
1454
1455 #[test]
1456 fn user_password_decrypts() {
1457 let plain = b"BT (user password works) Tj ET";
1458 let pdf = build_rc4_password_pdf(b"secret", b"master", plain, false);
1459 let file =
1460 crate::PdfFile::parse_with_password(pdf, b"secret").expect("user password opens");
1461 assert_eq!(content_stream_bytes(&file), plain);
1462 }
1463
1464 #[test]
1465 fn owner_password_decrypts_via_recovery() {
1466 let plain = b"BT (owner password works) Tj ET";
1469 let pdf = build_rc4_password_pdf(b"secret", b"master", plain, false);
1470 let file =
1471 crate::PdfFile::parse_with_password(pdf, b"master").expect("owner password opens");
1472 assert_eq!(content_stream_bytes(&file), plain);
1473 }
1474
1475 #[test]
1476 fn wrong_password_is_rejected() {
1477 let pdf = build_rc4_password_pdf(b"secret", b"master", b"BT (x) Tj ET", false);
1478 match crate::PdfFile::parse_with_password(pdf, b"nope") {
1479 Err(zpdf_core::Error::WrongPassword) => {}
1480 Err(e) => panic!("expected WrongPassword, got error {e:?}"),
1481 Ok(_) => panic!("expected WrongPassword, but the document opened"),
1482 }
1483 }
1484
1485 #[test]
1486 fn empty_password_open_degrades_without_erroring() {
1487 let plain = b"BT (needs a password) Tj ET";
1491 let pdf = build_rc4_password_pdf(b"secret", b"master", plain, false);
1492 let file = crate::PdfFile::parse(pdf).expect("default open still succeeds");
1493 assert!(file.is_encrypted());
1494 assert_ne!(content_stream_bytes(&file), plain);
1495 }
1496
1497 #[test]
1498 fn missing_u_opens_best_effort_not_wrong_password() {
1499 let plain = b"BT (no /U to check) Tj ET";
1503 let pdf = build_rc4_password_pdf(b"secret", b"master", plain, true);
1504 let file =
1505 crate::PdfFile::parse_with_password(pdf, b"secret").expect("correct password opens");
1506 assert_eq!(content_stream_bytes(&file), plain);
1507
1508 let pdf = build_rc4_password_pdf(b"secret", b"master", plain, true);
1510 let file = crate::PdfFile::parse_with_password(pdf, b"nope")
1511 .expect("wrong password still opens best-effort (no /U to reject against)");
1512 assert_ne!(content_stream_bytes(&file), plain);
1513 }
1514}