Skip to main content

pdfboss_core/
crypt.rs

1//! Decryption for the Standard security handler (ISO 32000 §7.6) opened with
2//! the empty user password.
3//!
4//! Handles RC4 (`/V` 1–2, `/R` 2–3, 40–128-bit), AESV2 (`/V` 4, 128-bit
5//! AES-CBC) and AESV3 (`/V` 5, `/R` 5–6, 256-bit AES-CBC). Documents that need
6//! a real password are reported as encrypted-and-unsupported by the caller. The
7//! primitives — MD5, RC4, AES and the SHA-2 family — are implemented here from
8//! their published specifications so the crate needs no cryptographic
9//! dependency.
10//!
11//! This is low-level API: [`crate::Document`] applies it automatically when
12//! opening an encrypted file, and most callers never name [`Decryptor`]
13//! directly. It is public for code that drives object reads itself, as the
14//! asynchronous API does.
15
16use crate::object::{Dict, Object};
17
18/// Password padding string (ISO 32000 §7.6.3.3, Algorithm 2, step (a)).
19const PAD: [u8; 32] = [
20    0x28, 0xBF, 0x4E, 0x5E, 0x4E, 0x75, 0x8A, 0x41, 0x64, 0x00, 0x4E, 0x56, 0xFF, 0xFA, 0x01, 0x08,
21    0x2E, 0x2E, 0x00, 0xB6, 0xD0, 0x68, 0x3E, 0x80, 0x2F, 0x0C, 0xA9, 0xFE, 0x64, 0x53, 0x69, 0x7A,
22];
23
24/// Which cipher a configured [`Decryptor`] applies to strings and streams.
25#[derive(Clone, Copy, PartialEq)]
26enum Cipher {
27    /// RC4 stream cipher, per-object key (V1/V2, and V4 with `/CFM /V2`).
28    Rc4,
29    /// AES-128-CBC, per-object key with the `sAlT` suffix (V4, `/CFM /AESV2`).
30    Aesv2,
31    /// AES-256-CBC, the file key applied directly (V5, `/CFM /AESV3`).
32    Aesv3,
33}
34
35/// A configured Standard-handler decryptor for a document opened with the empty
36/// user password.
37#[derive(Clone)]
38pub struct Decryptor {
39    /// The file key (`n` bytes for RC4/AESV2, 32 for AESV3).
40    key: Vec<u8>,
41    cipher: Cipher,
42}
43
44impl Decryptor {
45    /// Builds a decryptor from the resolved `/Encrypt` dictionary and the first
46    /// `/ID` element, assuming the empty user password. Returns `None` when the
47    /// handler or its parameters are unsupported, or the empty password does
48    /// not open the file (so the caller can report it as unsupported).
49    ///
50    /// This is low-level: [`crate::Document`] configures decryption itself, and
51    /// a caller only reaches for this when driving object reads directly — as
52    /// the asynchronous API does.
53    ///
54    /// ```
55    /// use pdfboss_core::{Decryptor, Dict};
56    ///
57    /// // An empty dictionary names no handler, so there is nothing to build.
58    /// assert!(Decryptor::from_standard(&Dict::default(), &[]).is_none());
59    /// ```
60    pub fn from_standard(enc: &Dict, id0: &[u8]) -> Option<Decryptor> {
61        if enc.get_name("Filter").map(|n| n.0.as_str()) != Some("Standard") {
62            return None;
63        }
64        let v = enc.get_int("V").unwrap_or(0);
65        let r = enc.get_int("R").unwrap_or(0);
66        match (v, r) {
67            // RC4: V1 (40-bit) and V2 (up to 128-bit).
68            (1 | 2, 2 | 3) => {
69                let n = if v == 1 {
70                    5
71                } else {
72                    (enc.get_int("Length").unwrap_or(40) / 8).clamp(5, 16) as usize
73                };
74                let key = md5_file_key(enc, id0, r, n)?;
75                let u = enc.get("U").and_then(Object::as_str_bytes)?;
76                verify_user_password(&key, r, id0, u).then_some(Decryptor {
77                    key,
78                    cipher: Cipher::Rc4,
79                })
80            }
81            // V4: 128-bit key, cipher chosen by the standard crypt filter.
82            (4, 4) => {
83                let key = md5_file_key(enc, id0, r, 16)?;
84                let u = enc.get("U").and_then(Object::as_str_bytes)?;
85                if !verify_user_password(&key, r, id0, u) {
86                    return None;
87                }
88                let cipher = match crypt_filter_method(enc)?.as_str() {
89                    "AESV2" => Cipher::Aesv2,
90                    "V2" => Cipher::Rc4,
91                    _ => return None, // Identity or unknown
92                };
93                Some(Decryptor { key, cipher })
94            }
95            // V5: AES-256 with SHA-2-based key derivation.
96            (5, 5 | 6) => aesv3_key(enc, r).map(|key| Decryptor {
97                key,
98                cipher: Cipher::Aesv3,
99            }),
100            _ => None,
101        }
102    }
103
104    /// Decrypts one indirect object's strings and stream data in place. Objects
105    /// extracted from object streams are already plaintext and must not be
106    /// passed here.
107    ///
108    /// Low-level, like [`Decryptor::from_standard`]: [`crate::Document`] applies
109    /// this itself as it loads objects, and a caller only reaches for it when
110    /// driving object reads directly — as the asynchronous API does.
111    ///
112    /// ```
113    /// use pdfboss_core::{Decryptor, Object};
114    ///
115    /// // Reachable from outside the crate; exercising it needs an encrypted
116    /// // document, so this only pins the signature and the visibility.
117    /// let apply: fn(&Decryptor, &mut Object, u32, u16) = Decryptor::decrypt_object;
118    /// let _ = apply;
119    /// ```
120    pub fn decrypt_object(&self, obj: &mut Object, num: u32, gen: u16) {
121        let key = match self.cipher {
122            Cipher::Aesv3 => self.key.clone(), // one file key for every object
123            Cipher::Rc4 | Cipher::Aesv2 => self.object_key(num, gen),
124        };
125        decrypt_in_place(obj, &key, self.cipher);
126    }
127
128    /// Per-object key: `MD5(filekey ++ num[0..3] ++ gen[0..2] [++ "sAlT"])`
129    /// truncated to `min(n + 5, 16)` bytes (ISO 32000 §7.6.2, Algorithm 1). The
130    /// `sAlT` suffix is added for AES crypt filters.
131    fn object_key(&self, num: u32, gen: u16) -> Vec<u8> {
132        let mut input = Vec::with_capacity(self.key.len() + 9);
133        input.extend_from_slice(&self.key);
134        input.extend_from_slice(&num.to_le_bytes()[..3]);
135        input.extend_from_slice(&gen.to_le_bytes()[..2]);
136        if self.cipher == Cipher::Aesv2 {
137            input.extend_from_slice(b"sAlT");
138        }
139        let digest = md5(&input);
140        let n = (self.key.len() + 5).min(16);
141        digest[..n].to_vec()
142    }
143}
144
145/// Recursively decrypts every string and stream body reachable from `obj` with
146/// the per-object `key` under `cipher`.
147fn decrypt_in_place(obj: &mut Object, key: &[u8], cipher: Cipher) {
148    match obj {
149        Object::String(bytes) => *bytes = decrypt_bytes(cipher, key, bytes),
150        Object::Array(items) => items
151            .iter_mut()
152            .for_each(|it| decrypt_in_place(it, key, cipher)),
153        Object::Dict(dict) => dict
154            .values_mut()
155            .for_each(|v| decrypt_in_place(v, key, cipher)),
156        Object::Stream(stream) => {
157            stream
158                .dict
159                .values_mut()
160                .for_each(|v| decrypt_in_place(v, key, cipher));
161            stream.data = decrypt_bytes(cipher, key, &stream.data);
162        }
163        _ => {}
164    }
165}
166
167/// Applies `cipher` to one string or stream body with the given `key`.
168fn decrypt_bytes(cipher: Cipher, key: &[u8], data: &[u8]) -> Vec<u8> {
169    match cipher {
170        Cipher::Rc4 => rc4(key, data),
171        Cipher::Aesv2 | Cipher::Aesv3 => aes_cbc_decrypt(key, data),
172    }
173}
174
175/// The Standard stream crypt filter's method (`/CF` → `/StmF` → `/CFM`):
176/// `V2`, `AESV2`, or `Identity`.
177fn crypt_filter_method(enc: &Dict) -> Option<String> {
178    let stmf = enc
179        .get_name("StmF")
180        .map(|n| n.0.as_str())
181        .unwrap_or("StdCF");
182    let filter = enc.get_dict("CF")?.get_dict(stmf)?;
183    Some(filter.get_name("CFM")?.0.clone())
184}
185
186/// Algorithm 2: derive the RC4/AESV2 file key from the empty user password.
187fn md5_file_key(enc: &Dict, id0: &[u8], r: i64, n: usize) -> Option<Vec<u8>> {
188    let o = enc.get("O").and_then(Object::as_str_bytes)?;
189    if o.len() < 32 {
190        return None;
191    }
192    let p = enc.get_int("P")?;
193    let mut input = Vec::with_capacity(32 + 32 + 4 + id0.len() + 4);
194    input.extend_from_slice(&PAD); // padded empty password == the pad itself
195    input.extend_from_slice(&o[..32]);
196    input.extend_from_slice(&(p as i32 as u32).to_le_bytes()); // /P low 32 bits, LE
197    input.extend_from_slice(id0);
198    // Revision 4 with /EncryptMetadata false hashes an extra 0xFFFFFFFF.
199    if r >= 4 && enc.get("EncryptMetadata").and_then(Object::as_bool) == Some(false) {
200        input.extend_from_slice(&[0xFF, 0xFF, 0xFF, 0xFF]);
201    }
202    let mut digest = md5(&input);
203    if r >= 3 {
204        for _ in 0..50 {
205            digest = md5(&digest[..n]);
206        }
207    }
208    Some(digest[..n].to_vec())
209}
210
211/// Checks the empty user password by recomputing `/U` and comparing.
212fn verify_user_password(key: &[u8], r: i64, id0: &[u8], u: &[u8]) -> bool {
213    if r == 2 {
214        // Algorithm 4: U = RC4(key, PAD).
215        let computed = rc4(key, &PAD);
216        u.len() >= 32 && computed == u[..32]
217    } else {
218        // Algorithm 5: U = MD5(PAD ++ ID[0]) encrypted with 20 keyed RC4 passes.
219        let mut input = Vec::with_capacity(32 + id0.len());
220        input.extend_from_slice(&PAD);
221        input.extend_from_slice(id0);
222        let mut x = md5(&input).to_vec();
223        x = rc4(key, &x);
224        for i in 1u8..=19 {
225            let keyed: Vec<u8> = key.iter().map(|b| b ^ i).collect();
226            x = rc4(&keyed, &x);
227        }
228        // Only the first 16 bytes are defined; the rest of /U is arbitrary padding.
229        u.len() >= 16 && x[..16] == u[..16]
230    }
231}
232
233/// RC4 stream cipher (symmetric: the same call encrypts and decrypts).
234fn rc4(key: &[u8], data: &[u8]) -> Vec<u8> {
235    debug_assert!(!key.is_empty());
236    let mut s: [u8; 256] = core::array::from_fn(|i| i as u8);
237    let mut j = 0u8;
238    for i in 0..256 {
239        j = j.wrapping_add(s[i]).wrapping_add(key[i % key.len()]);
240        s.swap(i, j as usize);
241    }
242    let mut out = Vec::with_capacity(data.len());
243    let (mut i, mut j) = (0u8, 0u8);
244    for &byte in data {
245        i = i.wrapping_add(1);
246        j = j.wrapping_add(s[i as usize]);
247        s.swap(i as usize, j as usize);
248        let k = s[s[i as usize].wrapping_add(s[j as usize]) as usize];
249        out.push(byte ^ k);
250    }
251    out
252}
253
254// --- AES (FIPS-197) and CBC mode -----------------------------------------
255
256/// AES substitution box.
257#[rustfmt::skip]
258const AES_SBOX: [u8; 256] = [
259    0x63,0x7c,0x77,0x7b,0xf2,0x6b,0x6f,0xc5,0x30,0x01,0x67,0x2b,0xfe,0xd7,0xab,0x76,
260    0xca,0x82,0xc9,0x7d,0xfa,0x59,0x47,0xf0,0xad,0xd4,0xa2,0xaf,0x9c,0xa4,0x72,0xc0,
261    0xb7,0xfd,0x93,0x26,0x36,0x3f,0xf7,0xcc,0x34,0xa5,0xe5,0xf1,0x71,0xd8,0x31,0x15,
262    0x04,0xc7,0x23,0xc3,0x18,0x96,0x05,0x9a,0x07,0x12,0x80,0xe2,0xeb,0x27,0xb2,0x75,
263    0x09,0x83,0x2c,0x1a,0x1b,0x6e,0x5a,0xa0,0x52,0x3b,0xd6,0xb3,0x29,0xe3,0x2f,0x84,
264    0x53,0xd1,0x00,0xed,0x20,0xfc,0xb1,0x5b,0x6a,0xcb,0xbe,0x39,0x4a,0x4c,0x58,0xcf,
265    0xd0,0xef,0xaa,0xfb,0x43,0x4d,0x33,0x85,0x45,0xf9,0x02,0x7f,0x50,0x3c,0x9f,0xa8,
266    0x51,0xa3,0x40,0x8f,0x92,0x9d,0x38,0xf5,0xbc,0xb6,0xda,0x21,0x10,0xff,0xf3,0xd2,
267    0xcd,0x0c,0x13,0xec,0x5f,0x97,0x44,0x17,0xc4,0xa7,0x7e,0x3d,0x64,0x5d,0x19,0x73,
268    0x60,0x81,0x4f,0xdc,0x22,0x2a,0x90,0x88,0x46,0xee,0xb8,0x14,0xde,0x5e,0x0b,0xdb,
269    0xe0,0x32,0x3a,0x0a,0x49,0x06,0x24,0x5c,0xc2,0xd3,0xac,0x62,0x91,0x95,0xe4,0x79,
270    0xe7,0xc8,0x37,0x6d,0x8d,0xd5,0x4e,0xa9,0x6c,0x56,0xf4,0xea,0x65,0x7a,0xae,0x08,
271    0xba,0x78,0x25,0x2e,0x1c,0xa6,0xb4,0xc6,0xe8,0xdd,0x74,0x1f,0x4b,0xbd,0x8b,0x8a,
272    0x70,0x3e,0xb5,0x66,0x48,0x03,0xf6,0x0e,0x61,0x35,0x57,0xb9,0x86,0xc1,0x1d,0x9e,
273    0xe1,0xf8,0x98,0x11,0x69,0xd9,0x8e,0x94,0x9b,0x1e,0x87,0xe9,0xce,0x55,0x28,0xdf,
274    0x8c,0xa1,0x89,0x0d,0xbf,0xe6,0x42,0x68,0x41,0x99,0x2d,0x0f,0xb0,0x54,0xbb,0x16,
275];
276
277/// Round constants for key expansion (`RCON[j]` used when `i % Nk == 0`).
278const AES_RCON: [u8; 11] = [
279    0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36,
280];
281
282/// The inverse S-box, derived once from `AES_SBOX`.
283fn aes_inv_sbox() -> [u8; 256] {
284    let mut inv = [0u8; 256];
285    for (i, &s) in AES_SBOX.iter().enumerate() {
286        inv[s as usize] = i as u8;
287    }
288    inv
289}
290
291/// Multiplies two elements of GF(2^8) with the AES reduction polynomial.
292fn gmul(mut a: u8, mut b: u8) -> u8 {
293    let mut p = 0u8;
294    for _ in 0..8 {
295        if b & 1 != 0 {
296            p ^= a;
297        }
298        let hi = a & 0x80;
299        a <<= 1;
300        if hi != 0 {
301            a ^= 0x1b;
302        }
303        b >>= 1;
304    }
305    p
306}
307
308/// Expands a 16- or 32-byte key into `Nr + 1` round keys (state is stored
309/// column-major, so byte `r + 4c` is row `r`, column `c`).
310fn aes_expand_key(key: &[u8]) -> Vec<[u8; 16]> {
311    let nk = key.len() / 4; // 4 (AES-128) or 8 (AES-256)
312    let nr = nk + 6;
313    let total = 4 * (nr + 1);
314    let mut w: Vec<[u8; 4]> = Vec::with_capacity(total);
315    for i in 0..nk {
316        w.push([key[4 * i], key[4 * i + 1], key[4 * i + 2], key[4 * i + 3]]);
317    }
318    for i in nk..total {
319        let mut t = w[i - 1];
320        if i.is_multiple_of(nk) {
321            t = [t[1], t[2], t[3], t[0]]; // RotWord
322            for b in &mut t {
323                *b = AES_SBOX[*b as usize]; // SubWord
324            }
325            t[0] ^= AES_RCON[i / nk];
326        } else if nk > 6 && i % nk == 4 {
327            for b in &mut t {
328                *b = AES_SBOX[*b as usize];
329            }
330        }
331        let prev = w[i - nk];
332        w.push([
333            prev[0] ^ t[0],
334            prev[1] ^ t[1],
335            prev[2] ^ t[2],
336            prev[3] ^ t[3],
337        ]);
338    }
339    (0..=nr)
340        .map(|round| {
341            let mut rk = [0u8; 16];
342            for c in 0..4 {
343                rk[4 * c..4 * c + 4].copy_from_slice(&w[4 * round + c]);
344            }
345            rk
346        })
347        .collect()
348}
349
350fn add_round_key(s: &mut [u8; 16], rk: &[u8; 16]) {
351    for (b, k) in s.iter_mut().zip(rk) {
352        *b ^= k;
353    }
354}
355
356fn shift_rows(s: &mut [u8; 16]) {
357    let o = *s;
358    for r in 1..4 {
359        for c in 0..4 {
360            s[r + 4 * c] = o[r + 4 * ((c + r) % 4)];
361        }
362    }
363}
364
365fn inv_shift_rows(s: &mut [u8; 16]) {
366    let o = *s;
367    for r in 1..4 {
368        for c in 0..4 {
369            s[r + 4 * c] = o[r + 4 * ((c + 4 - r) % 4)];
370        }
371    }
372}
373
374fn mix_columns(s: &mut [u8; 16]) {
375    for c in 0..4 {
376        let i = 4 * c;
377        let (a0, a1, a2, a3) = (s[i], s[i + 1], s[i + 2], s[i + 3]);
378        s[i] = gmul(a0, 2) ^ gmul(a1, 3) ^ a2 ^ a3;
379        s[i + 1] = a0 ^ gmul(a1, 2) ^ gmul(a2, 3) ^ a3;
380        s[i + 2] = a0 ^ a1 ^ gmul(a2, 2) ^ gmul(a3, 3);
381        s[i + 3] = gmul(a0, 3) ^ a1 ^ a2 ^ gmul(a3, 2);
382    }
383}
384
385fn inv_mix_columns(s: &mut [u8; 16]) {
386    for c in 0..4 {
387        let i = 4 * c;
388        let (a0, a1, a2, a3) = (s[i], s[i + 1], s[i + 2], s[i + 3]);
389        s[i] = gmul(a0, 14) ^ gmul(a1, 11) ^ gmul(a2, 13) ^ gmul(a3, 9);
390        s[i + 1] = gmul(a0, 9) ^ gmul(a1, 14) ^ gmul(a2, 11) ^ gmul(a3, 13);
391        s[i + 2] = gmul(a0, 13) ^ gmul(a1, 9) ^ gmul(a2, 14) ^ gmul(a3, 11);
392        s[i + 3] = gmul(a0, 11) ^ gmul(a1, 13) ^ gmul(a2, 9) ^ gmul(a3, 14);
393    }
394}
395
396fn aes_encrypt_block(s: &mut [u8; 16], rks: &[[u8; 16]]) {
397    let nr = rks.len() - 1;
398    add_round_key(s, &rks[0]);
399    for rk in &rks[1..nr] {
400        s.iter_mut().for_each(|b| *b = AES_SBOX[*b as usize]);
401        shift_rows(s);
402        mix_columns(s);
403        add_round_key(s, rk);
404    }
405    s.iter_mut().for_each(|b| *b = AES_SBOX[*b as usize]);
406    shift_rows(s);
407    add_round_key(s, &rks[nr]);
408}
409
410fn aes_decrypt_block(s: &mut [u8; 16], rks: &[[u8; 16]], inv_sbox: &[u8; 256]) {
411    let nr = rks.len() - 1;
412    add_round_key(s, &rks[nr]);
413    for rk in rks[1..nr].iter().rev() {
414        inv_shift_rows(s);
415        s.iter_mut().for_each(|b| *b = inv_sbox[*b as usize]);
416        add_round_key(s, rk);
417        inv_mix_columns(s);
418    }
419    inv_shift_rows(s);
420    s.iter_mut().for_each(|b| *b = inv_sbox[*b as usize]);
421    add_round_key(s, &rks[0]);
422}
423
424/// AES-CBC decryption of whole blocks (no IV prefix, no padding removal).
425/// Returns an empty vector when the input is not a positive multiple of 16.
426fn aes_cbc_decrypt_blocks(key: &[u8], iv: &[u8], data: &[u8]) -> Vec<u8> {
427    if data.is_empty() || !data.len().is_multiple_of(16) || iv.len() < 16 {
428        return Vec::new();
429    }
430    let rks = aes_expand_key(key);
431    let inv_sbox = aes_inv_sbox();
432    let mut prev = [0u8; 16];
433    prev.copy_from_slice(&iv[..16]);
434    let mut out = Vec::with_capacity(data.len());
435    for chunk in data.chunks_exact(16) {
436        let mut block = [0u8; 16];
437        block.copy_from_slice(chunk);
438        let cipher = block;
439        aes_decrypt_block(&mut block, &rks, &inv_sbox);
440        for (b, p) in block.iter_mut().zip(&prev) {
441            *b ^= p;
442        }
443        out.extend_from_slice(&block);
444        prev = cipher;
445    }
446    out
447}
448
449/// AES-CBC encryption of whole blocks (no IV prefix, no padding). Used only by
450/// the R6 key-derivation hash.
451fn aes_cbc_encrypt_blocks(key: &[u8], iv: &[u8], data: &[u8]) -> Vec<u8> {
452    let rks = aes_expand_key(key);
453    let mut prev = [0u8; 16];
454    prev.copy_from_slice(&iv[..16]);
455    let mut out = Vec::with_capacity(data.len());
456    for chunk in data.chunks_exact(16) {
457        let mut block = [0u8; 16];
458        for ((b, c), p) in block.iter_mut().zip(chunk).zip(&prev) {
459            *b = c ^ p;
460        }
461        aes_encrypt_block(&mut block, &rks);
462        out.extend_from_slice(&block);
463        prev = block;
464    }
465    out
466}
467
468/// Decrypts a PDF AES value: the first 16 bytes are the IV, the rest is
469/// CBC-encrypted with PKCS#7 padding. Malformed input yields empty output
470/// rather than garbage.
471fn aes_cbc_decrypt(key: &[u8], data: &[u8]) -> Vec<u8> {
472    if data.len() < 16 {
473        return Vec::new();
474    }
475    let (iv, ct) = data.split_at(16);
476    let mut out = aes_cbc_decrypt_blocks(key, iv, ct);
477    strip_pkcs7(&mut out);
478    out
479}
480
481/// Removes PKCS#7 padding in place if present and well-formed.
482fn strip_pkcs7(data: &mut Vec<u8>) {
483    let Some(&pad) = data.last() else {
484        return;
485    };
486    let pad = pad as usize;
487    if (1..=16).contains(&pad) && pad <= data.len() {
488        let start = data.len() - pad;
489        if data[start..].iter().all(|&b| b as usize == pad) {
490            data.truncate(start);
491        }
492    }
493}
494
495// --- SHA-2 (FIPS 180-4): 256, 512 and 384 --------------------------------
496
497#[rustfmt::skip]
498const SHA256_K: [u32; 64] = [
499    0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
500    0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
501    0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
502    0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
503    0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
504    0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
505    0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
506    0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
507];
508
509fn sha256(input: &[u8]) -> [u8; 32] {
510    let mut h: [u32; 8] = [
511        0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab,
512        0x5be0cd19,
513    ];
514    let mut msg = input.to_vec();
515    let bitlen = (input.len() as u64).wrapping_mul(8);
516    msg.push(0x80);
517    while msg.len() % 64 != 56 {
518        msg.push(0);
519    }
520    msg.extend_from_slice(&bitlen.to_be_bytes());
521
522    for chunk in msg.chunks_exact(64) {
523        let mut w = [0u32; 64];
524        for (word, bytes) in w.iter_mut().zip(chunk.chunks_exact(4)) {
525            *word = u32::from_be_bytes(bytes.try_into().unwrap());
526        }
527        for i in 16..64 {
528            let s0 = w[i - 15].rotate_right(7) ^ w[i - 15].rotate_right(18) ^ (w[i - 15] >> 3);
529            let s1 = w[i - 2].rotate_right(17) ^ w[i - 2].rotate_right(19) ^ (w[i - 2] >> 10);
530            w[i] = w[i - 16]
531                .wrapping_add(s0)
532                .wrapping_add(w[i - 7])
533                .wrapping_add(s1);
534        }
535        let [mut a, mut b, mut c, mut d, mut e, mut f, mut g, mut hh] = h;
536        for (k, wi) in SHA256_K.iter().zip(&w) {
537            let s1 = e.rotate_right(6) ^ e.rotate_right(11) ^ e.rotate_right(25);
538            let ch = (e & f) ^ ((!e) & g);
539            let t1 = hh
540                .wrapping_add(s1)
541                .wrapping_add(ch)
542                .wrapping_add(*k)
543                .wrapping_add(*wi);
544            let s0 = a.rotate_right(2) ^ a.rotate_right(13) ^ a.rotate_right(22);
545            let maj = (a & b) ^ (a & c) ^ (b & c);
546            let t2 = s0.wrapping_add(maj);
547            hh = g;
548            g = f;
549            f = e;
550            e = d.wrapping_add(t1);
551            d = c;
552            c = b;
553            b = a;
554            a = t1.wrapping_add(t2);
555        }
556        for (hv, v) in h.iter_mut().zip([a, b, c, d, e, f, g, hh]) {
557            *hv = hv.wrapping_add(v);
558        }
559    }
560
561    let mut out = [0u8; 32];
562    for (i, word) in h.iter().enumerate() {
563        out[4 * i..4 * i + 4].copy_from_slice(&word.to_be_bytes());
564    }
565    out
566}
567
568#[rustfmt::skip]
569const SHA512_K: [u64; 80] = [
570    0x428a2f98d728ae22, 0x7137449123ef65cd, 0xb5c0fbcfec4d3b2f, 0xe9b5dba58189dbbc,
571    0x3956c25bf348b538, 0x59f111f1b605d019, 0x923f82a4af194f9b, 0xab1c5ed5da6d8118,
572    0xd807aa98a3030242, 0x12835b0145706fbe, 0x243185be4ee4b28c, 0x550c7dc3d5ffb4e2,
573    0x72be5d74f27b896f, 0x80deb1fe3b1696b1, 0x9bdc06a725c71235, 0xc19bf174cf692694,
574    0xe49b69c19ef14ad2, 0xefbe4786384f25e3, 0x0fc19dc68b8cd5b5, 0x240ca1cc77ac9c65,
575    0x2de92c6f592b0275, 0x4a7484aa6ea6e483, 0x5cb0a9dcbd41fbd4, 0x76f988da831153b5,
576    0x983e5152ee66dfab, 0xa831c66d2db43210, 0xb00327c898fb213f, 0xbf597fc7beef0ee4,
577    0xc6e00bf33da88fc2, 0xd5a79147930aa725, 0x06ca6351e003826f, 0x142929670a0e6e70,
578    0x27b70a8546d22ffc, 0x2e1b21385c26c926, 0x4d2c6dfc5ac42aed, 0x53380d139d95b3df,
579    0x650a73548baf63de, 0x766a0abb3c77b2a8, 0x81c2c92e47edaee6, 0x92722c851482353b,
580    0xa2bfe8a14cf10364, 0xa81a664bbc423001, 0xc24b8b70d0f89791, 0xc76c51a30654be30,
581    0xd192e819d6ef5218, 0xd69906245565a910, 0xf40e35855771202a, 0x106aa07032bbd1b8,
582    0x19a4c116b8d2d0c8, 0x1e376c085141ab53, 0x2748774cdf8eeb99, 0x34b0bcb5e19b48a8,
583    0x391c0cb3c5c95a63, 0x4ed8aa4ae3418acb, 0x5b9cca4f7763e373, 0x682e6ff3d6b2b8a3,
584    0x748f82ee5defb2fc, 0x78a5636f43172f60, 0x84c87814a1f0ab72, 0x8cc702081a6439ec,
585    0x90befffa23631e28, 0xa4506cebde82bde9, 0xbef9a3f7b2c67915, 0xc67178f2e372532b,
586    0xca273eceea26619c, 0xd186b8c721c0c207, 0xeada7dd6cde0eb1e, 0xf57d4f7fee6ed178,
587    0x06f067aa72176fba, 0x0a637dc5a2c898a6, 0x113f9804bef90dae, 0x1b710b35131c471b,
588    0x28db77f523047d84, 0x32caab7b40c72493, 0x3c9ebe0a15c9bebc, 0x431d67c49c100d4c,
589    0x4cc5d4becb3e42b6, 0x597f299cfc657e2a, 0x5fcb6fab3ad6faec, 0x6c44198c4a475817,
590];
591
592fn sha512_core(input: &[u8], mut h: [u64; 8]) -> [u64; 8] {
593    let mut msg = input.to_vec();
594    let bitlen = (input.len() as u128).wrapping_mul(8);
595    msg.push(0x80);
596    while msg.len() % 128 != 112 {
597        msg.push(0);
598    }
599    msg.extend_from_slice(&bitlen.to_be_bytes());
600
601    for chunk in msg.chunks_exact(128) {
602        let mut w = [0u64; 80];
603        for (word, bytes) in w.iter_mut().zip(chunk.chunks_exact(8)) {
604            *word = u64::from_be_bytes(bytes.try_into().unwrap());
605        }
606        for i in 16..80 {
607            let s0 = w[i - 15].rotate_right(1) ^ w[i - 15].rotate_right(8) ^ (w[i - 15] >> 7);
608            let s1 = w[i - 2].rotate_right(19) ^ w[i - 2].rotate_right(61) ^ (w[i - 2] >> 6);
609            w[i] = w[i - 16]
610                .wrapping_add(s0)
611                .wrapping_add(w[i - 7])
612                .wrapping_add(s1);
613        }
614        let [mut a, mut b, mut c, mut d, mut e, mut f, mut g, mut hh] = h;
615        for (k, wi) in SHA512_K.iter().zip(&w) {
616            let s1 = e.rotate_right(14) ^ e.rotate_right(18) ^ e.rotate_right(41);
617            let ch = (e & f) ^ ((!e) & g);
618            let t1 = hh
619                .wrapping_add(s1)
620                .wrapping_add(ch)
621                .wrapping_add(*k)
622                .wrapping_add(*wi);
623            let s0 = a.rotate_right(28) ^ a.rotate_right(34) ^ a.rotate_right(39);
624            let maj = (a & b) ^ (a & c) ^ (b & c);
625            let t2 = s0.wrapping_add(maj);
626            hh = g;
627            g = f;
628            f = e;
629            e = d.wrapping_add(t1);
630            d = c;
631            c = b;
632            b = a;
633            a = t1.wrapping_add(t2);
634        }
635        for (hv, v) in h.iter_mut().zip([a, b, c, d, e, f, g, hh]) {
636            *hv = hv.wrapping_add(v);
637        }
638    }
639    h
640}
641
642fn sha512(input: &[u8]) -> Vec<u8> {
643    let h = sha512_core(
644        input,
645        [
646            0x6a09e667f3bcc908,
647            0xbb67ae8584caa73b,
648            0x3c6ef372fe94f82b,
649            0xa54ff53a5f1d36f1,
650            0x510e527fade682d1,
651            0x9b05688c2b3e6c1f,
652            0x1f83d9abfb41bd6b,
653            0x5be0cd19137e2179,
654        ],
655    );
656    h.iter().flat_map(|w| w.to_be_bytes()).collect()
657}
658
659fn sha384(input: &[u8]) -> Vec<u8> {
660    let h = sha512_core(
661        input,
662        [
663            0xcbbb9d5dc1059ed8,
664            0x629a292a367cd507,
665            0x9159015a3070dd17,
666            0x152fecd8f70e5939,
667            0x67332667ffc00b31,
668            0x8eb44a8768581511,
669            0xdb0c2e0d64f98fa7,
670            0x47b5481dbefa4fa4,
671        ],
672    );
673    h.iter().take(6).flat_map(|w| w.to_be_bytes()).collect()
674}
675
676/// Recovers the AES-256 file key for the empty user password (ISO 32000-2
677/// §7.6.4.3.3, Algorithm 2.A) for revisions 5 and 6.
678fn aesv3_key(enc: &Dict, r: i64) -> Option<Vec<u8>> {
679    let u = enc.get("U").and_then(Object::as_str_bytes)?;
680    let ue = enc.get("UE").and_then(Object::as_str_bytes)?;
681    if u.len() < 48 || ue.len() < 32 {
682        return None;
683    }
684    let validation_salt = &u[32..40];
685    let key_salt = &u[40..48];
686    let pw: &[u8] = b""; // empty user password
687    if hash_2b(r, pw, validation_salt, &[])[..32] != u[..32] {
688        return None; // empty password does not open the file
689    }
690    let intermediate = hash_2b(r, pw, key_salt, &[]);
691    let file_key = aes_cbc_decrypt_blocks(&intermediate, &[0u8; 16], &ue[..32]);
692    (file_key.len() == 32).then_some(file_key)
693}
694
695/// The revision-6 password hash (ISO 32000-2, Algorithm 2.B); a plain SHA-256
696/// for revision 5.
697fn hash_2b(r: i64, password: &[u8], salt: &[u8], udata: &[u8]) -> Vec<u8> {
698    let mut seed = Vec::with_capacity(password.len() + salt.len() + udata.len());
699    seed.extend_from_slice(password);
700    seed.extend_from_slice(salt);
701    seed.extend_from_slice(udata);
702    let mut k = sha256(&seed).to_vec();
703    if r < 6 {
704        return k; // revision 5: a single SHA-256
705    }
706    let mut round = 0usize;
707    loop {
708        let mut k1 = Vec::with_capacity(64 * (password.len() + k.len() + udata.len()));
709        for _ in 0..64 {
710            k1.extend_from_slice(password);
711            k1.extend_from_slice(&k);
712            k1.extend_from_slice(udata);
713        }
714        let e = aes_cbc_encrypt_blocks(&k[..16], &k[16..32], &k1);
715        let modulus = e[..16].iter().map(|&b| u32::from(b)).sum::<u32>() % 3;
716        k = match modulus {
717            0 => sha256(&e).to_vec(),
718            1 => sha384(&e),
719            _ => sha512(&e),
720        };
721        round += 1;
722        if round >= 64 && usize::from(*e.last().unwrap()) <= round - 32 {
723            break;
724        }
725    }
726    k.truncate(32);
727    k
728}
729
730/// Per-round left-rotation amounts (RFC 1321).
731#[rustfmt::skip]
732const MD5_S: [u32; 64] = [
733    7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22,
734    5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20,
735    4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23,
736    6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21,
737];
738
739/// Per-round additive constants `floor(2^32 * abs(sin(i + 1)))` (RFC 1321).
740#[rustfmt::skip]
741const MD5_K: [u32; 64] = [
742    0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501,
743    0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821,
744    0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8,
745    0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a,
746    0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70,
747    0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05, 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665,
748    0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1,
749    0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391,
750];
751
752/// One-shot MD5 (RFC 1321). Sufficient for the small key-derivation inputs; not
753/// a streaming API.
754fn md5(input: &[u8]) -> [u8; 16] {
755    let (mut a0, mut b0, mut c0, mut d0) = (
756        0x6745_2301u32,
757        0xefcd_ab89u32,
758        0x98ba_dcfeu32,
759        0x1032_5476u32,
760    );
761
762    let mut msg = input.to_vec();
763    let bitlen = (input.len() as u64).wrapping_mul(8);
764    msg.push(0x80);
765    while msg.len() % 64 != 56 {
766        msg.push(0);
767    }
768    msg.extend_from_slice(&bitlen.to_le_bytes());
769
770    for chunk in msg.chunks_exact(64) {
771        let mut m = [0u32; 16];
772        for (word, bytes) in m.iter_mut().zip(chunk.chunks_exact(4)) {
773            *word = u32::from_le_bytes(bytes.try_into().unwrap());
774        }
775        let (mut a, mut b, mut c, mut d) = (a0, b0, c0, d0);
776        for i in 0..64 {
777            let (f, g) = match i {
778                0..=15 => ((b & c) | (!b & d), i),
779                16..=31 => ((d & b) | (!d & c), (5 * i + 1) % 16),
780                32..=47 => (b ^ c ^ d, (3 * i + 5) % 16),
781                _ => (c ^ (b | !d), (7 * i) % 16),
782            };
783            let f = f.wrapping_add(a).wrapping_add(MD5_K[i]).wrapping_add(m[g]);
784            a = d;
785            d = c;
786            c = b;
787            b = b.wrapping_add(f.rotate_left(MD5_S[i]));
788        }
789        a0 = a0.wrapping_add(a);
790        b0 = b0.wrapping_add(b);
791        c0 = c0.wrapping_add(c);
792        d0 = d0.wrapping_add(d);
793    }
794
795    let mut out = [0u8; 16];
796    out[0..4].copy_from_slice(&a0.to_le_bytes());
797    out[4..8].copy_from_slice(&b0.to_le_bytes());
798    out[8..12].copy_from_slice(&c0.to_le_bytes());
799    out[12..16].copy_from_slice(&d0.to_le_bytes());
800    out
801}
802
803#[cfg(test)]
804mod tests {
805    use super::*;
806    use crate::object::Name;
807
808    fn hex(bytes: &[u8]) -> String {
809        bytes.iter().map(|b| format!("{b:02x}")).collect()
810    }
811
812    #[test]
813    fn md5_known_vectors() {
814        assert_eq!(hex(&md5(b"")), "d41d8cd98f00b204e9800998ecf8427e");
815        assert_eq!(hex(&md5(b"abc")), "900150983cd24fb0d6963f7d28e17f72");
816        assert_eq!(
817            hex(&md5(b"The quick brown fox jumps over the lazy dog")),
818            "9e107d9d372bb6826bd81d3542a419d6"
819        );
820    }
821
822    #[test]
823    fn md5_spans_block_boundary() {
824        // 56 bytes forces a second padded block.
825        let input = [b'a'; 56];
826        assert_eq!(hex(&md5(&input)), "3b0c8ac703f828b04c6c197006d17218");
827    }
828
829    #[test]
830    fn rc4_known_vector() {
831        // Classic RC4 test vector: key "Key", plaintext "Plaintext".
832        let ct = rc4(b"Key", b"Plaintext");
833        assert_eq!(hex(&ct), "bbf316e8d940af0ad3");
834        // Symmetric: decrypting the ciphertext returns the plaintext.
835        assert_eq!(rc4(b"Key", &ct), b"Plaintext");
836    }
837
838    // --- End-to-end fixture: build a V2/R3 (128-bit RC4) file encrypted under
839    // the empty password, then confirm the loader transparently decrypts it. ---
840
841    const N: usize = 16; // 128-bit key
842    const P: i32 = -44;
843    const ID0: &[u8] = b"0123456789abcdef";
844
845    /// `/O` for empty owner and user passwords (Algorithm 3, R3).
846    fn owner_entry() -> Vec<u8> {
847        let mut d = md5(&PAD);
848        for _ in 0..50 {
849            d = md5(&d[..N]);
850        }
851        let rc4key = d[..N].to_vec();
852        let mut o = rc4(&rc4key, &PAD);
853        for i in 1u8..=19 {
854            let k: Vec<u8> = rc4key.iter().map(|b| b ^ i).collect();
855            o = rc4(&k, &o);
856        }
857        o
858    }
859
860    /// File key from `/O` for the empty user password (Algorithm 2, R3).
861    fn file_key(o: &[u8]) -> Vec<u8> {
862        let mut input = Vec::new();
863        input.extend_from_slice(&PAD);
864        input.extend_from_slice(o);
865        input.extend_from_slice(&(P as u32).to_le_bytes());
866        input.extend_from_slice(ID0);
867        let mut d = md5(&input);
868        for _ in 0..50 {
869            d = md5(&d[..N]);
870        }
871        d[..N].to_vec()
872    }
873
874    /// `/U` for the empty user password (Algorithm 5, R3).
875    fn user_entry(key: &[u8]) -> Vec<u8> {
876        let mut input = Vec::new();
877        input.extend_from_slice(&PAD);
878        input.extend_from_slice(ID0);
879        let mut x = md5(&input).to_vec();
880        x = rc4(key, &x);
881        for i in 1u8..=19 {
882            let k: Vec<u8> = key.iter().map(|b| b ^ i).collect();
883            x = rc4(&k, &x);
884        }
885        x.resize(32, 0); // trailing padding is arbitrary
886        x
887    }
888
889    fn obj_key(key: &[u8], num: u32, gen: u16) -> Vec<u8> {
890        let mut input = key.to_vec();
891        input.extend_from_slice(&num.to_le_bytes()[..3]);
892        input.extend_from_slice(&gen.to_le_bytes()[..2]);
893        md5(&input)[..(key.len() + 5).min(16)].to_vec()
894    }
895
896    fn hexstr(b: &[u8]) -> String {
897        let mut s = String::from("<");
898        for x in b {
899            s.push_str(&format!("{x:02x}"));
900        }
901        s.push('>');
902        s
903    }
904
905    fn encrypted_fixture(u_override: Option<Vec<u8>>) -> Vec<u8> {
906        use pdfboss_testkit::PdfBuilder;
907        let o = owner_entry();
908        let key = file_key(&o);
909        let u = u_override.unwrap_or_else(|| user_entry(&key));
910
911        let msg = rc4(&obj_key(&key, 3, 0), b"Top secret message");
912        let stream = rc4(&obj_key(&key, 4, 0), b"decrypted stream body");
913
914        let mut b = PdfBuilder::new().version(1, 4);
915        b.object(1, "<< /Type /Catalog /Pages 2 0 R >>");
916        b.object(2, "<< /Type /Pages /Kids [] /Count 0 >>");
917        b.object(3, &format!("<< /Msg {} >>", hexstr(&msg)));
918        b.stream(4, "", &stream);
919        b.object(
920            9,
921            &format!(
922                "<< /Filter /Standard /V 2 /R 3 /Length 128 /P {P} /O {} /U {} >>",
923                hexstr(&o),
924                hexstr(&u)
925            ),
926        );
927        let trailer = format!("/Encrypt 9 0 R /ID [{}{}]", hexstr(ID0), hexstr(ID0));
928        b.trailer_extra(&trailer).build(1)
929    }
930
931    #[test]
932    fn document_load_decrypts_standard_rc4() {
933        use crate::object::ObjRef;
934        use crate::Document;
935
936        let doc = Document::load(encrypted_fixture(None)).expect("empty password opens the file");
937
938        let obj3 = doc.get(ObjRef { num: 3, gen: 0 }).unwrap();
939        let msg = obj3
940            .as_dict()
941            .unwrap()
942            .get("Msg")
943            .unwrap()
944            .as_str_bytes()
945            .unwrap();
946        assert_eq!(msg, b"Top secret message", "string decrypted");
947
948        let obj4 = doc.get(ObjRef { num: 4, gen: 0 }).unwrap();
949        let data = doc.stream_data(obj4.as_stream().unwrap()).unwrap();
950        assert_eq!(data, b"decrypted stream body", "stream decrypted");
951    }
952
953    #[test]
954    fn document_load_rejects_when_password_does_not_verify() {
955        use crate::error::Error;
956        use crate::Document;
957
958        // A `/U` that will not verify under the empty password stands in for a
959        // real password-protected file: the loader must decline, not decrypt.
960        let bad_u = vec![0u8; 32];
961        let err = Document::load(encrypted_fixture(Some(bad_u)));
962        assert!(matches!(err, Err(Error::Encrypted)));
963    }
964
965    #[test]
966    fn unsupported_handler_is_declined() {
967        // A future/unknown handler version is declined so the caller reports the
968        // file as encrypted-and-unsupported.
969        let mut enc = Dict::new();
970        enc.insert(Name("Filter".into()), Object::Name(Name("Standard".into())));
971        enc.insert(Name("V".into()), Object::Int(6));
972        enc.insert(Name("R".into()), Object::Int(7));
973        enc.insert(Name("O".into()), Object::String(vec![0; 48]));
974        enc.insert(Name("U".into()), Object::String(vec![0; 48]));
975        enc.insert(Name("P".into()), Object::Int(-4));
976        assert!(Decryptor::from_standard(&enc, ID0).is_none());
977    }
978
979    // --- AES / SHA-2 known-answer vectors ---
980
981    #[test]
982    fn aes_fips197_block_vectors() {
983        // FIPS-197 Appendix C.1 (AES-128) and C.3 (AES-256), same plaintext.
984        let pt: [u8; 16] = [
985            0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd,
986            0xee, 0xff,
987        ];
988        let key128: Vec<u8> = (0u8..16).collect();
989        let rks = aes_expand_key(&key128);
990        let mut b = pt;
991        aes_encrypt_block(&mut b, &rks);
992        assert_eq!(hex(&b), "69c4e0d86a7b0430d8cdb78070b4c55a");
993        aes_decrypt_block(&mut b, &rks, &aes_inv_sbox());
994        assert_eq!(b, pt, "AES-128 decrypt inverts encrypt");
995
996        let key256: Vec<u8> = (0u8..32).collect();
997        let rks = aes_expand_key(&key256);
998        let mut b = pt;
999        aes_encrypt_block(&mut b, &rks);
1000        assert_eq!(hex(&b), "8ea2b7ca516745bfeafc49904b496089");
1001    }
1002
1003    #[test]
1004    fn sha2_vectors() {
1005        assert_eq!(
1006            hex(&sha256(b"abc")),
1007            "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"
1008        );
1009        assert_eq!(
1010            hex(&sha512(b"abc")),
1011            "ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a\
1012             2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f"
1013        );
1014        assert_eq!(
1015            hex(&sha384(b"abc")),
1016            "cb00753f45a35e8bb5a03d699ac65007272c32ab0eded1631a8b605a43ff5bed\
1017             8086072ba1e7cc2358baeca134c825a7"
1018        );
1019    }
1020
1021    fn pkcs7_pad(data: &[u8]) -> Vec<u8> {
1022        let pad = 16 - (data.len() % 16); // 1..=16 (a full block when aligned)
1023        let mut v = data.to_vec();
1024        v.resize(data.len() + pad, pad as u8);
1025        v
1026    }
1027
1028    #[test]
1029    fn aes_cbc_roundtrip() {
1030        let key: Vec<u8> = (0u8..16).collect();
1031        let iv = [0x24u8; 16];
1032        let pt = b"a message spanning several AES blocks exactly?!!";
1033        let ct = aes_cbc_encrypt_blocks(&key, &iv, &pkcs7_pad(pt));
1034        let mut val = iv.to_vec(); // PDF format: IV followed by ciphertext
1035        val.extend_from_slice(&ct);
1036        assert_eq!(aes_cbc_decrypt(&key, &val), pt);
1037    }
1038
1039    // --- AESV2 (V4/R4) end-to-end fixture ---
1040
1041    fn obj_key_aes(key: &[u8], num: u32, gen: u16) -> Vec<u8> {
1042        let mut input = key.to_vec();
1043        input.extend_from_slice(&num.to_le_bytes()[..3]);
1044        input.extend_from_slice(&gen.to_le_bytes()[..2]);
1045        input.extend_from_slice(b"sAlT");
1046        md5(&input)[..(key.len() + 5).min(16)].to_vec()
1047    }
1048
1049    /// Encrypts as a PDF AES value: a 16-byte IV followed by CBC ciphertext of
1050    /// the PKCS#7-padded plaintext.
1051    fn aes_encrypt_pdf(key: &[u8], pt: &[u8], iv: &[u8; 16]) -> Vec<u8> {
1052        let mut out = iv.to_vec();
1053        out.extend_from_slice(&aes_cbc_encrypt_blocks(key, iv, &pkcs7_pad(pt)));
1054        out
1055    }
1056
1057    fn encrypted_fixture_aesv2() -> Vec<u8> {
1058        use pdfboss_testkit::PdfBuilder;
1059        let o = owner_entry();
1060        let key = file_key(&o); // R4 derivation matches R3 (EncryptMetadata true)
1061        let u = user_entry(&key);
1062        let iv = [0x11u8; 16];
1063        let msg = aes_encrypt_pdf(&obj_key_aes(&key, 3, 0), b"Top secret message", &iv);
1064        let stream = aes_encrypt_pdf(&obj_key_aes(&key, 4, 0), b"decrypted stream body", &iv);
1065
1066        let mut b = PdfBuilder::new().version(1, 5);
1067        b.object(1, "<< /Type /Catalog /Pages 2 0 R >>");
1068        b.object(2, "<< /Type /Pages /Kids [] /Count 0 >>");
1069        b.object(3, &format!("<< /Msg {} >>", hexstr(&msg)));
1070        b.stream(4, "", &stream);
1071        b.object(
1072            9,
1073            &format!(
1074                "<< /Filter /Standard /V 4 /R 4 /Length 128 /P {P} /O {} /U {} \
1075                 /CF << /StdCF << /CFM /AESV2 /Length 16 >> >> /StmF /StdCF /StrF /StdCF >>",
1076                hexstr(&o),
1077                hexstr(&u)
1078            ),
1079        );
1080        let trailer = format!("/Encrypt 9 0 R /ID [{}{}]", hexstr(ID0), hexstr(ID0));
1081        b.trailer_extra(&trailer).build(1)
1082    }
1083
1084    #[test]
1085    fn document_load_decrypts_aesv2() {
1086        use crate::object::ObjRef;
1087        use crate::Document;
1088        let doc = Document::load(encrypted_fixture_aesv2()).expect("AESV2 empty password opens");
1089        let obj3 = doc.get(ObjRef { num: 3, gen: 0 }).unwrap();
1090        let msg = obj3
1091            .as_dict()
1092            .unwrap()
1093            .get("Msg")
1094            .unwrap()
1095            .as_str_bytes()
1096            .unwrap();
1097        assert_eq!(msg, b"Top secret message");
1098        let obj4 = doc.get(ObjRef { num: 4, gen: 0 }).unwrap();
1099        assert_eq!(
1100            doc.stream_data(obj4.as_stream().unwrap()).unwrap(),
1101            b"decrypted stream body"
1102        );
1103    }
1104
1105    // --- AESV3 (V5/R5 and R6) end-to-end fixture ---
1106
1107    fn encrypted_fixture_aesv3(r: i64) -> Vec<u8> {
1108        use pdfboss_testkit::PdfBuilder;
1109        let key: Vec<u8> = (0u8..32).map(|i| i ^ 0x5a).collect(); // arbitrary 256-bit file key
1110        let vsalt: [u8; 8] = [1, 2, 3, 4, 5, 6, 7, 8];
1111        let ksalt: [u8; 8] = [9, 10, 11, 12, 13, 14, 15, 16];
1112        let mut u = hash_2b(r, b"", &vsalt, &[]); // 32-byte validation hash
1113        u.extend_from_slice(&vsalt);
1114        u.extend_from_slice(&ksalt);
1115        let intermediate = hash_2b(r, b"", &ksalt, &[]);
1116        let ue = aes_cbc_encrypt_blocks(&intermediate, &[0u8; 16], &key);
1117        let iv = [0x22u8; 16];
1118        let msg = aes_encrypt_pdf(&key, b"AES-256 secret", &iv);
1119        let stream = aes_encrypt_pdf(&key, b"AES-256 stream body", &iv);
1120
1121        let mut b = PdfBuilder::new().version(1, 7);
1122        b.object(1, "<< /Type /Catalog /Pages 2 0 R >>");
1123        b.object(2, "<< /Type /Pages /Kids [] /Count 0 >>");
1124        b.object(3, &format!("<< /Msg {} >>", hexstr(&msg)));
1125        b.stream(4, "", &stream);
1126        b.object(
1127            9,
1128            &format!(
1129                "<< /Filter /Standard /V 5 /R {r} /Length 256 /P {P} /U {} /UE {} \
1130                 /O {} /OE {} \
1131                 /CF << /StdCF << /CFM /AESV3 /Length 32 >> >> /StmF /StdCF /StrF /StdCF >>",
1132                hexstr(&u),
1133                hexstr(&ue),
1134                hexstr(&[0u8; 48]),
1135                hexstr(&[0u8; 32])
1136            ),
1137        );
1138        let trailer = format!("/Encrypt 9 0 R /ID [{}{}]", hexstr(ID0), hexstr(ID0));
1139        b.trailer_extra(&trailer).build(1)
1140    }
1141
1142    fn assert_aesv3_decrypts(r: i64) {
1143        use crate::object::ObjRef;
1144        use crate::Document;
1145        let doc = Document::load(encrypted_fixture_aesv3(r)).expect("AESV3 empty password opens");
1146        let obj3 = doc.get(ObjRef { num: 3, gen: 0 }).unwrap();
1147        let msg = obj3
1148            .as_dict()
1149            .unwrap()
1150            .get("Msg")
1151            .unwrap()
1152            .as_str_bytes()
1153            .unwrap();
1154        assert_eq!(msg, b"AES-256 secret", "R{r} string");
1155        let obj4 = doc.get(ObjRef { num: 4, gen: 0 }).unwrap();
1156        assert_eq!(
1157            doc.stream_data(obj4.as_stream().unwrap()).unwrap(),
1158            b"AES-256 stream body",
1159            "R{r} stream"
1160        );
1161    }
1162
1163    #[test]
1164    fn document_load_decrypts_aesv3_r5() {
1165        assert_aesv3_decrypts(5);
1166    }
1167
1168    #[test]
1169    fn document_load_decrypts_aesv3_r6() {
1170        assert_aesv3_decrypts(6); // exercises the iterated Algorithm 2.B hash
1171    }
1172}