Skip to main content

pdfboss_core/
crypt.rs

1//! Decryption for the Standard security handler (ISO 32000 §7.6), opened
2//! with the user or the owner password (the empty user password opens
3//! transparently).
4//!
5//! Handles RC4 (`/V` 1–2, `/R` 2–3, 40–128-bit), AESV2 (`/V` 4, 128-bit
6//! AES-CBC) and AESV3 (`/V` 5, `/R` 5–6, 256-bit AES-CBC). Documents whose
7//! password the caller does not supply are reported as encrypted. The
8//! primitives — MD5, RC4, AES and the SHA-2 family — are implemented here from
9//! their published specifications so the crate needs no cryptographic
10//! dependency.
11//!
12//! This is low-level API: [`crate::Document`] applies it automatically when
13//! opening an encrypted file, and most callers never name [`Decryptor`]
14//! directly. It is public for code that drives object reads itself, as the
15//! asynchronous API does.
16
17use crate::object::{Dict, Object};
18
19/// Password padding string (ISO 32000 §7.6.3.3, Algorithm 2, step (a)).
20const PAD: [u8; 32] = [
21    0x28, 0xBF, 0x4E, 0x5E, 0x4E, 0x75, 0x8A, 0x41, 0x64, 0x00, 0x4E, 0x56, 0xFF, 0xFA, 0x01, 0x08,
22    0x2E, 0x2E, 0x00, 0xB6, 0xD0, 0x68, 0x3E, 0x80, 0x2F, 0x0C, 0xA9, 0xFE, 0x64, 0x53, 0x69, 0x7A,
23];
24
25/// Which cipher a configured [`Decryptor`] applies to strings and streams.
26#[derive(Clone, Copy, PartialEq)]
27enum Cipher {
28    /// RC4 stream cipher, per-object key (V1/V2, and V4 with `/CFM /V2`).
29    Rc4,
30    /// AES-128-CBC, per-object key with the `sAlT` suffix (V4, `/CFM /AESV2`).
31    Aesv2,
32    /// AES-256-CBC, the file key applied directly (V5, `/CFM /AESV3`).
33    Aesv3,
34}
35
36/// A configured Standard-handler decryptor for an opened document.
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        Decryptor::from_standard_with_password(enc, id0, b"")
62    }
63
64    /// [`Decryptor::from_standard_with_password`] for a text password: the
65    /// UTF-8 bytes are tried first and, when they differ and every char
66    /// fits, the Latin-1 bytes as well — the legacy revisions hash raw
67    /// bytes without naming an encoding, and real files use both.
68    pub fn from_standard_with_password_str(
69        enc: &Dict,
70        id0: &[u8],
71        password: &str,
72    ) -> Option<Decryptor> {
73        let dec = Decryptor::from_standard_with_password(enc, id0, password.as_bytes());
74        if dec.is_some() || password.is_ascii() || password.chars().any(|c| (c as u32) > 255) {
75            return dec;
76        }
77        let latin1: Vec<u8> = password.chars().map(|c| c as u8).collect();
78        Decryptor::from_standard_with_password(enc, id0, &latin1)
79    }
80
81    /// [`Decryptor::from_standard`] with a caller-supplied password, tried
82    /// first as the user password and then as the owner password (which
83    /// recovers the user-level key, ISO 32000 §7.6.3.4 Algorithm 7 for the
84    /// RC4/AES-128 revisions, §7.6.4.3.3 for AES-256). `None` when the
85    /// password opens nothing.
86    pub fn from_standard_with_password(
87        enc: &Dict,
88        id0: &[u8],
89        password: &[u8],
90    ) -> Option<Decryptor> {
91        if enc.get_name("Filter").map(|n| n.0.as_str()) != Some("Standard") {
92            return None;
93        }
94        let v = enc.get_int("V").unwrap_or(0);
95        let r = enc.get_int("R").unwrap_or(0);
96        match (v, r) {
97            // RC4: V1 (40-bit) and V2 (up to 128-bit).
98            (1 | 2, 2 | 3) => {
99                let n = if v == 1 {
100                    5
101                } else {
102                    (enc.get_int("Length").unwrap_or(40) / 8).clamp(5, 16) as usize
103                };
104                let key = rc4_family_key(enc, id0, r, n, password)?;
105                Some(Decryptor {
106                    key,
107                    cipher: Cipher::Rc4,
108                })
109            }
110            // V4: 128-bit key, cipher chosen by the standard crypt filter.
111            (4, 4) => {
112                let key = rc4_family_key(enc, id0, r, 16, password)?;
113                let cipher = match crypt_filter_method(enc)?.as_str() {
114                    "AESV2" => Cipher::Aesv2,
115                    "V2" => Cipher::Rc4,
116                    _ => return None, // Identity or unknown
117                };
118                Some(Decryptor { key, cipher })
119            }
120            // V5: AES-256 with SHA-2-based key derivation.
121            (5, 5 | 6) => aesv3_key(enc, r, password).map(|key| Decryptor {
122                key,
123                cipher: Cipher::Aesv3,
124            }),
125            _ => None,
126        }
127    }
128
129    /// Decrypts one indirect object's strings and stream data in place. Objects
130    /// extracted from object streams are already plaintext and must not be
131    /// passed here.
132    ///
133    /// Low-level, like [`Decryptor::from_standard`]: [`crate::Document`] applies
134    /// this itself as it loads objects, and a caller only reaches for it when
135    /// driving object reads directly — as the asynchronous API does.
136    ///
137    /// ```
138    /// use pdfboss_core::{Decryptor, Object};
139    ///
140    /// // Reachable from outside the crate; exercising it needs an encrypted
141    /// // document, so this only pins the signature and the visibility.
142    /// let apply: fn(&Decryptor, &mut Object, u32, u16) = Decryptor::decrypt_object;
143    /// let _ = apply;
144    /// ```
145    pub fn decrypt_object(&self, obj: &mut Object, num: u32, gen: u16) {
146        let key = match self.cipher {
147            Cipher::Aesv3 => self.key.clone(), // one file key for every object
148            Cipher::Rc4 | Cipher::Aesv2 => self.object_key(num, gen),
149        };
150        decrypt_in_place(obj, &key, self.cipher);
151    }
152
153    /// Per-object key: `MD5(filekey ++ num[0..3] ++ gen[0..2] [++ "sAlT"])`
154    /// truncated to `min(n + 5, 16)` bytes (ISO 32000 §7.6.2, Algorithm 1). The
155    /// `sAlT` suffix is added for AES crypt filters.
156    fn object_key(&self, num: u32, gen: u16) -> Vec<u8> {
157        let mut input = Vec::with_capacity(self.key.len() + 9);
158        input.extend_from_slice(&self.key);
159        input.extend_from_slice(&num.to_le_bytes()[..3]);
160        input.extend_from_slice(&gen.to_le_bytes()[..2]);
161        if self.cipher == Cipher::Aesv2 {
162            input.extend_from_slice(b"sAlT");
163        }
164        let digest = md5(&input);
165        let n = (self.key.len() + 5).min(16);
166        digest[..n].to_vec()
167    }
168}
169
170/// Recursively decrypts every string and stream body reachable from `obj` with
171/// the per-object `key` under `cipher`.
172fn decrypt_in_place(obj: &mut Object, key: &[u8], cipher: Cipher) {
173    match obj {
174        Object::String(bytes) => *bytes = decrypt_bytes(cipher, key, bytes),
175        Object::Array(items) => items
176            .iter_mut()
177            .for_each(|it| decrypt_in_place(it, key, cipher)),
178        Object::Dict(dict) => dict
179            .values_mut()
180            .for_each(|v| decrypt_in_place(v, key, cipher)),
181        Object::Stream(stream) => {
182            stream
183                .dict
184                .values_mut()
185                .for_each(|v| decrypt_in_place(v, key, cipher));
186            stream.data = decrypt_bytes(cipher, key, &stream.data);
187        }
188        _ => {}
189    }
190}
191
192/// Applies `cipher` to one string or stream body with the given `key`.
193fn decrypt_bytes(cipher: Cipher, key: &[u8], data: &[u8]) -> Vec<u8> {
194    match cipher {
195        Cipher::Rc4 => rc4(key, data),
196        Cipher::Aesv2 | Cipher::Aesv3 => aes_cbc_decrypt(key, data),
197    }
198}
199
200/// The Standard stream crypt filter's method (`/CF` → `/StmF` → `/CFM`):
201/// `V2`, `AESV2`, or `Identity`.
202fn crypt_filter_method(enc: &Dict) -> Option<String> {
203    let stmf = enc
204        .get_name("StmF")
205        .map(|n| n.0.as_str())
206        .unwrap_or("StdCF");
207    let filter = enc.get_dict("CF")?.get_dict(stmf)?;
208    Some(filter.get_name("CFM")?.0.clone())
209}
210
211/// Pads or truncates a password to the 32 bytes every legacy algorithm
212/// hashes (ISO 32000 §7.6.3.3, Algorithm 2 step (a)). The empty password
213/// pads to [`PAD`] itself.
214fn pad_password(password: &[u8]) -> [u8; 32] {
215    let mut out = [0u8; 32];
216    let n = password.len().min(32);
217    out[..n].copy_from_slice(&password[..n]);
218    out[n..].copy_from_slice(&PAD[..32 - n]);
219    out
220}
221
222/// The RC4/AESV2 file key `password` actually opens: tried as the user
223/// password (Algorithm 2 + the `/U` check), then as the owner password
224/// (Algorithm 7: the owner key decrypts `/O` back into the padded user
225/// password, which must then verify like any user password).
226fn rc4_family_key(enc: &Dict, id0: &[u8], r: i64, n: usize, password: &[u8]) -> Option<Vec<u8>> {
227    let u = enc.get("U").and_then(Object::as_str_bytes)?;
228    if let Some(key) = md5_file_key(enc, id0, r, n, &pad_password(password)) {
229        if verify_user_password(&key, r, id0, u) {
230            return Some(key);
231        }
232    }
233    // Owner attempt. The owner key comes from the owner password alone
234    // (Algorithm 3 steps (a)-(d)); what it decrypts out of `/O` is the
235    // padded user password, ready for Algorithm 2 verbatim.
236    let o = enc.get("O").and_then(Object::as_str_bytes)?;
237    if o.len() < 32 {
238        return None;
239    }
240    let mut d = md5(&pad_password(password));
241    if r >= 3 {
242        for _ in 0..50 {
243            d = md5(&d[..n]);
244        }
245    }
246    let okey = &d[..n];
247    let recovered = if r == 2 {
248        rc4(okey, &o[..32])
249    } else {
250        let mut x = o[..32].to_vec();
251        for i in (1u8..=19).rev() {
252            let keyed: Vec<u8> = okey.iter().map(|b| b ^ i).collect();
253            x = rc4(&keyed, &x);
254        }
255        rc4(okey, &x)
256    };
257    let padded: [u8; 32] = recovered.get(..32)?.try_into().ok()?;
258    let key = md5_file_key(enc, id0, r, n, &padded)?;
259    verify_user_password(&key, r, id0, u).then_some(key)
260}
261
262/// Algorithm 2: derive the RC4/AESV2 file key from a padded user password.
263fn md5_file_key(enc: &Dict, id0: &[u8], r: i64, n: usize, padded: &[u8; 32]) -> Option<Vec<u8>> {
264    let o = enc.get("O").and_then(Object::as_str_bytes)?;
265    if o.len() < 32 {
266        return None;
267    }
268    let p = enc.get_int("P")?;
269    let mut input = Vec::with_capacity(32 + 32 + 4 + id0.len() + 4);
270    input.extend_from_slice(padded);
271    input.extend_from_slice(&o[..32]);
272    input.extend_from_slice(&(p as i32 as u32).to_le_bytes()); // /P low 32 bits, LE
273    input.extend_from_slice(id0);
274    // Revision 4 with /EncryptMetadata false hashes an extra 0xFFFFFFFF.
275    if r >= 4 && enc.get("EncryptMetadata").and_then(Object::as_bool) == Some(false) {
276        input.extend_from_slice(&[0xFF, 0xFF, 0xFF, 0xFF]);
277    }
278    let mut digest = md5(&input);
279    if r >= 3 {
280        for _ in 0..50 {
281            digest = md5(&digest[..n]);
282        }
283    }
284    Some(digest[..n].to_vec())
285}
286
287/// Checks the empty user password by recomputing `/U` and comparing.
288fn verify_user_password(key: &[u8], r: i64, id0: &[u8], u: &[u8]) -> bool {
289    if r == 2 {
290        // Algorithm 4: U = RC4(key, PAD).
291        let computed = rc4(key, &PAD);
292        u.len() >= 32 && computed == u[..32]
293    } else {
294        // Algorithm 5: U = MD5(PAD ++ ID[0]) encrypted with 20 keyed RC4 passes.
295        let mut input = Vec::with_capacity(32 + id0.len());
296        input.extend_from_slice(&PAD);
297        input.extend_from_slice(id0);
298        let mut x = md5(&input).to_vec();
299        x = rc4(key, &x);
300        for i in 1u8..=19 {
301            let keyed: Vec<u8> = key.iter().map(|b| b ^ i).collect();
302            x = rc4(&keyed, &x);
303        }
304        // Only the first 16 bytes are defined; the rest of /U is arbitrary padding.
305        u.len() >= 16 && x[..16] == u[..16]
306    }
307}
308
309/// RC4 stream cipher (symmetric: the same call encrypts and decrypts).
310fn rc4(key: &[u8], data: &[u8]) -> Vec<u8> {
311    debug_assert!(!key.is_empty());
312    let mut s: [u8; 256] = core::array::from_fn(|i| i as u8);
313    let mut j = 0u8;
314    for i in 0..256 {
315        j = j.wrapping_add(s[i]).wrapping_add(key[i % key.len()]);
316        s.swap(i, j as usize);
317    }
318    let mut out = Vec::with_capacity(data.len());
319    let (mut i, mut j) = (0u8, 0u8);
320    for &byte in data {
321        i = i.wrapping_add(1);
322        j = j.wrapping_add(s[i as usize]);
323        s.swap(i as usize, j as usize);
324        let k = s[s[i as usize].wrapping_add(s[j as usize]) as usize];
325        out.push(byte ^ k);
326    }
327    out
328}
329
330// --- AES (FIPS-197) and CBC mode -----------------------------------------
331
332/// AES substitution box.
333#[rustfmt::skip]
334const AES_SBOX: [u8; 256] = [
335    0x63,0x7c,0x77,0x7b,0xf2,0x6b,0x6f,0xc5,0x30,0x01,0x67,0x2b,0xfe,0xd7,0xab,0x76,
336    0xca,0x82,0xc9,0x7d,0xfa,0x59,0x47,0xf0,0xad,0xd4,0xa2,0xaf,0x9c,0xa4,0x72,0xc0,
337    0xb7,0xfd,0x93,0x26,0x36,0x3f,0xf7,0xcc,0x34,0xa5,0xe5,0xf1,0x71,0xd8,0x31,0x15,
338    0x04,0xc7,0x23,0xc3,0x18,0x96,0x05,0x9a,0x07,0x12,0x80,0xe2,0xeb,0x27,0xb2,0x75,
339    0x09,0x83,0x2c,0x1a,0x1b,0x6e,0x5a,0xa0,0x52,0x3b,0xd6,0xb3,0x29,0xe3,0x2f,0x84,
340    0x53,0xd1,0x00,0xed,0x20,0xfc,0xb1,0x5b,0x6a,0xcb,0xbe,0x39,0x4a,0x4c,0x58,0xcf,
341    0xd0,0xef,0xaa,0xfb,0x43,0x4d,0x33,0x85,0x45,0xf9,0x02,0x7f,0x50,0x3c,0x9f,0xa8,
342    0x51,0xa3,0x40,0x8f,0x92,0x9d,0x38,0xf5,0xbc,0xb6,0xda,0x21,0x10,0xff,0xf3,0xd2,
343    0xcd,0x0c,0x13,0xec,0x5f,0x97,0x44,0x17,0xc4,0xa7,0x7e,0x3d,0x64,0x5d,0x19,0x73,
344    0x60,0x81,0x4f,0xdc,0x22,0x2a,0x90,0x88,0x46,0xee,0xb8,0x14,0xde,0x5e,0x0b,0xdb,
345    0xe0,0x32,0x3a,0x0a,0x49,0x06,0x24,0x5c,0xc2,0xd3,0xac,0x62,0x91,0x95,0xe4,0x79,
346    0xe7,0xc8,0x37,0x6d,0x8d,0xd5,0x4e,0xa9,0x6c,0x56,0xf4,0xea,0x65,0x7a,0xae,0x08,
347    0xba,0x78,0x25,0x2e,0x1c,0xa6,0xb4,0xc6,0xe8,0xdd,0x74,0x1f,0x4b,0xbd,0x8b,0x8a,
348    0x70,0x3e,0xb5,0x66,0x48,0x03,0xf6,0x0e,0x61,0x35,0x57,0xb9,0x86,0xc1,0x1d,0x9e,
349    0xe1,0xf8,0x98,0x11,0x69,0xd9,0x8e,0x94,0x9b,0x1e,0x87,0xe9,0xce,0x55,0x28,0xdf,
350    0x8c,0xa1,0x89,0x0d,0xbf,0xe6,0x42,0x68,0x41,0x99,0x2d,0x0f,0xb0,0x54,0xbb,0x16,
351];
352
353/// Round constants for key expansion (`RCON[j]` used when `i % Nk == 0`).
354const AES_RCON: [u8; 11] = [
355    0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36,
356];
357
358/// The inverse S-box, derived once from `AES_SBOX`.
359fn aes_inv_sbox() -> [u8; 256] {
360    let mut inv = [0u8; 256];
361    for (i, &s) in AES_SBOX.iter().enumerate() {
362        inv[s as usize] = i as u8;
363    }
364    inv
365}
366
367/// Multiplies two elements of GF(2^8) with the AES reduction polynomial.
368fn gmul(mut a: u8, mut b: u8) -> u8 {
369    let mut p = 0u8;
370    for _ in 0..8 {
371        if b & 1 != 0 {
372            p ^= a;
373        }
374        let hi = a & 0x80;
375        a <<= 1;
376        if hi != 0 {
377            a ^= 0x1b;
378        }
379        b >>= 1;
380    }
381    p
382}
383
384/// Expands a 16- or 32-byte key into `Nr + 1` round keys (state is stored
385/// column-major, so byte `r + 4c` is row `r`, column `c`).
386fn aes_expand_key(key: &[u8]) -> Vec<[u8; 16]> {
387    let nk = key.len() / 4; // 4 (AES-128) or 8 (AES-256)
388    let nr = nk + 6;
389    let total = 4 * (nr + 1);
390    let mut w: Vec<[u8; 4]> = Vec::with_capacity(total);
391    for i in 0..nk {
392        w.push([key[4 * i], key[4 * i + 1], key[4 * i + 2], key[4 * i + 3]]);
393    }
394    for i in nk..total {
395        let mut t = w[i - 1];
396        if i.is_multiple_of(nk) {
397            t = [t[1], t[2], t[3], t[0]]; // RotWord
398            for b in &mut t {
399                *b = AES_SBOX[*b as usize]; // SubWord
400            }
401            t[0] ^= AES_RCON[i / nk];
402        } else if nk > 6 && i % nk == 4 {
403            for b in &mut t {
404                *b = AES_SBOX[*b as usize];
405            }
406        }
407        let prev = w[i - nk];
408        w.push([
409            prev[0] ^ t[0],
410            prev[1] ^ t[1],
411            prev[2] ^ t[2],
412            prev[3] ^ t[3],
413        ]);
414    }
415    (0..=nr)
416        .map(|round| {
417            let mut rk = [0u8; 16];
418            for c in 0..4 {
419                rk[4 * c..4 * c + 4].copy_from_slice(&w[4 * round + c]);
420            }
421            rk
422        })
423        .collect()
424}
425
426fn add_round_key(s: &mut [u8; 16], rk: &[u8; 16]) {
427    for (b, k) in s.iter_mut().zip(rk) {
428        *b ^= k;
429    }
430}
431
432fn shift_rows(s: &mut [u8; 16]) {
433    let o = *s;
434    for r in 1..4 {
435        for c in 0..4 {
436            s[r + 4 * c] = o[r + 4 * ((c + r) % 4)];
437        }
438    }
439}
440
441fn inv_shift_rows(s: &mut [u8; 16]) {
442    let o = *s;
443    for r in 1..4 {
444        for c in 0..4 {
445            s[r + 4 * c] = o[r + 4 * ((c + 4 - r) % 4)];
446        }
447    }
448}
449
450fn mix_columns(s: &mut [u8; 16]) {
451    for c in 0..4 {
452        let i = 4 * c;
453        let (a0, a1, a2, a3) = (s[i], s[i + 1], s[i + 2], s[i + 3]);
454        s[i] = gmul(a0, 2) ^ gmul(a1, 3) ^ a2 ^ a3;
455        s[i + 1] = a0 ^ gmul(a1, 2) ^ gmul(a2, 3) ^ a3;
456        s[i + 2] = a0 ^ a1 ^ gmul(a2, 2) ^ gmul(a3, 3);
457        s[i + 3] = gmul(a0, 3) ^ a1 ^ a2 ^ gmul(a3, 2);
458    }
459}
460
461fn inv_mix_columns(s: &mut [u8; 16]) {
462    for c in 0..4 {
463        let i = 4 * c;
464        let (a0, a1, a2, a3) = (s[i], s[i + 1], s[i + 2], s[i + 3]);
465        s[i] = gmul(a0, 14) ^ gmul(a1, 11) ^ gmul(a2, 13) ^ gmul(a3, 9);
466        s[i + 1] = gmul(a0, 9) ^ gmul(a1, 14) ^ gmul(a2, 11) ^ gmul(a3, 13);
467        s[i + 2] = gmul(a0, 13) ^ gmul(a1, 9) ^ gmul(a2, 14) ^ gmul(a3, 11);
468        s[i + 3] = gmul(a0, 11) ^ gmul(a1, 13) ^ gmul(a2, 9) ^ gmul(a3, 14);
469    }
470}
471
472fn aes_encrypt_block(s: &mut [u8; 16], rks: &[[u8; 16]]) {
473    #[cfg(target_arch = "aarch64")]
474    if std::arch::is_aarch64_feature_detected!("aes") {
475        // SAFETY: the `aes` target feature was just detected at runtime.
476        unsafe { aes_hw::encrypt_block(s, rks) };
477        return;
478    }
479    #[cfg(target_arch = "x86_64")]
480    if std::arch::is_x86_feature_detected!("aes") {
481        // SAFETY: the `aes` target feature was just detected at runtime.
482        unsafe { aes_hw::encrypt_block(s, rks) };
483        return;
484    }
485    aes_encrypt_block_soft(s, rks);
486}
487
488/// The portable byte-oriented rounds, for CPUs without AES instructions.
489fn aes_encrypt_block_soft(s: &mut [u8; 16], rks: &[[u8; 16]]) {
490    let nr = rks.len() - 1;
491    add_round_key(s, &rks[0]);
492    for rk in &rks[1..nr] {
493        s.iter_mut().for_each(|b| *b = AES_SBOX[*b as usize]);
494        shift_rows(s);
495        mix_columns(s);
496        add_round_key(s, rk);
497    }
498    s.iter_mut().for_each(|b| *b = AES_SBOX[*b as usize]);
499    shift_rows(s);
500    add_round_key(s, &rks[nr]);
501}
502
503fn aes_decrypt_block(s: &mut [u8; 16], rks: &[[u8; 16]], inv_sbox: &[u8; 256]) {
504    #[cfg(target_arch = "aarch64")]
505    if std::arch::is_aarch64_feature_detected!("aes") {
506        // SAFETY: the `aes` target feature was just detected at runtime.
507        unsafe { aes_hw::decrypt_block(s, rks) };
508        return;
509    }
510    #[cfg(target_arch = "x86_64")]
511    if std::arch::is_x86_feature_detected!("aes") {
512        // SAFETY: the `aes` target feature was just detected at runtime.
513        unsafe { aes_hw::decrypt_block(s, rks) };
514        return;
515    }
516    aes_decrypt_block_soft(s, rks, inv_sbox);
517}
518
519/// The portable byte-oriented rounds, for CPUs without AES instructions.
520fn aes_decrypt_block_soft(s: &mut [u8; 16], rks: &[[u8; 16]], inv_sbox: &[u8; 256]) {
521    let nr = rks.len() - 1;
522    add_round_key(s, &rks[nr]);
523    for rk in rks[1..nr].iter().rev() {
524        inv_shift_rows(s);
525        s.iter_mut().for_each(|b| *b = inv_sbox[*b as usize]);
526        add_round_key(s, rk);
527        inv_mix_columns(s);
528    }
529    inv_shift_rows(s);
530    s.iter_mut().for_each(|b| *b = inv_sbox[*b as usize]);
531    add_round_key(s, &rks[0]);
532}
533
534/// AES block rounds on the CPU's AES instructions. The R6 password hash
535/// CBC-encrypts about a megabyte per key derivation (Algorithm 2.B runs
536/// 64+ rounds over a 64-fold repeated block), which the byte-oriented
537/// software rounds above turn into ~500ms per encrypted document; these
538/// paths bring that under a millisecond. Callers detect the `aes` feature
539/// before entering; every function must produce bytes identical to the
540/// software rounds (pinned by the FIPS-197 vectors in the tests below).
541#[cfg(target_arch = "aarch64")]
542mod aes_hw {
543    use core::arch::aarch64::{
544        vaesdq_u8, vaeseq_u8, vaesimcq_u8, vaesmcq_u8, veorq_u8, vld1q_u8, vmovq_n_u8, vst1q_u8,
545    };
546
547    /// # Safety
548    /// Requires the `aes` target feature.
549    #[target_feature(enable = "aes")]
550    pub unsafe fn encrypt_block(s: &mut [u8; 16], rks: &[[u8; 16]]) {
551        let nr = rks.len() - 1;
552        // AESE folds AddRoundKey into SubBytes+ShiftRows, so the loop
553        // consumes rks[0..nr-1] and the last round's key lands as a plain
554        // XOR after the final AESE.
555        let mut x = vld1q_u8(s.as_ptr());
556        for rk in &rks[..nr - 1] {
557            x = vaesmcq_u8(vaeseq_u8(x, vld1q_u8(rk.as_ptr())));
558        }
559        x = vaeseq_u8(x, vld1q_u8(rks[nr - 1].as_ptr()));
560        x = veorq_u8(x, vld1q_u8(rks[nr].as_ptr()));
561        vst1q_u8(s.as_mut_ptr(), x);
562    }
563
564    /// # Safety
565    /// Requires the `aes` target feature.
566    #[target_feature(enable = "aes")]
567    pub unsafe fn decrypt_block(s: &mut [u8; 16], rks: &[[u8; 16]]) {
568        let nr = rks.len() - 1;
569        // AESD with a zero key is exactly InvSubBytes(InvShiftRows(x)),
570        // which lets the loop mirror the software inverse cipher's
571        // operation order with the round keys untransformed.
572        let zero = vmovq_n_u8(0);
573        let mut x = vld1q_u8(s.as_ptr());
574        x = veorq_u8(x, vld1q_u8(rks[nr].as_ptr()));
575        for rk in rks[1..nr].iter().rev() {
576            x = vaesdq_u8(x, zero);
577            x = veorq_u8(x, vld1q_u8(rk.as_ptr()));
578            x = vaesimcq_u8(x);
579        }
580        x = vaesdq_u8(x, zero);
581        x = veorq_u8(x, vld1q_u8(rks[0].as_ptr()));
582        vst1q_u8(s.as_mut_ptr(), x);
583    }
584}
585
586/// See the aarch64 twin above; same contract, AES-NI instructions.
587#[cfg(target_arch = "x86_64")]
588mod aes_hw {
589    use core::arch::x86_64::{
590        __m128i, _mm_aesdec_si128, _mm_aesdeclast_si128, _mm_aesenc_si128, _mm_aesenclast_si128,
591        _mm_aesimc_si128, _mm_loadu_si128, _mm_storeu_si128, _mm_xor_si128,
592    };
593
594    /// # Safety
595    /// Requires the `aes` target feature.
596    #[target_feature(enable = "aes")]
597    pub unsafe fn encrypt_block(s: &mut [u8; 16], rks: &[[u8; 16]]) {
598        let nr = rks.len() - 1;
599        let mut x = _mm_loadu_si128(s.as_ptr().cast::<__m128i>());
600        x = _mm_xor_si128(x, _mm_loadu_si128(rks[0].as_ptr().cast::<__m128i>()));
601        for rk in &rks[1..nr] {
602            x = _mm_aesenc_si128(x, _mm_loadu_si128(rk.as_ptr().cast::<__m128i>()));
603        }
604        x = _mm_aesenclast_si128(x, _mm_loadu_si128(rks[nr].as_ptr().cast::<__m128i>()));
605        _mm_storeu_si128(s.as_mut_ptr().cast::<__m128i>(), x);
606    }
607
608    /// # Safety
609    /// Requires the `aes` target feature.
610    #[target_feature(enable = "aes")]
611    pub unsafe fn decrypt_block(s: &mut [u8; 16], rks: &[[u8; 16]]) {
612        let nr = rks.len() - 1;
613        // AESDEC applies InvMixColumns before its key XOR, so the middle
614        // round keys go through AESIMC (the equivalent inverse cipher).
615        let mut x = _mm_loadu_si128(s.as_ptr().cast::<__m128i>());
616        x = _mm_xor_si128(x, _mm_loadu_si128(rks[nr].as_ptr().cast::<__m128i>()));
617        for rk in rks[1..nr].iter().rev() {
618            let dk = _mm_aesimc_si128(_mm_loadu_si128(rk.as_ptr().cast::<__m128i>()));
619            x = _mm_aesdec_si128(x, dk);
620        }
621        x = _mm_aesdeclast_si128(x, _mm_loadu_si128(rks[0].as_ptr().cast::<__m128i>()));
622        _mm_storeu_si128(s.as_mut_ptr().cast::<__m128i>(), x);
623    }
624}
625
626/// AES-CBC decryption of whole blocks (no IV prefix, no padding removal).
627/// Returns an empty vector when the input is not a positive multiple of 16.
628fn aes_cbc_decrypt_blocks(key: &[u8], iv: &[u8], data: &[u8]) -> Vec<u8> {
629    if data.is_empty() || !data.len().is_multiple_of(16) || iv.len() < 16 {
630        return Vec::new();
631    }
632    let rks = aes_expand_key(key);
633    let inv_sbox = aes_inv_sbox();
634    let mut prev = [0u8; 16];
635    prev.copy_from_slice(&iv[..16]);
636    let mut out = Vec::with_capacity(data.len());
637    for chunk in data.as_chunks::<16>().0 {
638        let mut block = [0u8; 16];
639        block.copy_from_slice(chunk);
640        let cipher = block;
641        aes_decrypt_block(&mut block, &rks, &inv_sbox);
642        for (b, p) in block.iter_mut().zip(&prev) {
643            *b ^= p;
644        }
645        out.extend_from_slice(&block);
646        prev = cipher;
647    }
648    out
649}
650
651/// AES-CBC encryption of whole blocks (no IV prefix, no padding). Used only by
652/// the R6 key-derivation hash.
653fn aes_cbc_encrypt_blocks(key: &[u8], iv: &[u8], data: &[u8]) -> Vec<u8> {
654    let rks = aes_expand_key(key);
655    let mut prev = [0u8; 16];
656    prev.copy_from_slice(&iv[..16]);
657    let mut out = Vec::with_capacity(data.len());
658    for chunk in data.as_chunks::<16>().0 {
659        let mut block = [0u8; 16];
660        for ((b, c), p) in block.iter_mut().zip(chunk).zip(&prev) {
661            *b = c ^ p;
662        }
663        aes_encrypt_block(&mut block, &rks);
664        out.extend_from_slice(&block);
665        prev = block;
666    }
667    out
668}
669
670/// Decrypts a PDF AES value: the first 16 bytes are the IV, the rest is
671/// CBC-encrypted with PKCS#7 padding. Malformed input yields empty output
672/// rather than garbage.
673fn aes_cbc_decrypt(key: &[u8], data: &[u8]) -> Vec<u8> {
674    if data.len() < 16 {
675        return Vec::new();
676    }
677    let (iv, ct) = data.split_at(16);
678    let mut out = aes_cbc_decrypt_blocks(key, iv, ct);
679    strip_pkcs7(&mut out);
680    out
681}
682
683/// Removes PKCS#7 padding in place if present and well-formed.
684fn strip_pkcs7(data: &mut Vec<u8>) {
685    let Some(&pad) = data.last() else {
686        return;
687    };
688    let pad = pad as usize;
689    if (1..=16).contains(&pad) && pad <= data.len() {
690        let start = data.len() - pad;
691        if data[start..].iter().all(|&b| b as usize == pad) {
692            data.truncate(start);
693        }
694    }
695}
696
697// --- SHA-2 (FIPS 180-4): 256, 512 and 384 --------------------------------
698
699#[rustfmt::skip]
700const SHA256_K: [u32; 64] = [
701    0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
702    0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
703    0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
704    0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
705    0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
706    0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
707    0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
708    0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
709];
710
711/// Streaming SHA-256 (FIPS 180-4): feed bytes with [`Sha256::update`],
712/// close with [`Sha256::finalize`]. Public so the writer can hash a file's
713/// body as it emits it, without holding the whole file in memory; the
714/// one-shot [`sha256`] delegates here.
715#[derive(Debug, Clone)]
716pub struct Sha256 {
717    h: [u32; 8],
718    tail: [u8; 64],
719    tail_len: usize,
720    total: u64,
721}
722
723impl Default for Sha256 {
724    fn default() -> Sha256 {
725        Sha256::new()
726    }
727}
728
729impl Sha256 {
730    /// A hasher in the FIPS 180-4 initial state.
731    pub fn new() -> Sha256 {
732        Sha256 {
733            h: [
734                0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab,
735                0x5be0cd19,
736            ],
737            tail: [0u8; 64],
738            tail_len: 0,
739            total: 0,
740        }
741    }
742
743    /// Absorbs `input`. Slice boundaries do not affect the digest: any
744    /// sequence of updates whose concatenation is the same message yields
745    /// the same [`Sha256::finalize`] result. Whole blocks are compressed
746    /// straight from `input`; only a partial trailing block is buffered.
747    pub fn update(&mut self, input: &[u8]) {
748        self.total = self.total.wrapping_add(input.len() as u64);
749        let mut input = input;
750        if self.tail_len > 0 {
751            let take = input.len().min(64 - self.tail_len);
752            self.tail[self.tail_len..self.tail_len + take].copy_from_slice(&input[..take]);
753            self.tail_len += take;
754            input = &input[take..];
755            if self.tail_len < 64 {
756                return;
757            }
758            let block = self.tail;
759            sha256_compress(&mut self.h, &block);
760            self.tail_len = 0;
761        }
762        let (blocks, rest) = input.as_chunks::<64>();
763        for block in blocks {
764            sha256_compress(&mut self.h, block);
765        }
766        self.tail[..rest.len()].copy_from_slice(rest);
767        self.tail_len = rest.len();
768    }
769
770    /// Pads and returns the digest of everything absorbed so far.
771    pub fn finalize(mut self) -> [u8; 32] {
772        // Final padding touches at most two blocks: 0x80, zeros, and the
773        // bit length in the last 8 bytes (FIPS 180-4).
774        let mut pad = [0u8; 128];
775        pad[..self.tail_len].copy_from_slice(&self.tail[..self.tail_len]);
776        pad[self.tail_len] = 0x80;
777        let padded = if self.tail_len < 56 { 64 } else { 128 };
778        let bitlen = self.total.wrapping_mul(8);
779        pad[padded - 8..padded].copy_from_slice(&bitlen.to_be_bytes());
780        for block in pad[..padded].as_chunks::<64>().0 {
781            sha256_compress(&mut self.h, block);
782        }
783        let mut out = [0u8; 32];
784        for (i, word) in self.h.iter().enumerate() {
785            out[4 * i..4 * i + 4].copy_from_slice(&word.to_be_bytes());
786        }
787        out
788    }
789}
790
791/// SHA-256 of `input` (FIPS 180-4); public so the writer can derive its `/ID`.
792pub fn sha256(input: &[u8]) -> [u8; 32] {
793    let mut hasher = Sha256::new();
794    hasher.update(input);
795    hasher.finalize()
796}
797
798fn sha256_compress(h: &mut [u32; 8], block: &[u8; 64]) {
799    #[cfg(target_arch = "aarch64")]
800    if std::arch::is_aarch64_feature_detected!("sha2") {
801        // SAFETY: the `sha2` target feature was just detected at runtime.
802        unsafe { sha_hw::compress256(h, block) };
803        return;
804    }
805    sha256_compress_soft(h, block);
806}
807
808/// The portable compression, for CPUs without SHA-256 instructions.
809fn sha256_compress_soft(h: &mut [u32; 8], block: &[u8; 64]) {
810    let mut w = [0u32; 64];
811    for (word, bytes) in w.iter_mut().zip(block.as_chunks::<4>().0) {
812        *word = u32::from_be_bytes(*bytes);
813    }
814    for i in 16..64 {
815        let s0 = w[i - 15].rotate_right(7) ^ w[i - 15].rotate_right(18) ^ (w[i - 15] >> 3);
816        let s1 = w[i - 2].rotate_right(17) ^ w[i - 2].rotate_right(19) ^ (w[i - 2] >> 10);
817        w[i] = w[i - 16]
818            .wrapping_add(s0)
819            .wrapping_add(w[i - 7])
820            .wrapping_add(s1);
821    }
822    let [mut a, mut b, mut c, mut d, mut e, mut f, mut g, mut hh] = *h;
823    for (k, wi) in SHA256_K.iter().zip(&w) {
824        let s1 = e.rotate_right(6) ^ e.rotate_right(11) ^ e.rotate_right(25);
825        let ch = (e & f) ^ ((!e) & g);
826        let t1 = hh
827            .wrapping_add(s1)
828            .wrapping_add(ch)
829            .wrapping_add(*k)
830            .wrapping_add(*wi);
831        let s0 = a.rotate_right(2) ^ a.rotate_right(13) ^ a.rotate_right(22);
832        let maj = (a & b) ^ (a & c) ^ (b & c);
833        let t2 = s0.wrapping_add(maj);
834        hh = g;
835        g = f;
836        f = e;
837        e = d.wrapping_add(t1);
838        d = c;
839        c = b;
840        b = a;
841        a = t1.wrapping_add(t2);
842    }
843    for (hv, v) in h.iter_mut().zip([a, b, c, d, e, f, g, hh]) {
844        *hv = hv.wrapping_add(v);
845    }
846}
847
848#[rustfmt::skip]
849const SHA512_K: [u64; 80] = [
850    0x428a2f98d728ae22, 0x7137449123ef65cd, 0xb5c0fbcfec4d3b2f, 0xe9b5dba58189dbbc,
851    0x3956c25bf348b538, 0x59f111f1b605d019, 0x923f82a4af194f9b, 0xab1c5ed5da6d8118,
852    0xd807aa98a3030242, 0x12835b0145706fbe, 0x243185be4ee4b28c, 0x550c7dc3d5ffb4e2,
853    0x72be5d74f27b896f, 0x80deb1fe3b1696b1, 0x9bdc06a725c71235, 0xc19bf174cf692694,
854    0xe49b69c19ef14ad2, 0xefbe4786384f25e3, 0x0fc19dc68b8cd5b5, 0x240ca1cc77ac9c65,
855    0x2de92c6f592b0275, 0x4a7484aa6ea6e483, 0x5cb0a9dcbd41fbd4, 0x76f988da831153b5,
856    0x983e5152ee66dfab, 0xa831c66d2db43210, 0xb00327c898fb213f, 0xbf597fc7beef0ee4,
857    0xc6e00bf33da88fc2, 0xd5a79147930aa725, 0x06ca6351e003826f, 0x142929670a0e6e70,
858    0x27b70a8546d22ffc, 0x2e1b21385c26c926, 0x4d2c6dfc5ac42aed, 0x53380d139d95b3df,
859    0x650a73548baf63de, 0x766a0abb3c77b2a8, 0x81c2c92e47edaee6, 0x92722c851482353b,
860    0xa2bfe8a14cf10364, 0xa81a664bbc423001, 0xc24b8b70d0f89791, 0xc76c51a30654be30,
861    0xd192e819d6ef5218, 0xd69906245565a910, 0xf40e35855771202a, 0x106aa07032bbd1b8,
862    0x19a4c116b8d2d0c8, 0x1e376c085141ab53, 0x2748774cdf8eeb99, 0x34b0bcb5e19b48a8,
863    0x391c0cb3c5c95a63, 0x4ed8aa4ae3418acb, 0x5b9cca4f7763e373, 0x682e6ff3d6b2b8a3,
864    0x748f82ee5defb2fc, 0x78a5636f43172f60, 0x84c87814a1f0ab72, 0x8cc702081a6439ec,
865    0x90befffa23631e28, 0xa4506cebde82bde9, 0xbef9a3f7b2c67915, 0xc67178f2e372532b,
866    0xca273eceea26619c, 0xd186b8c721c0c207, 0xeada7dd6cde0eb1e, 0xf57d4f7fee6ed178,
867    0x06f067aa72176fba, 0x0a637dc5a2c898a6, 0x113f9804bef90dae, 0x1b710b35131c471b,
868    0x28db77f523047d84, 0x32caab7b40c72493, 0x3c9ebe0a15c9bebc, 0x431d67c49c100d4c,
869    0x4cc5d4becb3e42b6, 0x597f299cfc657e2a, 0x5fcb6fab3ad6faec, 0x6c44198c4a475817,
870];
871
872fn sha512_core(input: &[u8], mut h: [u64; 8]) -> [u64; 8] {
873    let (blocks, tail) = input.as_chunks::<128>();
874    for block in blocks {
875        sha512_compress(&mut h, block);
876    }
877    // Final padding touches at most two blocks: 0x80, zeros, and the bit
878    // length in the last 16 bytes (FIPS 180-4) — the message itself is
879    // hashed in place above, never copied.
880    let mut pad = [0u8; 256];
881    pad[..tail.len()].copy_from_slice(tail);
882    pad[tail.len()] = 0x80;
883    let padded = if tail.len() < 112 { 128 } else { 256 };
884    let bitlen = (input.len() as u128).wrapping_mul(8);
885    pad[padded - 16..padded].copy_from_slice(&bitlen.to_be_bytes());
886    for block in pad[..padded].as_chunks::<128>().0 {
887        sha512_compress(&mut h, block);
888    }
889    h
890}
891
892fn sha512_compress(h: &mut [u64; 8], block: &[u8; 128]) {
893    #[cfg(target_arch = "aarch64")]
894    if std::arch::is_aarch64_feature_detected!("sha3") {
895        // SAFETY: the `sha3` target feature (SHA-512 instructions) was
896        // just detected at runtime.
897        unsafe { sha_hw::compress512(h, block) };
898        return;
899    }
900    sha512_compress_soft(h, block);
901}
902
903/// The portable compression, for CPUs without SHA-512 instructions.
904fn sha512_compress_soft(h: &mut [u64; 8], block: &[u8; 128]) {
905    let mut w = [0u64; 80];
906    for (word, bytes) in w.iter_mut().zip(block.as_chunks::<8>().0) {
907        *word = u64::from_be_bytes(*bytes);
908    }
909    for i in 16..80 {
910        let s0 = w[i - 15].rotate_right(1) ^ w[i - 15].rotate_right(8) ^ (w[i - 15] >> 7);
911        let s1 = w[i - 2].rotate_right(19) ^ w[i - 2].rotate_right(61) ^ (w[i - 2] >> 6);
912        w[i] = w[i - 16]
913            .wrapping_add(s0)
914            .wrapping_add(w[i - 7])
915            .wrapping_add(s1);
916    }
917    let [mut a, mut b, mut c, mut d, mut e, mut f, mut g, mut hh] = *h;
918    for (k, wi) in SHA512_K.iter().zip(&w) {
919        let s1 = e.rotate_right(14) ^ e.rotate_right(18) ^ e.rotate_right(41);
920        let ch = (e & f) ^ ((!e) & g);
921        let t1 = hh
922            .wrapping_add(s1)
923            .wrapping_add(ch)
924            .wrapping_add(*k)
925            .wrapping_add(*wi);
926        let s0 = a.rotate_right(28) ^ a.rotate_right(34) ^ a.rotate_right(39);
927        let maj = (a & b) ^ (a & c) ^ (b & c);
928        let t2 = s0.wrapping_add(maj);
929        hh = g;
930        g = f;
931        f = e;
932        e = d.wrapping_add(t1);
933        d = c;
934        c = b;
935        b = a;
936        a = t1.wrapping_add(t2);
937    }
938    for (hv, v) in h.iter_mut().zip([a, b, c, d, e, f, g, hh]) {
939        *hv = hv.wrapping_add(v);
940    }
941}
942
943/// SHA-2 compressions on the CPU's SHA instructions. Algorithm 2.B hashes
944/// roughly a megabyte per key derivation across its rounds — after the AES
945/// rounds moved to hardware this hashing was the remaining ~1-3ms of every
946/// encrypted-document open (PMU counters put it 60x below the memory-bound
947/// probe and at the ALU probe's branch signature: a pure serial dependency
948/// chain, which is exactly what the SHA instructions collapse). Callers
949/// detect `sha2`/`sha3` before entering; results are pinned to the FIPS
950/// 180 vectors in the tests below.
951#[cfg(target_arch = "aarch64")]
952mod sha_hw {
953    use core::arch::aarch64::{
954        uint32x4_t, uint64x2_t, vaddq_u32, vaddq_u64, vextq_u64, vld1q_u32, vld1q_u64, vld1q_u8,
955        vreinterpretq_u32_u8, vreinterpretq_u64_u8, vrev32q_u8, vrev64q_u8, vsha256h2q_u32,
956        vsha256hq_u32, vsha256su0q_u32, vsha256su1q_u32, vsha512h2q_u64, vsha512hq_u64,
957        vsha512su0q_u64, vsha512su1q_u64, vst1q_u32,
958    };
959
960    use super::{SHA256_K, SHA512_K};
961
962    /// # Safety
963    /// Requires the `sha2` target feature.
964    #[target_feature(enable = "sha2")]
965    pub unsafe fn compress256(h: &mut [u32; 8], block: &[u8; 64]) {
966        let mut abcd = vld1q_u32(h.as_ptr());
967        let mut efgh = vld1q_u32(h.as_ptr().add(4));
968        let saved = (abcd, efgh);
969        let mut w: [uint32x4_t; 4] = [
970            vreinterpretq_u32_u8(vrev32q_u8(vld1q_u8(block.as_ptr()))),
971            vreinterpretq_u32_u8(vrev32q_u8(vld1q_u8(block.as_ptr().add(16)))),
972            vreinterpretq_u32_u8(vrev32q_u8(vld1q_u8(block.as_ptr().add(32)))),
973            vreinterpretq_u32_u8(vrev32q_u8(vld1q_u8(block.as_ptr().add(48)))),
974        ];
975        for step in 0..16 {
976            let wk = vaddq_u32(w[step % 4], vld1q_u32(SHA256_K.as_ptr().add(4 * step)));
977            // The last three windows feed no further schedule.
978            if step < 12 {
979                w[step % 4] = vsha256su1q_u32(
980                    vsha256su0q_u32(w[step % 4], w[(step + 1) % 4]),
981                    w[(step + 2) % 4],
982                    w[(step + 3) % 4],
983                );
984            }
985            let prev = abcd;
986            abcd = vsha256hq_u32(abcd, efgh, wk);
987            efgh = vsha256h2q_u32(efgh, prev, wk);
988        }
989        vst1q_u32(h.as_mut_ptr(), vaddq_u32(abcd, saved.0));
990        vst1q_u32(h.as_mut_ptr().add(4), vaddq_u32(efgh, saved.1));
991    }
992
993    /// # Safety
994    /// Requires the `sha3` target feature (the SHA-512 instructions).
995    #[target_feature(enable = "sha2,sha3")]
996    pub unsafe fn compress512(h: &mut [u64; 8], block: &[u8; 128]) {
997        // State as register pairs s = [ab, cd, ef, gh]. Each step covers
998        // two rounds; the register playing the "gh" role rotates
999        // gh -> ef -> cd -> ab, which the r index tracks.
1000        let mut s: [uint64x2_t; 4] = [
1001            vld1q_u64(h.as_ptr()),
1002            vld1q_u64(h.as_ptr().add(2)),
1003            vld1q_u64(h.as_ptr().add(4)),
1004            vld1q_u64(h.as_ptr().add(6)),
1005        ];
1006        let saved = s;
1007        let mut w: [uint64x2_t; 8] = [core::mem::zeroed(); 8];
1008        for (pair, bytes) in w.iter_mut().zip(block.as_chunks::<16>().0) {
1009            *pair = vreinterpretq_u64_u8(vrev64q_u8(vld1q_u8(bytes.as_ptr())));
1010        }
1011        for step in 0..40 {
1012            let r = 3 - (step % 4);
1013            let wk = vaddq_u64(w[step % 8], vld1q_u64(SHA512_K.as_ptr().add(2 * step)));
1014            let sum = vaddq_u64(vextq_u64::<1>(wk, wk), s[r]);
1015            let im = vsha512hq_u64(
1016                sum,
1017                vextq_u64::<1>(s[(r + 3) % 4], s[r]),
1018                vextq_u64::<1>(s[(r + 2) % 4], s[(r + 3) % 4]),
1019            );
1020            let updated = vsha512h2q_u64(im, s[(r + 2) % 4], s[(r + 1) % 4]);
1021            s[(r + 2) % 4] = vaddq_u64(s[(r + 2) % 4], im);
1022            s[r] = updated;
1023            // The last four windows feed no further schedule.
1024            if step < 32 {
1025                let i = step % 8;
1026                w[i] = vsha512su1q_u64(
1027                    vsha512su0q_u64(w[i], w[(i + 1) % 8]),
1028                    w[(i + 7) % 8],
1029                    vextq_u64::<1>(w[(i + 4) % 8], w[(i + 5) % 8]),
1030                );
1031            }
1032        }
1033        for (i, (out, kept)) in s.iter().zip(saved).enumerate() {
1034            let summed = vaddq_u64(*out, kept);
1035            core::arch::aarch64::vst1q_u64(h.as_mut_ptr().add(2 * i), summed);
1036        }
1037    }
1038}
1039
1040fn sha512(input: &[u8]) -> Vec<u8> {
1041    let h = sha512_core(
1042        input,
1043        [
1044            0x6a09e667f3bcc908,
1045            0xbb67ae8584caa73b,
1046            0x3c6ef372fe94f82b,
1047            0xa54ff53a5f1d36f1,
1048            0x510e527fade682d1,
1049            0x9b05688c2b3e6c1f,
1050            0x1f83d9abfb41bd6b,
1051            0x5be0cd19137e2179,
1052        ],
1053    );
1054    h.iter().flat_map(|w| w.to_be_bytes()).collect()
1055}
1056
1057fn sha384(input: &[u8]) -> Vec<u8> {
1058    let h = sha512_core(
1059        input,
1060        [
1061            0xcbbb9d5dc1059ed8,
1062            0x629a292a367cd507,
1063            0x9159015a3070dd17,
1064            0x152fecd8f70e5939,
1065            0x67332667ffc00b31,
1066            0x8eb44a8768581511,
1067            0xdb0c2e0d64f98fa7,
1068            0x47b5481dbefa4fa4,
1069        ],
1070    );
1071    h.iter().take(6).flat_map(|w| w.to_be_bytes()).collect()
1072}
1073
1074/// Recovers the AES-256 file key `password` opens (ISO 32000-2 §7.6.4.3.3,
1075/// Algorithm 2.A) for revisions 5 and 6: as the user password against
1076/// `/U`+`/UE`, then as the owner password against `/O`+`/OE` (whose hashes
1077/// additionally salt in the first 48 bytes of `/U`). Passwords longer than
1078/// the 127 UTF-8 bytes the algorithm defines are truncated.
1079fn aesv3_key(enc: &Dict, r: i64, password: &[u8]) -> Option<Vec<u8>> {
1080    let pw = &password[..password.len().min(127)];
1081    let u = enc.get("U").and_then(Object::as_str_bytes)?;
1082    if u.len() < 48 {
1083        return None;
1084    }
1085    // Fast paths: derive each candidate file key straight from its key
1086    // salt and let the encrypted /Perms confirm it (ISO 32000-2 Algorithm
1087    // 2.A step g validates exactly this way) — one Algorithm 2.B hash per
1088    // path instead of two. Algorithm 2.B is the whole cost of opening an
1089    // encrypted document, so this halves it for every file whose /Perms
1090    // is intact; a wrong password decrypts /Perms to garbage and falls
1091    // through to the full U/O validation below, unchanged.
1092    let perms = enc.get("Perms").and_then(Object::as_str_bytes);
1093    let o = enc.get("O").and_then(Object::as_str_bytes);
1094    if let Some(perms) = perms.filter(|p| p.len() >= 16) {
1095        if let Some(ue) = enc.get("UE").and_then(Object::as_str_bytes) {
1096            if ue.len() >= 32 {
1097                let intermediate = hash_2b(r, pw, &u[40..48], &[]);
1098                let file_key = aes_cbc_decrypt_blocks(&intermediate, &[0u8; 16], &ue[..32]);
1099                if file_key.len() == 32 && perms_marker_valid(&file_key, perms) {
1100                    return Some(file_key);
1101                }
1102            }
1103        }
1104        if let (Some(o), Some(oe)) = (o, enc.get("OE").and_then(Object::as_str_bytes)) {
1105            if o.len() >= 48 && oe.len() >= 32 {
1106                let intermediate = hash_2b(r, pw, &o[40..48], &u[..48]);
1107                let file_key = aes_cbc_decrypt_blocks(&intermediate, &[0u8; 16], &oe[..32]);
1108                if file_key.len() == 32 && perms_marker_valid(&file_key, perms) {
1109                    return Some(file_key);
1110                }
1111            }
1112        }
1113    }
1114    if let Some(ue) = enc.get("UE").and_then(Object::as_str_bytes) {
1115        if ue.len() >= 32 && hash_2b(r, pw, &u[32..40], &[])[..32] == u[..32] {
1116            let intermediate = hash_2b(r, pw, &u[40..48], &[]);
1117            let file_key = aes_cbc_decrypt_blocks(&intermediate, &[0u8; 16], &ue[..32]);
1118            if file_key.len() == 32 {
1119                return Some(file_key);
1120            }
1121        }
1122    }
1123    let o = o?;
1124    let oe = enc.get("OE").and_then(Object::as_str_bytes)?;
1125    if o.len() < 48 || oe.len() < 32 {
1126        return None;
1127    }
1128    if hash_2b(r, pw, &o[32..40], &u[..48])[..32] != o[..32] {
1129        return None;
1130    }
1131    let intermediate = hash_2b(r, pw, &o[40..48], &u[..48]);
1132    let file_key = aes_cbc_decrypt_blocks(&intermediate, &[0u8; 16], &oe[..32]);
1133    (file_key.len() == 32).then_some(file_key)
1134}
1135
1136/// Whether `file_key` decrypts `/Perms` to its mandated marker: bytes 9-11
1137/// spell "adb" (ISO 32000-2 Algorithm 2.A step g; AES-256 ECB, no IV).
1138fn perms_marker_valid(file_key: &[u8], perms: &[u8]) -> bool {
1139    let rks = aes_expand_key(file_key);
1140    let inv_sbox = aes_inv_sbox();
1141    let mut block = [0u8; 16];
1142    block.copy_from_slice(&perms[..16]);
1143    aes_decrypt_block(&mut block, &rks, &inv_sbox);
1144    &block[9..12] == b"adb"
1145}
1146
1147/// The revision-6 password hash (ISO 32000-2, Algorithm 2.B); a plain SHA-256
1148/// for revision 5.
1149fn hash_2b(r: i64, password: &[u8], salt: &[u8], udata: &[u8]) -> Vec<u8> {
1150    let mut seed = Vec::with_capacity(password.len() + salt.len() + udata.len());
1151    seed.extend_from_slice(password);
1152    seed.extend_from_slice(salt);
1153    seed.extend_from_slice(udata);
1154    let mut k = sha256(&seed).to_vec();
1155    if r < 6 {
1156        return k; // revision 5: a single SHA-256
1157    }
1158    let mut round = 0usize;
1159    let mut k1 = Vec::with_capacity(64 * (password.len() + 64 + udata.len()));
1160    loop {
1161        k1.clear();
1162        for _ in 0..64 {
1163            k1.extend_from_slice(password);
1164            k1.extend_from_slice(&k);
1165            k1.extend_from_slice(udata);
1166        }
1167        let e = aes_cbc_encrypt_blocks(&k[..16], &k[16..32], &k1);
1168        let modulus = e[..16].iter().map(|&b| u32::from(b)).sum::<u32>() % 3;
1169        k = match modulus {
1170            0 => sha256(&e).to_vec(),
1171            1 => sha384(&e),
1172            _ => sha512(&e),
1173        };
1174        round += 1;
1175        if round >= 64 && usize::from(*e.last().unwrap()) <= round - 32 {
1176            break;
1177        }
1178    }
1179    k.truncate(32);
1180    k
1181}
1182
1183/// Per-round left-rotation amounts (RFC 1321).
1184#[rustfmt::skip]
1185const MD5_S: [u32; 64] = [
1186    7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22,
1187    5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20,
1188    4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23,
1189    6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21,
1190];
1191
1192/// Per-round additive constants `floor(2^32 * abs(sin(i + 1)))` (RFC 1321).
1193#[rustfmt::skip]
1194const MD5_K: [u32; 64] = [
1195    0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501,
1196    0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821,
1197    0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8,
1198    0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a,
1199    0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70,
1200    0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05, 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665,
1201    0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1,
1202    0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391,
1203];
1204
1205/// One-shot MD5 (RFC 1321). Sufficient for the small key-derivation inputs; not
1206/// a streaming API.
1207fn md5(input: &[u8]) -> [u8; 16] {
1208    let (mut a0, mut b0, mut c0, mut d0) = (
1209        0x6745_2301u32,
1210        0xefcd_ab89u32,
1211        0x98ba_dcfeu32,
1212        0x1032_5476u32,
1213    );
1214
1215    let mut msg = input.to_vec();
1216    let bitlen = (input.len() as u64).wrapping_mul(8);
1217    msg.push(0x80);
1218    while msg.len() % 64 != 56 {
1219        msg.push(0);
1220    }
1221    msg.extend_from_slice(&bitlen.to_le_bytes());
1222
1223    for chunk in msg.as_chunks::<64>().0 {
1224        let mut m = [0u32; 16];
1225        for (word, bytes) in m.iter_mut().zip(chunk.as_chunks::<4>().0) {
1226            *word = u32::from_le_bytes(*bytes);
1227        }
1228        let (mut a, mut b, mut c, mut d) = (a0, b0, c0, d0);
1229        for i in 0..64 {
1230            let (f, g) = match i {
1231                0..=15 => ((b & c) | (!b & d), i),
1232                16..=31 => ((d & b) | (!d & c), (5 * i + 1) % 16),
1233                32..=47 => (b ^ c ^ d, (3 * i + 5) % 16),
1234                _ => (c ^ (b | !d), (7 * i) % 16),
1235            };
1236            let f = f.wrapping_add(a).wrapping_add(MD5_K[i]).wrapping_add(m[g]);
1237            a = d;
1238            d = c;
1239            c = b;
1240            b = b.wrapping_add(f.rotate_left(MD5_S[i]));
1241        }
1242        a0 = a0.wrapping_add(a);
1243        b0 = b0.wrapping_add(b);
1244        c0 = c0.wrapping_add(c);
1245        d0 = d0.wrapping_add(d);
1246    }
1247
1248    let mut out = [0u8; 16];
1249    out[0..4].copy_from_slice(&a0.to_le_bytes());
1250    out[4..8].copy_from_slice(&b0.to_le_bytes());
1251    out[8..12].copy_from_slice(&c0.to_le_bytes());
1252    out[12..16].copy_from_slice(&d0.to_le_bytes());
1253    out
1254}
1255
1256#[cfg(test)]
1257mod tests {
1258    use super::*;
1259    use crate::object::Name;
1260
1261    fn hex(bytes: &[u8]) -> String {
1262        bytes.iter().map(|b| format!("{b:02x}")).collect()
1263    }
1264
1265    #[test]
1266    fn md5_known_vectors() {
1267        assert_eq!(hex(&md5(b"")), "d41d8cd98f00b204e9800998ecf8427e");
1268        assert_eq!(hex(&md5(b"abc")), "900150983cd24fb0d6963f7d28e17f72");
1269        assert_eq!(
1270            hex(&md5(b"The quick brown fox jumps over the lazy dog")),
1271            "9e107d9d372bb6826bd81d3542a419d6"
1272        );
1273    }
1274
1275    #[test]
1276    fn md5_spans_block_boundary() {
1277        // 56 bytes forces a second padded block.
1278        let input = [b'a'; 56];
1279        assert_eq!(hex(&md5(&input)), "3b0c8ac703f828b04c6c197006d17218");
1280    }
1281
1282    /// FIPS-197 Appendix C block vectors, both key sizes and both
1283    /// directions, held against the dispatching entry (the hardware path
1284    /// wherever this test runs on a CPU with AES instructions) AND the
1285    /// portable rounds directly, so neither path can drift.
1286    #[test]
1287    fn aes_block_known_vectors() {
1288        let plain: [u8; 16] = core::array::from_fn(|i| (i as u8) * 0x11);
1289        let key128: [u8; 16] = core::array::from_fn(|i| i as u8);
1290        let key256: [u8; 32] = core::array::from_fn(|i| i as u8);
1291
1292        type Encrypt = fn(&mut [u8; 16], &[[u8; 16]]);
1293        type Decrypt = fn(&mut [u8; 16], &[[u8; 16]], &[u8; 256]);
1294        let paths: [(Encrypt, Decrypt); 2] = [
1295            (aes_encrypt_block, aes_decrypt_block),
1296            (aes_encrypt_block_soft, aes_decrypt_block_soft),
1297        ];
1298        for (encrypt, decrypt) in paths {
1299            let mut s = plain;
1300            encrypt(&mut s, &aes_expand_key(&key128));
1301            assert_eq!(hex(&s), "69c4e0d86a7b0430d8cdb78070b4c55a");
1302            decrypt(&mut s, &aes_expand_key(&key128), &aes_inv_sbox());
1303            assert_eq!(s, plain);
1304
1305            let mut s = plain;
1306            encrypt(&mut s, &aes_expand_key(&key256));
1307            assert_eq!(hex(&s), "8ea2b7ca516745bfeafc49904b496089");
1308            decrypt(&mut s, &aes_expand_key(&key256), &aes_inv_sbox());
1309            assert_eq!(s, plain);
1310        }
1311    }
1312
1313    /// Multi-block CBC round-trips through the block paths, chaining
1314    /// included: what the R6 hash encrypts, decryption must invert.
1315    #[test]
1316    fn aes_cbc_round_trips_multiple_blocks() {
1317        let key: [u8; 16] = core::array::from_fn(|i| (i as u8).wrapping_mul(7));
1318        let iv: [u8; 16] = core::array::from_fn(|i| 0xa5 ^ (i as u8));
1319        let data: Vec<u8> = (0..64u8).collect();
1320        let ct = aes_cbc_encrypt_blocks(&key, &iv, &data);
1321        assert_eq!(ct.len(), data.len());
1322        assert_ne!(ct, data);
1323        assert_eq!(aes_cbc_decrypt_blocks(&key, &iv, &ct), data);
1324    }
1325
1326    /// NIST FIPS 180 vectors for the SHA-2 family, spanning the empty
1327    /// input, one block, and inputs long enough to cross block
1328    /// boundaries — the contract the hardware compressions must match.
1329    #[test]
1330    fn sha2_known_vectors() {
1331        assert_eq!(
1332            hex(&sha256(b"")),
1333            "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
1334        );
1335        assert_eq!(
1336            hex(&sha256(b"abc")),
1337            "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"
1338        );
1339        assert_eq!(
1340            hex(&sha256(&[b'a'; 200])),
1341            "c2a908d98f5df987ade41b5fce213067efbcc21ef2240212a41e54b5e7c28ae5"
1342        );
1343        assert_eq!(
1344            hex(&sha384(b"abc")),
1345            "cb00753f45a35e8bb5a03d699ac65007272c32ab0eded1631a8b605a43ff5bed\
1346             8086072ba1e7cc2358baeca134c825a7"
1347        );
1348        assert_eq!(
1349            hex(&sha512(b"abc")),
1350            "ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a\
1351             2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f"
1352        );
1353        assert_eq!(
1354            hex(&sha512(&[b'a'; 300])),
1355            "a6a77010dd9696c23831e6549de51724df332c2075039b75fcfe6c2e6de42fbd\
1356             3c80ed4073267e00c8c320712c3cdd9d65a96f90a3fe4a58a6b70a103be08e83"
1357        );
1358    }
1359
1360    /// The portable compressions must evolve state identically to the
1361    /// dispatching entries (the hardware path wherever this runs on a CPU
1362    /// with SHA instructions), block by block.
1363    #[test]
1364    fn sha2_soft_compressions_match_the_dispatch() {
1365        let block64: [u8; 64] = core::array::from_fn(|i| (i as u8).wrapping_mul(31));
1366        let mut via_dispatch = [0x6a09e667u32; 8];
1367        let mut via_soft = via_dispatch;
1368        sha256_compress(&mut via_dispatch, &block64);
1369        sha256_compress_soft(&mut via_soft, &block64);
1370        assert_eq!(via_dispatch, via_soft);
1371
1372        let block128: [u8; 128] = core::array::from_fn(|i| (i as u8).wrapping_mul(29));
1373        let mut via_dispatch = [0x6a09e667f3bcc908u64; 8];
1374        let mut via_soft = via_dispatch;
1375        sha512_compress(&mut via_dispatch, &block128);
1376        sha512_compress_soft(&mut via_soft, &block128);
1377        assert_eq!(via_dispatch, via_soft);
1378    }
1379
1380    /// The incremental hasher must match the one-shot digest whatever the
1381    /// slice boundaries: odd sizes, block-straddling feeds, empty updates.
1382    #[test]
1383    fn incremental_sha256_matches_the_one_shot() {
1384        let input: Vec<u8> = (0..300u16).map(|i| (i % 251) as u8).collect();
1385        for splits in [
1386            vec![0, 1, 3, 7, 60, 63, 64, 65, 37],
1387            vec![300],
1388            vec![128, 128, 44],
1389            vec![55, 9, 236],
1390        ] {
1391            let mut hasher = Sha256::new();
1392            let mut fed = 0usize;
1393            for len in splits {
1394                hasher.update(&input[fed..fed + len]);
1395                fed += len;
1396            }
1397            hasher.update(&input[fed..]);
1398            assert_eq!(hasher.finalize(), sha256(&input));
1399        }
1400        assert_eq!(Sha256::new().finalize(), sha256(b""));
1401        assert_eq!(Sha256::default().finalize(), sha256(b""));
1402    }
1403
1404    #[test]
1405    fn rc4_known_vector() {
1406        // Classic RC4 test vector: key "Key", plaintext "Plaintext".
1407        let ct = rc4(b"Key", b"Plaintext");
1408        assert_eq!(hex(&ct), "bbf316e8d940af0ad3");
1409        // Symmetric: decrypting the ciphertext returns the plaintext.
1410        assert_eq!(rc4(b"Key", &ct), b"Plaintext");
1411    }
1412
1413    // --- End-to-end fixture: build a V2/R3 (128-bit RC4) file encrypted under
1414    // caller-chosen user and owner passwords, then confirm the loader
1415    // decrypts it — transparently for the empty user password, and through
1416    // the password APIs for real ones. ---
1417
1418    const N: usize = 16; // 128-bit key
1419    const P: i32 = -44;
1420    const ID0: &[u8] = b"0123456789abcdef";
1421
1422    /// `/O` for the given owner and user passwords (Algorithm 3, R3).
1423    fn owner_entry(owner_pw: &[u8], user_pw: &[u8]) -> Vec<u8> {
1424        let mut d = md5(&pad_password(owner_pw));
1425        for _ in 0..50 {
1426            d = md5(&d[..N]);
1427        }
1428        let rc4key = d[..N].to_vec();
1429        let mut o = rc4(&rc4key, &pad_password(user_pw));
1430        for i in 1u8..=19 {
1431            let k: Vec<u8> = rc4key.iter().map(|b| b ^ i).collect();
1432            o = rc4(&k, &o);
1433        }
1434        o
1435    }
1436
1437    /// File key from `/O` for the given user password (Algorithm 2, R3).
1438    fn file_key(o: &[u8], user_pw: &[u8]) -> Vec<u8> {
1439        let mut input = Vec::new();
1440        input.extend_from_slice(&pad_password(user_pw));
1441        input.extend_from_slice(o);
1442        input.extend_from_slice(&(P as u32).to_le_bytes());
1443        input.extend_from_slice(ID0);
1444        let mut d = md5(&input);
1445        for _ in 0..50 {
1446            d = md5(&d[..N]);
1447        }
1448        d[..N].to_vec()
1449    }
1450
1451    /// `/U` for the given file key (Algorithm 5, R3).
1452    fn user_entry(key: &[u8]) -> Vec<u8> {
1453        let mut input = Vec::new();
1454        input.extend_from_slice(&PAD);
1455        input.extend_from_slice(ID0);
1456        let mut x = md5(&input).to_vec();
1457        x = rc4(key, &x);
1458        for i in 1u8..=19 {
1459            let k: Vec<u8> = key.iter().map(|b| b ^ i).collect();
1460            x = rc4(&k, &x);
1461        }
1462        x.resize(32, 0); // trailing padding is arbitrary
1463        x
1464    }
1465
1466    fn obj_key(key: &[u8], num: u32, gen: u16) -> Vec<u8> {
1467        let mut input = key.to_vec();
1468        input.extend_from_slice(&num.to_le_bytes()[..3]);
1469        input.extend_from_slice(&gen.to_le_bytes()[..2]);
1470        md5(&input)[..(key.len() + 5).min(16)].to_vec()
1471    }
1472
1473    fn hexstr(b: &[u8]) -> String {
1474        let mut s = String::from("<");
1475        for x in b {
1476            s.push_str(&format!("{x:02x}"));
1477        }
1478        s.push('>');
1479        s
1480    }
1481
1482    fn encrypted_fixture(u_override: Option<Vec<u8>>) -> Vec<u8> {
1483        encrypted_fixture_with(b"", b"", u_override)
1484    }
1485
1486    fn encrypted_fixture_with(
1487        user_pw: &[u8],
1488        owner_pw: &[u8],
1489        u_override: Option<Vec<u8>>,
1490    ) -> Vec<u8> {
1491        use pdfboss_testkit::PdfBuilder;
1492        let o = owner_entry(owner_pw, user_pw);
1493        let key = file_key(&o, user_pw);
1494        let u = u_override.unwrap_or_else(|| user_entry(&key));
1495
1496        let msg = rc4(&obj_key(&key, 3, 0), b"Top secret message");
1497        let stream = rc4(&obj_key(&key, 4, 0), b"decrypted stream body");
1498
1499        let mut b = PdfBuilder::new().version(1, 4);
1500        b.object(1, "<< /Type /Catalog /Pages 2 0 R >>");
1501        b.object(2, "<< /Type /Pages /Kids [] /Count 0 >>");
1502        b.object(3, &format!("<< /Msg {} >>", hexstr(&msg)));
1503        b.stream(4, "", &stream);
1504        b.object(
1505            9,
1506            &format!(
1507                "<< /Filter /Standard /V 2 /R 3 /Length 128 /P {P} /O {} /U {} >>",
1508                hexstr(&o),
1509                hexstr(&u)
1510            ),
1511        );
1512        let trailer = format!("/Encrypt 9 0 R /ID [{}{}]", hexstr(ID0), hexstr(ID0));
1513        b.trailer_extra(&trailer).build(1)
1514    }
1515
1516    #[test]
1517    fn document_load_decrypts_standard_rc4() {
1518        use crate::object::ObjRef;
1519        use crate::Document;
1520
1521        let doc = Document::load(encrypted_fixture(None)).expect("empty password opens the file");
1522
1523        let obj3 = doc.get(ObjRef { num: 3, gen: 0 }).unwrap();
1524        let msg = obj3
1525            .as_dict()
1526            .unwrap()
1527            .get("Msg")
1528            .unwrap()
1529            .as_str_bytes()
1530            .unwrap();
1531        assert_eq!(msg, b"Top secret message", "string decrypted");
1532
1533        let obj4 = doc.get(ObjRef { num: 4, gen: 0 }).unwrap();
1534        let data = doc.stream_data(obj4.as_stream().unwrap()).unwrap();
1535        assert_eq!(data, b"decrypted stream body", "stream decrypted");
1536    }
1537
1538    #[test]
1539    fn document_load_rejects_when_password_does_not_verify() {
1540        use crate::error::Error;
1541        use crate::Document;
1542
1543        // A `/U` that will not verify under the empty password stands in for a
1544        // real password-protected file: the loader must decline, not decrypt.
1545        let bad_u = vec![0u8; 32];
1546        let err = Document::load(encrypted_fixture(Some(bad_u)));
1547        assert!(matches!(err, Err(Error::Encrypted)));
1548    }
1549
1550    #[test]
1551    fn real_user_password_opens_an_rc4_file() {
1552        use crate::error::Error;
1553        use crate::object::ObjRef;
1554        use crate::Document;
1555
1556        let bytes = encrypted_fixture_with(b"hunter2", b"owner-secret", None);
1557        assert!(
1558            matches!(Document::load(bytes.clone()), Err(Error::Encrypted)),
1559            "without the password the file stays closed"
1560        );
1561        let doc = Document::load_with_password(bytes, "hunter2").expect("user password opens");
1562        let obj3 = doc.get(ObjRef { num: 3, gen: 0 }).unwrap();
1563        let msg = obj3.as_dict().unwrap().get("Msg").unwrap();
1564        assert_eq!(msg.as_str_bytes().unwrap(), b"Top secret message");
1565    }
1566
1567    #[test]
1568    fn owner_password_opens_an_rc4_file() {
1569        use crate::object::ObjRef;
1570        use crate::Document;
1571
1572        let bytes = encrypted_fixture_with(b"hunter2", b"owner-secret", None);
1573        let doc =
1574            Document::load_with_password(bytes, "owner-secret").expect("owner password opens");
1575        let obj3 = doc.get(ObjRef { num: 3, gen: 0 }).unwrap();
1576        let msg = obj3.as_dict().unwrap().get("Msg").unwrap();
1577        assert_eq!(msg.as_str_bytes().unwrap(), b"Top secret message");
1578    }
1579
1580    #[test]
1581    fn wrong_password_stays_encrypted() {
1582        use crate::error::Error;
1583        use crate::Document;
1584
1585        let bytes = encrypted_fixture_with(b"hunter2", b"owner-secret", None);
1586        let err = Document::load_with_password(bytes, "letmein");
1587        assert!(matches!(err, Err(Error::Encrypted)));
1588    }
1589
1590    #[test]
1591    fn real_passwords_open_an_aes256_r6_file() {
1592        use crate::error::Error;
1593        use crate::object::ObjRef;
1594        use crate::Document;
1595
1596        let bytes = encrypted_fixture_aesv3_with(6, "pässword".as_bytes(), b"owner-secret");
1597        assert!(
1598            matches!(Document::load(bytes.clone()), Err(Error::Encrypted)),
1599            "without the password the file stays closed"
1600        );
1601        for pw in ["pässword", "owner-secret"] {
1602            let doc = Document::load_with_password(bytes.clone(), pw)
1603                .unwrap_or_else(|_| panic!("{pw:?} opens the file"));
1604            let obj3 = doc.get(ObjRef { num: 3, gen: 0 }).unwrap();
1605            let msg = obj3.as_dict().unwrap().get("Msg").unwrap();
1606            assert_eq!(msg.as_str_bytes().unwrap(), b"AES-256 secret");
1607        }
1608        assert!(matches!(
1609            Document::load_with_password(bytes, "letmein"),
1610            Err(Error::Encrypted)
1611        ));
1612    }
1613
1614    #[test]
1615    fn empty_password_files_still_open_through_the_password_api() {
1616        use crate::object::ObjRef;
1617        use crate::Document;
1618
1619        // Passing a password to an empty-password file must not break it:
1620        // the empty user password still verifies... only if the caller's
1621        // password IS empty; a random one is simply wrong for this file.
1622        let doc = Document::load_with_password(encrypted_fixture(None), "")
1623            .expect("the empty password opens through the password API too");
1624        let obj3 = doc.get(ObjRef { num: 3, gen: 0 }).unwrap();
1625        let msg = obj3.as_dict().unwrap().get("Msg").unwrap();
1626        assert_eq!(msg.as_str_bytes().unwrap(), b"Top secret message");
1627    }
1628
1629    #[test]
1630    fn unsupported_handler_is_declined() {
1631        // A future/unknown handler version is declined so the caller reports the
1632        // file as encrypted-and-unsupported.
1633        let mut enc = Dict::new();
1634        enc.insert(Name("Filter".into()), Object::Name(Name("Standard".into())));
1635        enc.insert(Name("V".into()), Object::Int(6));
1636        enc.insert(Name("R".into()), Object::Int(7));
1637        enc.insert(Name("O".into()), Object::String(vec![0; 48]));
1638        enc.insert(Name("U".into()), Object::String(vec![0; 48]));
1639        enc.insert(Name("P".into()), Object::Int(-4));
1640        assert!(Decryptor::from_standard(&enc, ID0).is_none());
1641    }
1642
1643    // --- AES / SHA-2 known-answer vectors ---
1644
1645    #[test]
1646    fn aes_fips197_block_vectors() {
1647        // FIPS-197 Appendix C.1 (AES-128) and C.3 (AES-256), same plaintext.
1648        let pt: [u8; 16] = [
1649            0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd,
1650            0xee, 0xff,
1651        ];
1652        let key128: Vec<u8> = (0u8..16).collect();
1653        let rks = aes_expand_key(&key128);
1654        let mut b = pt;
1655        aes_encrypt_block(&mut b, &rks);
1656        assert_eq!(hex(&b), "69c4e0d86a7b0430d8cdb78070b4c55a");
1657        aes_decrypt_block(&mut b, &rks, &aes_inv_sbox());
1658        assert_eq!(b, pt, "AES-128 decrypt inverts encrypt");
1659
1660        let key256: Vec<u8> = (0u8..32).collect();
1661        let rks = aes_expand_key(&key256);
1662        let mut b = pt;
1663        aes_encrypt_block(&mut b, &rks);
1664        assert_eq!(hex(&b), "8ea2b7ca516745bfeafc49904b496089");
1665    }
1666
1667    #[test]
1668    fn sha2_vectors() {
1669        assert_eq!(
1670            hex(&sha256(b"abc")),
1671            "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"
1672        );
1673        assert_eq!(
1674            hex(&sha512(b"abc")),
1675            "ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a\
1676             2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f"
1677        );
1678        assert_eq!(
1679            hex(&sha384(b"abc")),
1680            "cb00753f45a35e8bb5a03d699ac65007272c32ab0eded1631a8b605a43ff5bed\
1681             8086072ba1e7cc2358baeca134c825a7"
1682        );
1683    }
1684
1685    fn pkcs7_pad(data: &[u8]) -> Vec<u8> {
1686        let pad = 16 - (data.len() % 16); // 1..=16 (a full block when aligned)
1687        let mut v = data.to_vec();
1688        v.resize(data.len() + pad, pad as u8);
1689        v
1690    }
1691
1692    #[test]
1693    fn aes_cbc_roundtrip() {
1694        let key: Vec<u8> = (0u8..16).collect();
1695        let iv = [0x24u8; 16];
1696        let pt = b"a message spanning several AES blocks exactly?!!";
1697        let ct = aes_cbc_encrypt_blocks(&key, &iv, &pkcs7_pad(pt));
1698        let mut val = iv.to_vec(); // PDF format: IV followed by ciphertext
1699        val.extend_from_slice(&ct);
1700        assert_eq!(aes_cbc_decrypt(&key, &val), pt);
1701    }
1702
1703    // --- AESV2 (V4/R4) end-to-end fixture ---
1704
1705    fn obj_key_aes(key: &[u8], num: u32, gen: u16) -> Vec<u8> {
1706        let mut input = key.to_vec();
1707        input.extend_from_slice(&num.to_le_bytes()[..3]);
1708        input.extend_from_slice(&gen.to_le_bytes()[..2]);
1709        input.extend_from_slice(b"sAlT");
1710        md5(&input)[..(key.len() + 5).min(16)].to_vec()
1711    }
1712
1713    /// Encrypts as a PDF AES value: a 16-byte IV followed by CBC ciphertext of
1714    /// the PKCS#7-padded plaintext.
1715    fn aes_encrypt_pdf(key: &[u8], pt: &[u8], iv: &[u8; 16]) -> Vec<u8> {
1716        let mut out = iv.to_vec();
1717        out.extend_from_slice(&aes_cbc_encrypt_blocks(key, iv, &pkcs7_pad(pt)));
1718        out
1719    }
1720
1721    fn encrypted_fixture_aesv2() -> Vec<u8> {
1722        use pdfboss_testkit::PdfBuilder;
1723        let o = owner_entry(b"", b"");
1724        let key = file_key(&o, b""); // R4 derivation matches R3 (EncryptMetadata true)
1725        let u = user_entry(&key);
1726        let iv = [0x11u8; 16];
1727        let msg = aes_encrypt_pdf(&obj_key_aes(&key, 3, 0), b"Top secret message", &iv);
1728        let stream = aes_encrypt_pdf(&obj_key_aes(&key, 4, 0), b"decrypted stream body", &iv);
1729
1730        let mut b = PdfBuilder::new().version(1, 5);
1731        b.object(1, "<< /Type /Catalog /Pages 2 0 R >>");
1732        b.object(2, "<< /Type /Pages /Kids [] /Count 0 >>");
1733        b.object(3, &format!("<< /Msg {} >>", hexstr(&msg)));
1734        b.stream(4, "", &stream);
1735        b.object(
1736            9,
1737            &format!(
1738                "<< /Filter /Standard /V 4 /R 4 /Length 128 /P {P} /O {} /U {} \
1739                 /CF << /StdCF << /CFM /AESV2 /Length 16 >> >> /StmF /StdCF /StrF /StdCF >>",
1740                hexstr(&o),
1741                hexstr(&u)
1742            ),
1743        );
1744        let trailer = format!("/Encrypt 9 0 R /ID [{}{}]", hexstr(ID0), hexstr(ID0));
1745        b.trailer_extra(&trailer).build(1)
1746    }
1747
1748    #[test]
1749    fn document_load_decrypts_aesv2() {
1750        use crate::object::ObjRef;
1751        use crate::Document;
1752        let doc = Document::load(encrypted_fixture_aesv2()).expect("AESV2 empty password opens");
1753        let obj3 = doc.get(ObjRef { num: 3, gen: 0 }).unwrap();
1754        let msg = obj3
1755            .as_dict()
1756            .unwrap()
1757            .get("Msg")
1758            .unwrap()
1759            .as_str_bytes()
1760            .unwrap();
1761        assert_eq!(msg, b"Top secret message");
1762        let obj4 = doc.get(ObjRef { num: 4, gen: 0 }).unwrap();
1763        assert_eq!(
1764            doc.stream_data(obj4.as_stream().unwrap()).unwrap(),
1765            b"decrypted stream body"
1766        );
1767    }
1768
1769    // --- AESV3 (V5/R5 and R6) end-to-end fixture ---
1770
1771    fn encrypted_fixture_aesv3(r: i64) -> Vec<u8> {
1772        encrypted_fixture_aesv3_with(r, b"", b"")
1773    }
1774
1775    fn encrypted_fixture_aesv3_with(r: i64, user_pw: &[u8], owner_pw: &[u8]) -> Vec<u8> {
1776        use pdfboss_testkit::PdfBuilder;
1777        let key: Vec<u8> = (0u8..32).map(|i| i ^ 0x5a).collect(); // arbitrary 256-bit file key
1778        let vsalt: [u8; 8] = [1, 2, 3, 4, 5, 6, 7, 8];
1779        let ksalt: [u8; 8] = [9, 10, 11, 12, 13, 14, 15, 16];
1780        let mut u = hash_2b(r, user_pw, &vsalt, &[]); // 32-byte validation hash
1781        u.extend_from_slice(&vsalt);
1782        u.extend_from_slice(&ksalt);
1783        let intermediate = hash_2b(r, user_pw, &ksalt, &[]);
1784        let ue = aes_cbc_encrypt_blocks(&intermediate, &[0u8; 16], &key);
1785        // The owner hashes additionally salt in the first 48 bytes of /U.
1786        let ovsalt: [u8; 8] = [21, 22, 23, 24, 25, 26, 27, 28];
1787        let oksalt: [u8; 8] = [31, 32, 33, 34, 35, 36, 37, 38];
1788        let mut o = hash_2b(r, owner_pw, &ovsalt, &u[..48]);
1789        o.extend_from_slice(&ovsalt);
1790        o.extend_from_slice(&oksalt);
1791        let ointermediate = hash_2b(r, owner_pw, &oksalt, &u[..48]);
1792        let oe = aes_cbc_encrypt_blocks(&ointermediate, &[0u8; 16], &key);
1793        let iv = [0x22u8; 16];
1794        let msg = aes_encrypt_pdf(&key, b"AES-256 secret", &iv);
1795        let stream = aes_encrypt_pdf(&key, b"AES-256 stream body", &iv);
1796
1797        let mut b = PdfBuilder::new().version(1, 7);
1798        b.object(1, "<< /Type /Catalog /Pages 2 0 R >>");
1799        b.object(2, "<< /Type /Pages /Kids [] /Count 0 >>");
1800        b.object(3, &format!("<< /Msg {} >>", hexstr(&msg)));
1801        b.stream(4, "", &stream);
1802        b.object(
1803            9,
1804            &format!(
1805                "<< /Filter /Standard /V 5 /R {r} /Length 256 /P {P} /U {} /UE {} \
1806                 /O {} /OE {} \
1807                 /CF << /StdCF << /CFM /AESV3 /Length 32 >> >> /StmF /StdCF /StrF /StdCF >>",
1808                hexstr(&u),
1809                hexstr(&ue),
1810                hexstr(&o),
1811                hexstr(&oe)
1812            ),
1813        );
1814        let trailer = format!("/Encrypt 9 0 R /ID [{}{}]", hexstr(ID0), hexstr(ID0));
1815        b.trailer_extra(&trailer).build(1)
1816    }
1817
1818    fn assert_aesv3_decrypts(r: i64) {
1819        use crate::object::ObjRef;
1820        use crate::Document;
1821        let doc = Document::load(encrypted_fixture_aesv3(r)).expect("AESV3 empty password opens");
1822        let obj3 = doc.get(ObjRef { num: 3, gen: 0 }).unwrap();
1823        let msg = obj3
1824            .as_dict()
1825            .unwrap()
1826            .get("Msg")
1827            .unwrap()
1828            .as_str_bytes()
1829            .unwrap();
1830        assert_eq!(msg, b"AES-256 secret", "R{r} string");
1831        let obj4 = doc.get(ObjRef { num: 4, gen: 0 }).unwrap();
1832        assert_eq!(
1833            doc.stream_data(obj4.as_stream().unwrap()).unwrap(),
1834            b"AES-256 stream body",
1835            "R{r} stream"
1836        );
1837    }
1838
1839    #[test]
1840    fn document_load_decrypts_aesv3_r5() {
1841        assert_aesv3_decrypts(5);
1842    }
1843
1844    #[test]
1845    fn document_load_decrypts_aesv3_r6() {
1846        assert_aesv3_decrypts(6); // exercises the iterated Algorithm 2.B hash
1847    }
1848}