Skip to main content

stet_pdf_reader/
crypto.rs

1// stet-pdf-reader
2// Copyright (c) 2026 Scott Bowman
3// SPDX-License-Identifier: Apache-2.0 OR MIT
4
5//! PDF encryption: Standard security handler (RC4 + AES-128/256).
6
7use crate::error::PdfError;
8use crate::objects::PdfDict;
9
10/// Encryption parameters extracted from the Encrypt dict.
11pub struct EncryptionState {
12    /// Encryption key (5-32 bytes).
13    key: Vec<u8>,
14    /// Version (1=RC4-40, 2=RC4-128, 4=AES-128, 5=AES-256).
15    #[allow(dead_code)]
16    version: i32,
17    /// Stream encryption method.
18    stm_method: CryptMethod,
19    /// String encryption method.
20    str_method: CryptMethod,
21}
22
23#[derive(Clone, Copy, PartialEq)]
24enum CryptMethod {
25    None,
26    RC4,
27    AesV2, // AES-128
28    AesV3, // AES-256
29}
30
31impl EncryptionState {
32    /// Try to decrypt with an empty user password.
33    /// Equivalent to `try_open_with_password(..., b"")`.
34    /// Returns `Err(PdfError::PasswordRequired)` if the empty password
35    /// does not match; callers that want to try a user-supplied password
36    /// should use [`Self::try_open_with_password`] instead.
37    pub fn try_open(
38        encrypt_dict: &PdfDict,
39        trailer: &PdfDict,
40        file_id: &[u8],
41    ) -> Result<Self, PdfError> {
42        Self::try_open_with_password(encrypt_dict, trailer, file_id, b"")
43    }
44
45    /// Try to decrypt with the given user password.
46    /// Returns `Ok(state)` on success and
47    /// `Err(PdfError::PasswordRequired)` when the password doesn't match.
48    pub fn try_open_with_password(
49        encrypt_dict: &PdfDict,
50        _trailer: &PdfDict,
51        file_id: &[u8],
52        password: &[u8],
53    ) -> Result<Self, PdfError> {
54        let v = encrypt_dict.get_int(b"V").unwrap_or(0) as i32;
55        let r = encrypt_dict.get_int(b"R").unwrap_or(0) as i32;
56        // Key length: prefer top-level /Length (in bits), then fall back to the
57        // crypt filter's /Length (in bytes) for V≄4 where the top-level key may
58        // be absent. Default to 40 bits (5 bytes) for older encryption.
59        let key_length = if let Some(len) = encrypt_dict.get_int(b"Length") {
60            len as usize / 8
61        } else if v >= 4 {
62            // Try CF/<filter>/Length (value is in bytes for crypt filter dicts)
63            let cf_len = encrypt_dict.get_dict(b"CF").and_then(|cf| {
64                let filter_name = encrypt_dict.get_name(b"StmF").unwrap_or(b"StdCF");
65                cf.get_dict(filter_name)
66                    .and_then(|f| f.get_int(b"Length").map(|n| n as usize))
67            });
68            cf_len.unwrap_or(16) // AES-128 default
69        } else {
70            5 // 40-bit RC4 default
71        };
72
73        let o_value = encrypt_dict
74            .get(b"O")
75            .and_then(|o| o.as_str())
76            .ok_or(PdfError::Other("Encrypt missing /O".into()))?
77            .to_vec();
78        let u_value = encrypt_dict
79            .get(b"U")
80            .and_then(|o| o.as_str())
81            .ok_or(PdfError::Other("Encrypt missing /U".into()))?
82            .to_vec();
83        let p_value = encrypt_dict.get_int(b"P").unwrap_or(0) as i32;
84
85        if v == 5 {
86            // AES-256 (PDF 2.0)
87            return Self::try_open_v5(encrypt_dict, &u_value, r, password);
88        }
89
90        // V4: check if both StmF and StrF are Identity — if so, nothing is encrypted
91        // (common for PDFs that only encrypt embedded files via /EFF). Skip password
92        // verification since we don't need a key to decrypt anything.
93        if v >= 4 {
94            let stm_name = encrypt_dict.get_name(b"StmF").unwrap_or(b"Identity");
95            let str_name = encrypt_dict.get_name(b"StrF").unwrap_or(b"Identity");
96            if stm_name == b"Identity" && str_name == b"Identity" {
97                return Ok(Self {
98                    key: Vec::new(),
99                    version: v,
100                    stm_method: CryptMethod::None,
101                    str_method: CryptMethod::None,
102                });
103            }
104        }
105
106        // Standard handler: compute encryption key from the given password.
107        let encrypt_metadata = encrypt_dict
108            .get(b"EncryptMetadata")
109            .and_then(|o| match o {
110                crate::objects::PdfObj::Bool(b) => Some(*b),
111                _ => None,
112            })
113            .unwrap_or(true);
114        let key = compute_encryption_key(
115            password,
116            &o_value,
117            p_value,
118            file_id,
119            key_length,
120            r,
121            encrypt_metadata,
122        );
123
124        // Verify against /U value
125        if !verify_user_password(&key, &u_value, file_id, r) {
126            return Err(PdfError::PasswordRequired);
127        }
128
129        // Acrobat quirk: when V=4 specifies a sub-128-bit /Length (e.g. 40-bit
130        // RC4 keys in some Adobe InDesign / PDF Library 8.0 files), Acrobat
131        // pads the file encryption key with NUL bytes out to 128 bits before
132        // deriving per-object keys via Algorithm 1. The /U value is still
133        // verified against the un-padded key. Without this padding, every
134        // stream and string in the file decrypts to garbage.
135        let key = if v >= 4 && key.len() < 16 {
136            let mut padded = key;
137            padded.resize(16, 0);
138            padded
139        } else {
140            key
141        };
142
143        let (stm_method, str_method) = if v >= 4 {
144            // Parse CF dict for methods
145            let cf = encrypt_dict.get_dict(b"CF");
146            let stm_name = encrypt_dict.get_name(b"StmF").unwrap_or(b"Identity");
147            let str_name = encrypt_dict.get_name(b"StrF").unwrap_or(b"Identity");
148            (
149                parse_crypt_method(cf, stm_name),
150                parse_crypt_method(cf, str_name),
151            )
152        } else {
153            (CryptMethod::RC4, CryptMethod::RC4)
154        };
155
156        Ok(Self {
157            key,
158            version: v,
159            stm_method,
160            str_method,
161        })
162    }
163
164    fn try_open_v5(
165        encrypt_dict: &PdfDict,
166        u_value: &[u8],
167        r: i32,
168        password: &[u8],
169    ) -> Result<Self, PdfError> {
170        // AES-256: R=5 or R=6
171        if u_value.len() < 48 {
172            return Err(PdfError::Other("AES-256: /U too short".into()));
173        }
174
175        let validation_salt = &u_value[32..40];
176        let key_salt = &u_value[40..48];
177
178        // Hash the given password with validation salt (u_key is empty for user password).
179        let hash = if r >= 6 {
180            compute_hash_r6(password, validation_salt, b"")
181        } else {
182            sha256(&[password, validation_salt])
183        };
184        if hash[..] != u_value[..32] {
185            return Err(PdfError::PasswordRequired);
186        }
187
188        // Derive file encryption key (u_key is empty for user password).
189        let key_hash = if r >= 6 {
190            compute_hash_r6(password, key_salt, b"")
191        } else {
192            sha256(&[password, key_salt])
193        };
194
195        // Decrypt UE with this key to get file encryption key
196        let ue = encrypt_dict
197            .get(b"UE")
198            .and_then(|o| o.as_str())
199            .ok_or(PdfError::Other("Encrypt missing /UE".into()))?;
200
201        if ue.len() < 32 {
202            return Err(PdfError::Other("AES-256: /UE too short".into()));
203        }
204
205        // Decrypt UE without padding removal — the raw 32-byte output IS the file key
206        let file_key = aes_cbc_decrypt_no_pad(&key_hash, &[0u8; 16], &ue[..32]);
207
208        Ok(Self {
209            key: file_key,
210            version: 5,
211            stm_method: CryptMethod::AesV3,
212            str_method: CryptMethod::AesV3,
213        })
214    }
215
216    /// Decrypt a string.
217    pub fn decrypt_string(&self, data: &[u8], obj_num: u32, gen_num: u16) -> Vec<u8> {
218        self.decrypt_data(data, obj_num, gen_num, self.str_method)
219    }
220
221    /// Decrypt a stream.
222    pub fn decrypt_stream(&self, data: &[u8], obj_num: u32, gen_num: u16) -> Vec<u8> {
223        self.decrypt_data(data, obj_num, gen_num, self.stm_method)
224    }
225
226    fn decrypt_data(
227        &self,
228        data: &[u8],
229        obj_num: u32,
230        gen_num: u16,
231        method: CryptMethod,
232    ) -> Vec<u8> {
233        match method {
234            CryptMethod::None => data.to_vec(),
235            CryptMethod::RC4 => {
236                let obj_key = compute_object_key(&self.key, obj_num, gen_num, false);
237                rc4(&obj_key, data)
238            }
239            CryptMethod::AesV2 => {
240                let obj_key = compute_object_key(&self.key, obj_num, gen_num, true);
241                if data.len() < 16 {
242                    return data.to_vec();
243                }
244                aes_cbc_decrypt(&obj_key, &data[..16], &data[16..])
245                    .unwrap_or_else(|_| data.to_vec())
246            }
247            CryptMethod::AesV3 => {
248                if data.len() < 16 {
249                    return data.to_vec();
250                }
251                aes_cbc_decrypt(&self.key, &data[..16], &data[16..])
252                    .unwrap_or_else(|_| data.to_vec())
253            }
254        }
255    }
256}
257
258fn parse_crypt_method(cf: Option<&PdfDict>, name: &[u8]) -> CryptMethod {
259    if name == b"Identity" {
260        return CryptMethod::None;
261    }
262    let cfm = cf
263        .and_then(|d| d.get_dict(name))
264        .and_then(|d| d.get_name(b"CFM"));
265    match cfm {
266        Some(b"None") => CryptMethod::None,
267        Some(b"V2") => CryptMethod::RC4,
268        Some(b"AESV2") => CryptMethod::AesV2,
269        Some(b"AESV3") => CryptMethod::AesV3,
270        _ => CryptMethod::RC4,
271    }
272}
273
274/// Compute encryption key (Algorithm 2, PDF spec).
275fn compute_encryption_key(
276    password: &[u8],
277    o_value: &[u8],
278    p: i32,
279    file_id: &[u8],
280    key_length: usize,
281    revision: i32,
282    encrypt_metadata: bool,
283) -> Vec<u8> {
284    // Pad password to 32 bytes
285    let padded = pad_password(password);
286
287    let mut digest = md5::Context::new();
288    digest.consume(padded);
289    digest.consume(o_value);
290    digest.consume(p.to_le_bytes());
291    digest.consume(file_id);
292
293    // Step (f): if metadata is not encrypted (R >= 4), include 0xFFFFFFFF
294    if revision >= 4 && !encrypt_metadata {
295        digest.consume([0xFFu8; 4]);
296    }
297
298    let mut hash = digest.compute().0;
299
300    if revision >= 3 {
301        for _ in 0..50 {
302            hash = md5::compute(&hash[..key_length]).0;
303        }
304    }
305
306    hash[..key_length].to_vec()
307}
308
309/// Verify user password (Algorithm 6/7).
310fn verify_user_password(key: &[u8], u_value: &[u8], file_id: &[u8], revision: i32) -> bool {
311    if revision <= 2 {
312        // Algorithm 6: encrypt padding with key, compare to /U
313        let encrypted = rc4(key, &PASSWORD_PADDING);
314        encrypted == u_value
315    } else {
316        // Algorithm 7: MD5(padding + file_id), then 20 rounds of RC4
317        let mut digest = md5::Context::new();
318        digest.consume(PASSWORD_PADDING);
319        digest.consume(file_id);
320        let hash = digest.compute().0;
321
322        let mut result = rc4(key, &hash);
323        for i in 1..=19u8 {
324            let derived_key: Vec<u8> = key.iter().map(|&b| b ^ i).collect();
325            result = rc4(&derived_key, &result);
326        }
327
328        // Compare first 16 bytes
329        u_value.len() >= 16 && result[..16] == u_value[..16]
330    }
331}
332
333/// Compute per-object encryption key.
334fn compute_object_key(key: &[u8], obj_num: u32, gen_num: u16, is_aes: bool) -> Vec<u8> {
335    let mut digest = md5::Context::new();
336    digest.consume(key);
337    digest.consume(&obj_num.to_le_bytes()[..3]);
338    digest.consume(gen_num.to_le_bytes());
339    if is_aes {
340        digest.consume(b"sAlT");
341    }
342    let hash = digest.compute().0;
343    let len = (key.len() + 5).min(16);
344    hash[..len].to_vec()
345}
346
347/// RC4 encryption/decryption (symmetric).
348fn rc4(key: &[u8], data: &[u8]) -> Vec<u8> {
349    let mut s: Vec<u8> = (0..=255u8).collect();
350    let mut j: u8 = 0;
351    for i in 0..256 {
352        j = j.wrapping_add(s[i]).wrapping_add(key[i % key.len()]);
353        s.swap(i, j as usize);
354    }
355
356    let mut i: u8 = 0;
357    j = 0;
358    let mut result = Vec::with_capacity(data.len());
359    for &byte in data {
360        i = i.wrapping_add(1);
361        j = j.wrapping_add(s[i as usize]);
362        s.swap(i as usize, j as usize);
363        let k = s[(s[i as usize].wrapping_add(s[j as usize])) as usize];
364        result.push(byte ^ k);
365    }
366    result
367}
368
369/// AES-CBC decryption without PKCS#7 padding removal (for key derivation).
370fn aes_cbc_decrypt_no_pad(key: &[u8], iv: &[u8], data: &[u8]) -> Vec<u8> {
371    if data.is_empty() || !data.len().is_multiple_of(16) {
372        return data.to_vec();
373    }
374    let mut prev_block = [0u8; 16];
375    let iv_len = iv.len().min(16);
376    prev_block[..iv_len].copy_from_slice(&iv[..iv_len]);
377    let round_keys = aes_key_expansion(key);
378    let mut result = Vec::with_capacity(data.len());
379    for chunk in data.chunks(16) {
380        let decrypted = aes_decrypt_block(chunk, &round_keys);
381        for i in 0..16 {
382            result.push(decrypted[i] ^ prev_block[i]);
383        }
384        prev_block.copy_from_slice(chunk);
385    }
386    result
387}
388
389/// AES-CBC decryption.
390fn aes_cbc_decrypt(key: &[u8], iv: &[u8], data: &[u8]) -> Result<Vec<u8>, PdfError> {
391    if data.is_empty() {
392        return Ok(Vec::new());
393    }
394    if !data.len().is_multiple_of(16) {
395        return Err(PdfError::Other("AES data not block-aligned".into()));
396    }
397
398    let mut prev_block = [0u8; 16];
399    let iv_len = iv.len().min(16);
400    prev_block[..iv_len].copy_from_slice(&iv[..iv_len]);
401
402    let round_keys = aes_key_expansion(key);
403
404    let mut result = Vec::with_capacity(data.len());
405    for chunk in data.chunks(16) {
406        let decrypted = aes_decrypt_block(chunk, &round_keys);
407        for i in 0..16 {
408            result.push(decrypted[i] ^ prev_block[i]);
409        }
410        prev_block.copy_from_slice(chunk);
411    }
412
413    // Remove PKCS#7 padding
414    if let Some(&pad_len) = result.last() {
415        let pad_len = pad_len as usize;
416        if pad_len > 0
417            && pad_len <= 16
418            && result.len() >= pad_len
419            && result[result.len() - pad_len..]
420                .iter()
421                .all(|&b| b == pad_len as u8)
422        {
423            result.truncate(result.len() - pad_len);
424        }
425    }
426
427    Ok(result)
428}
429
430/// AES-CBC encryption (no padding, data must be block-aligned).
431fn aes_cbc_encrypt(key: &[u8], iv: &[u8], data: &[u8]) -> Vec<u8> {
432    let round_keys = aes_key_expansion(key);
433    let mut prev = [0u8; 16];
434    let iv_len = iv.len().min(16);
435    prev[..iv_len].copy_from_slice(&iv[..iv_len]);
436
437    let mut result = Vec::with_capacity(data.len());
438    for chunk in data.chunks(16) {
439        let mut block = [0u8; 16];
440        let n = chunk.len().min(16);
441        block[..n].copy_from_slice(&chunk[..n]);
442        for i in 0..16 {
443            block[i] ^= prev[i];
444        }
445        let encrypted = aes_encrypt_block(&block, &round_keys);
446        result.extend_from_slice(&encrypted);
447        prev = encrypted;
448    }
449    result
450}
451
452/// AES single-block encryption.
453fn aes_encrypt_block(block: &[u8; 16], round_keys: &[[u8; 16]]) -> [u8; 16] {
454    let nr = round_keys.len() - 1;
455    let mut state = *block;
456
457    // AddRoundKey with first round key
458    for i in 0..16 {
459        state[i] ^= round_keys[0][i];
460    }
461
462    for round in 1..nr {
463        for b in &mut state {
464            *b = SBOX[*b as usize];
465        }
466        shift_rows(&mut state);
467        mix_columns(&mut state);
468        for i in 0..16 {
469            state[i] ^= round_keys[round][i];
470        }
471    }
472
473    // Last round (no MixColumns)
474    for b in &mut state {
475        *b = SBOX[*b as usize];
476    }
477    shift_rows(&mut state);
478    for i in 0..16 {
479        state[i] ^= round_keys[nr][i];
480    }
481
482    state
483}
484
485fn shift_rows(state: &mut [u8; 16]) {
486    // Row 1: shift left 1
487    let t = state[1];
488    state[1] = state[5];
489    state[5] = state[9];
490    state[9] = state[13];
491    state[13] = t;
492    // Row 2: shift left 2
493    let (t0, t1) = (state[2], state[6]);
494    state[2] = state[10];
495    state[6] = state[14];
496    state[10] = t0;
497    state[14] = t1;
498    // Row 3: shift left 3 (= shift right 1)
499    let t = state[15];
500    state[15] = state[11];
501    state[11] = state[7];
502    state[7] = state[3];
503    state[3] = t;
504}
505
506fn mix_columns(state: &mut [u8; 16]) {
507    for col in 0..4 {
508        let i = col * 4;
509        let (s0, s1, s2, s3) = (state[i], state[i + 1], state[i + 2], state[i + 3]);
510        state[i] = gmul(0x02, s0) ^ gmul(0x03, s1) ^ s2 ^ s3;
511        state[i + 1] = s0 ^ gmul(0x02, s1) ^ gmul(0x03, s2) ^ s3;
512        state[i + 2] = s0 ^ s1 ^ gmul(0x02, s2) ^ gmul(0x03, s3);
513        state[i + 3] = gmul(0x03, s0) ^ s1 ^ s2 ^ gmul(0x02, s3);
514    }
515}
516
517/// Algorithm 2.B from ISO 32000-2: iterative hash for R=6 encryption.
518fn compute_hash_r6(password: &[u8], salt: &[u8], u_key: &[u8]) -> [u8; 32] {
519    use sha2::Digest;
520
521    // Step 1: initial SHA-256 hash of (password + salt + u_key)
522    let mut hasher = sha2::Sha256::new();
523    hasher.update(password);
524    hasher.update(salt);
525    if !u_key.is_empty() {
526        hasher.update(u_key);
527    }
528    let initial: [u8; 32] = hasher.finalize().into();
529    // K holds the full hash output (32/48/64 bytes depending on hash function)
530    let mut k: Vec<u8> = initial.to_vec();
531
532    let mut round: u32 = 0;
533    loop {
534        // Step 2a: K1 = 64 repetitions of (password + K + u_key)
535        let segment_len = password.len() + k.len() + u_key.len();
536        let mut k1 = Vec::with_capacity(segment_len * 64);
537        for _ in 0..64 {
538            k1.extend_from_slice(password);
539            k1.extend_from_slice(&k);
540            if !u_key.is_empty() {
541                k1.extend_from_slice(u_key);
542            }
543        }
544
545        // Step 2b: AES-128-CBC encrypt K1 using K[0..16] as key, K[16..32] as IV
546        let e = aes_cbc_encrypt(&k[..16], &k[16..32], &k1);
547
548        // Step 2c: determine hash function from first 16 bytes of E mod 3
549        let mut mod3: u32 = 0;
550        for &b in &e[..16] {
551            mod3 = (mod3 * 256 + b as u32) % 3;
552        }
553
554        // Step 2d: hash all of E — keep FULL output (affects K1 size in next round)
555        k = match mod3 {
556            0 => {
557                let mut h = sha2::Sha256::new();
558                h.update(&e);
559                h.finalize().to_vec()
560            }
561            1 => {
562                let mut h = sha2::Sha384::new();
563                h.update(&e);
564                h.finalize().to_vec()
565            }
566            _ => {
567                let mut h = sha2::Sha512::new();
568                h.update(&e);
569                h.finalize().to_vec()
570            }
571        };
572
573        // Step 2f: termination check (increment first, matching spec's round numbering)
574        round += 1;
575        let last_byte = *e.last().unwrap_or(&0);
576        if round >= 64 && (last_byte as u32) <= round - 32 {
577            break;
578        }
579        if round > 1000 {
580            break;
581        }
582    }
583
584    let mut result = [0u8; 32];
585    result.copy_from_slice(&k[..32]);
586    result
587}
588
589/// SHA-256 hash (for AES-256).
590fn sha256(inputs: &[&[u8]]) -> [u8; 32] {
591    sha256_impl(inputs)
592}
593
594// --- AES implementation (128/256-bit) ---
595// Standard AES following FIPS 197.
596
597const SBOX: [u8; 256] = [
598    0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76,
599    0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0,
600    0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15,
601    0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75,
602    0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84,
603    0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf,
604    0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8,
605    0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2,
606    0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73,
607    0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb,
608    0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79,
609    0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08,
610    0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a,
611    0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e,
612    0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf,
613    0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16,
614];
615
616const INV_SBOX: [u8; 256] = [
617    0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb,
618    0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb,
619    0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e,
620    0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25,
621    0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92,
622    0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84,
623    0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06,
624    0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b,
625    0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73,
626    0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e,
627    0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b,
628    0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4,
629    0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f,
630    0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef,
631    0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61,
632    0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d,
633];
634
635const RCON: [u8; 11] = [
636    0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36,
637];
638
639const PASSWORD_PADDING: [u8; 32] = [
640    0x28, 0xBF, 0x4E, 0x5E, 0x4E, 0x75, 0x8A, 0x41, 0x64, 0x00, 0x4E, 0x56, 0xFF, 0xFA, 0x01, 0x08,
641    0x2E, 0x2E, 0x00, 0xB6, 0xD0, 0x68, 0x3E, 0x80, 0x2F, 0x0C, 0xA9, 0xFE, 0x64, 0x53, 0x69, 0x7A,
642];
643
644fn pad_password(password: &[u8]) -> [u8; 32] {
645    let mut padded = [0u8; 32];
646    let len = password.len().min(32);
647    padded[..len].copy_from_slice(&password[..len]);
648    let pad_len = 32 - len;
649    padded[len..].copy_from_slice(&PASSWORD_PADDING[..pad_len]);
650    padded
651}
652
653/// GF(2^8) multiply for MixColumns.
654fn gmul(a: u8, b: u8) -> u8 {
655    let mut result = 0u8;
656    let mut a = a;
657    let mut b = b;
658    for _ in 0..8 {
659        if b & 1 != 0 {
660            result ^= a;
661        }
662        let hi = a & 0x80;
663        a <<= 1;
664        if hi != 0 {
665            a ^= 0x1b;
666        }
667        b >>= 1;
668    }
669    result
670}
671
672fn aes_key_expansion(key: &[u8]) -> Vec<[u8; 16]> {
673    let nk = key.len() / 4; // 4 for AES-128, 8 for AES-256
674    let nr = nk + 6; // 10 for 128, 14 for 256
675    let nw = 4 * (nr + 1);
676
677    let mut w = vec![0u32; nw];
678    for i in 0..nk {
679        w[i] = u32::from_be_bytes([key[4 * i], key[4 * i + 1], key[4 * i + 2], key[4 * i + 3]]);
680    }
681
682    for i in nk..nw {
683        let mut temp = w[i - 1];
684        if i % nk == 0 {
685            // RotWord + SubWord + Rcon
686            temp = temp.rotate_left(8);
687            let bytes = temp.to_be_bytes();
688            temp = u32::from_be_bytes([
689                SBOX[bytes[0] as usize],
690                SBOX[bytes[1] as usize],
691                SBOX[bytes[2] as usize],
692                SBOX[bytes[3] as usize],
693            ]);
694            temp ^= (RCON[i / nk] as u32) << 24;
695        } else if nk > 6 && i % nk == 4 {
696            let bytes = temp.to_be_bytes();
697            temp = u32::from_be_bytes([
698                SBOX[bytes[0] as usize],
699                SBOX[bytes[1] as usize],
700                SBOX[bytes[2] as usize],
701                SBOX[bytes[3] as usize],
702            ]);
703        }
704        w[i] = w[i - nk] ^ temp;
705    }
706
707    // Convert to round keys
708    let mut round_keys = Vec::with_capacity(nr + 1);
709    for i in 0..=nr {
710        let mut rk = [0u8; 16];
711        for j in 0..4 {
712            let bytes = w[4 * i + j].to_be_bytes();
713            rk[4 * j..4 * j + 4].copy_from_slice(&bytes);
714        }
715        round_keys.push(rk);
716    }
717    round_keys
718}
719
720fn aes_decrypt_block(block: &[u8], round_keys: &[[u8; 16]]) -> [u8; 16] {
721    let nr = round_keys.len() - 1;
722    let mut state = [0u8; 16];
723    state.copy_from_slice(&block[..16]);
724
725    // AddRoundKey with last round key
726    for i in 0..16 {
727        state[i] ^= round_keys[nr][i];
728    }
729
730    for round in (1..nr).rev() {
731        inv_shift_rows(&mut state);
732        for b in &mut state {
733            *b = INV_SBOX[*b as usize];
734        }
735        for i in 0..16 {
736            state[i] ^= round_keys[round][i];
737        }
738        inv_mix_columns(&mut state);
739    }
740
741    // Last round (no InvMixColumns)
742    inv_shift_rows(&mut state);
743    for b in &mut state {
744        *b = INV_SBOX[*b as usize];
745    }
746    for i in 0..16 {
747        state[i] ^= round_keys[0][i];
748    }
749
750    state
751}
752
753fn inv_shift_rows(state: &mut [u8; 16]) {
754    // Row 1: shift right 1
755    let t = state[13];
756    state[13] = state[9];
757    state[9] = state[5];
758    state[5] = state[1];
759    state[1] = t;
760    // Row 2: shift right 2
761    let (t0, t1) = (state[2], state[6]);
762    state[2] = state[10];
763    state[6] = state[14];
764    state[10] = t0;
765    state[14] = t1;
766    // Row 3: shift right 3 (= shift left 1)
767    let t = state[3];
768    state[3] = state[7];
769    state[7] = state[11];
770    state[11] = state[15];
771    state[15] = t;
772}
773
774fn inv_mix_columns(state: &mut [u8; 16]) {
775    for col in 0..4 {
776        let i = col * 4;
777        let (s0, s1, s2, s3) = (state[i], state[i + 1], state[i + 2], state[i + 3]);
778        state[i] = gmul(0x0e, s0) ^ gmul(0x0b, s1) ^ gmul(0x0d, s2) ^ gmul(0x09, s3);
779        state[i + 1] = gmul(0x09, s0) ^ gmul(0x0e, s1) ^ gmul(0x0b, s2) ^ gmul(0x0d, s3);
780        state[i + 2] = gmul(0x0d, s0) ^ gmul(0x09, s1) ^ gmul(0x0e, s2) ^ gmul(0x0b, s3);
781        state[i + 3] = gmul(0x0b, s0) ^ gmul(0x0d, s1) ^ gmul(0x09, s2) ^ gmul(0x0e, s3);
782    }
783}
784
785// Minimal SHA-256 for AES-256 password validation
786fn sha256_impl(inputs: &[&[u8]]) -> [u8; 32] {
787    let mut h: [u32; 8] = [
788        0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab,
789        0x5be0cd19,
790    ];
791
792    const K: [u32; 64] = [
793        0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4,
794        0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe,
795        0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f,
796        0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
797        0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc,
798        0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b,
799        0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116,
800        0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
801        0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7,
802        0xc67178f2,
803    ];
804
805    // Collect all input bytes
806    let mut message = Vec::new();
807    for input in inputs {
808        message.extend_from_slice(input);
809    }
810    let bit_len = (message.len() as u64) * 8;
811
812    // Pad
813    message.push(0x80);
814    while message.len() % 64 != 56 {
815        message.push(0);
816    }
817    message.extend_from_slice(&bit_len.to_be_bytes());
818
819    // Process blocks
820    for block in message.chunks(64) {
821        let mut w = [0u32; 64];
822        for i in 0..16 {
823            w[i] = u32::from_be_bytes([
824                block[4 * i],
825                block[4 * i + 1],
826                block[4 * i + 2],
827                block[4 * i + 3],
828            ]);
829        }
830        for i in 16..64 {
831            let s0 = w[i - 15].rotate_right(7) ^ w[i - 15].rotate_right(18) ^ (w[i - 15] >> 3);
832            let s1 = w[i - 2].rotate_right(17) ^ w[i - 2].rotate_right(19) ^ (w[i - 2] >> 10);
833            w[i] = w[i - 16]
834                .wrapping_add(s0)
835                .wrapping_add(w[i - 7])
836                .wrapping_add(s1);
837        }
838
839        let [mut a, mut b, mut c, mut d, mut e, mut f, mut g, mut hh] = h;
840        for i in 0..64 {
841            let s1 = e.rotate_right(6) ^ e.rotate_right(11) ^ e.rotate_right(25);
842            let ch = (e & f) ^ (!e & g);
843            let t1 = hh
844                .wrapping_add(s1)
845                .wrapping_add(ch)
846                .wrapping_add(K[i])
847                .wrapping_add(w[i]);
848            let s0 = a.rotate_right(2) ^ a.rotate_right(13) ^ a.rotate_right(22);
849            let maj = (a & b) ^ (a & c) ^ (b & c);
850            let t2 = s0.wrapping_add(maj);
851            hh = g;
852            g = f;
853            f = e;
854            e = d.wrapping_add(t1);
855            d = c;
856            c = b;
857            b = a;
858            a = t1.wrapping_add(t2);
859        }
860        h[0] = h[0].wrapping_add(a);
861        h[1] = h[1].wrapping_add(b);
862        h[2] = h[2].wrapping_add(c);
863        h[3] = h[3].wrapping_add(d);
864        h[4] = h[4].wrapping_add(e);
865        h[5] = h[5].wrapping_add(f);
866        h[6] = h[6].wrapping_add(g);
867        h[7] = h[7].wrapping_add(hh);
868    }
869
870    let mut result = [0u8; 32];
871    for (i, &val) in h.iter().enumerate() {
872        result[4 * i..4 * i + 4].copy_from_slice(&val.to_be_bytes());
873    }
874    result
875}
876
877#[cfg(test)]
878mod tests {
879    use super::*;
880
881    #[test]
882    fn rc4_roundtrip() {
883        let key = b"secret";
884        let plaintext = b"Hello, World!";
885        let encrypted = rc4(key, plaintext);
886        let decrypted = rc4(key, &encrypted);
887        assert_eq!(&decrypted, plaintext);
888    }
889
890    #[test]
891    fn aes128_known_vector() {
892        // FIPS 197 Appendix B test vector
893        let key = [
894            0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf,
895            0x4f, 0x3c,
896        ];
897        let plaintext = [
898            0x32, 0x43, 0xf6, 0xa8, 0x88, 0x5a, 0x30, 0x8d, 0x31, 0x31, 0x98, 0xa2, 0xe0, 0x37,
899            0x07, 0x34,
900        ];
901        let expected_cipher = [
902            0x39, 0x25, 0x84, 0x1d, 0x02, 0xdc, 0x09, 0xfb, 0xdc, 0x11, 0x85, 0x97, 0x19, 0x6a,
903            0x0b, 0x32,
904        ];
905
906        // Verify decryption: decrypt(encrypt(plain)) == plain
907        let round_keys = aes_key_expansion(&key);
908        let decrypted = aes_decrypt_block(&expected_cipher, &round_keys);
909        assert_eq!(decrypted, plaintext);
910    }
911
912    #[test]
913    fn sha256_empty() {
914        let hash = sha256(&[b""]);
915        // SHA-256 of empty string
916        let expected = [
917            0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14, 0x9a, 0xfb, 0xf4, 0xc8, 0x99, 0x6f,
918            0xb9, 0x24, 0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b, 0x93, 0x4c, 0xa4, 0x95, 0x99, 0x1b,
919            0x78, 0x52, 0xb8, 0x55,
920        ];
921        assert_eq!(hash, expected);
922    }
923
924    #[test]
925    fn sha256_abc() {
926        let hash = sha256(&[b"abc"]);
927        let expected = [
928            0xba, 0x78, 0x16, 0xbf, 0x8f, 0x01, 0xcf, 0xea, 0x41, 0x41, 0x40, 0xde, 0x5d, 0xae,
929            0x22, 0x23, 0xb0, 0x03, 0x61, 0xa3, 0x96, 0x17, 0x7a, 0x9c, 0xb4, 0x10, 0xff, 0x61,
930            0xf2, 0x00, 0x15, 0xad,
931        ];
932        assert_eq!(hash, expected);
933    }
934
935    #[test]
936    fn password_padding() {
937        let padded = pad_password(b"");
938        assert_eq!(padded, PASSWORD_PADDING);
939
940        let padded = pad_password(b"test");
941        assert_eq!(&padded[..4], b"test");
942        assert_eq!(&padded[4..], &PASSWORD_PADDING[..28]);
943    }
944
945    #[test]
946    fn r6_hash_user_password() {
947        // From pdf_samples/0000120.pdf: R=6, empty user password
948        let validation_salt = [0x94, 0x92, 0x7c, 0x84, 0x96, 0xaf, 0xd8, 0x92];
949        let expected_hash = [
950            0x20, 0x23, 0xa0, 0xa6, 0x05, 0x6b, 0x38, 0x54, 0x42, 0xb0, 0xcb, 0x1a, 0x16, 0x5c,
951            0xb4, 0xf9, 0x29, 0x31, 0xec, 0xd9, 0xaf, 0x4b, 0xd5, 0xc2, 0x32, 0xdf, 0xb6, 0xbc,
952            0x8b, 0xe7, 0xb4, 0x05,
953        ];
954        let result = compute_hash_r6(b"", &validation_salt, b"");
955        assert_eq!(result, expected_hash, "R=6 validation hash mismatch");
956    }
957
958    #[test]
959    fn r6_key_derivation() {
960        // From pdf_samples/0000120.pdf: key derivation
961        let key_salt = [0xa2, 0x33, 0xb1, 0x19, 0xb7, 0x3f, 0xe6, 0xc2];
962        let expected_key_hash: [u8; 32] = [
963            0x0b, 0xdb, 0xaf, 0x8c, 0xa6, 0x25, 0x6c, 0xcf, 0xb1, 0xba, 0x29, 0x4d, 0xeb, 0x54,
964            0xc3, 0x83, 0xfd, 0xcc, 0x11, 0x90, 0xcf, 0x6c, 0xf5, 0x9d, 0x4a, 0x7b, 0xc0, 0x38,
965            0x4f, 0x73, 0x9a, 0x62,
966        ];
967        let result = compute_hash_r6(b"", &key_salt, b"");
968        assert_eq!(result, expected_key_hash, "R=6 key hash mismatch");
969    }
970
971    #[test]
972    fn aes_cbc_encrypt_known_vector() {
973        // AES-128-CBC encrypt: zero key, zero IV, 32 zero bytes
974        let key = [0u8; 16];
975        let iv = [0u8; 16];
976        let data = [0u8; 32];
977        let result = aes_cbc_encrypt(&key, &iv, &data);
978        let expected = "66e94bd4ef8a2c3b884cfa59ca342b2ef795bd4a52e29ed713d313fa20e98dbc";
979        let got: String = result.iter().map(|b| format!("{:02x}", b)).collect();
980        assert_eq!(got, expected);
981    }
982
983    #[test]
984    fn aes_cbc_roundtrip() {
985        // Test AES-CBC decryption with known data
986        let key = [0u8; 16]; // Zero key
987        let iv = [0u8; 16]; // Zero IV
988
989        // Single block of zeros encrypted with zero key and zero IV
990        // Just verify it doesn't panic and produces 16 bytes or less
991        let ciphertext = [0u8; 16];
992        let result = aes_cbc_decrypt(&key, &iv, &ciphertext);
993        assert!(result.is_ok());
994    }
995}