Skip to main content

rpdfium_parser/crypto/
fx_crypt.rs

1// Derived from PDFium's core/fdrm/fx_crypt.h + core/fdrm/fx_crypt.cpp
2// Original: Copyright 2014 The PDFium Authors
3// Licensed under BSD-3-Clause / Apache-2.0
4// See pdfium-upstream/LICENSE for the original license.
5
6//! MD5, SHA-1 (stub), and RC4 primitives.
7//!
8//! Upstream: `fx_crypt.h` + `fx_crypt.cpp`
9
10use md5::{Digest, Md5};
11
12/// Errors arising from cryptographic operations.
13#[derive(Debug, thiserror::Error)]
14pub enum CryptoError {
15    /// The supplied key has an invalid length for the algorithm.
16    #[error("invalid key length")]
17    InvalidKeyLength,
18    /// The ciphertext length is not a multiple of the block size.
19    #[error("invalid data length (must be multiple of block size)")]
20    InvalidDataLength,
21    /// PKCS#7 padding is invalid after decryption.
22    #[error("invalid padding")]
23    InvalidPadding,
24    /// The data is too short to contain the required IV prefix.
25    #[error("data too short for IV")]
26    DataTooShort,
27    /// The requested algorithm is not supported.
28    #[error("not supported: {0}")]
29    NotSupported(String),
30}
31
32/// Compute the MD5 hash of `data`.
33pub fn md5(data: &[u8]) -> [u8; 16] {
34    let mut hasher = Md5::new();
35    hasher.update(data);
36    hasher.finalize().into()
37}
38
39/// Compute the SHA-1 hash of `data`.
40///
41/// # Not Supported
42///
43/// SHA-1 is not used in PDF encryption (R2–R6 use MD5 or SHA-256/384/512).
44/// If digital signature validation requires SHA-1 in the future, add the
45/// `sha1` crate and replace this stub.
46pub fn sha1(_data: &[u8]) -> Result<[u8; 20], CryptoError> {
47    Err(CryptoError::NotSupported(
48        "sha1: not needed for PDF encryption; add sha1 crate if digital signatures require it"
49            .into(),
50    ))
51}
52
53/// RC4 encrypt/decrypt (symmetric — same operation for both).
54///
55/// Accepts key lengths from 1 to 256 bytes, as used in PDF encryption
56/// (typically 5 bytes for 40-bit or 16 bytes for 128-bit).
57pub fn rc4_crypt(key: &[u8], data: &[u8]) -> Vec<u8> {
58    assert!(
59        !key.is_empty() && key.len() <= 256,
60        "RC4 key length 1..=256"
61    );
62
63    // KSA (Key Scheduling Algorithm)
64    let mut s = [0u8; 256];
65    for (i, b) in s.iter_mut().enumerate() {
66        *b = i as u8;
67    }
68    let mut j: u8 = 0;
69    for i in 0..256 {
70        j = j.wrapping_add(s[i]).wrapping_add(key[i % key.len()]);
71        s.swap(i, j as usize);
72    }
73
74    // PRGA (Pseudo-Random Generation Algorithm)
75    let mut i: u8 = 0;
76    let mut j: u8 = 0;
77    data.iter()
78        .map(|&byte| {
79            i = i.wrapping_add(1);
80            j = j.wrapping_add(s[i as usize]);
81            s.swap(i as usize, j as usize);
82            let k = s[(s[i as usize].wrapping_add(s[j as usize])) as usize];
83            byte ^ k
84        })
85        .collect()
86}
87
88#[cfg(test)]
89mod tests {
90    use super::*;
91
92    #[test]
93    fn sha1_not_supported() {
94        let result = sha1(b"abc");
95        assert!(result.is_err());
96        assert!(
97            result
98                .unwrap_err()
99                .to_string()
100                .contains("not needed for PDF encryption")
101        );
102    }
103
104    #[test]
105    fn md5_empty() {
106        let hash = md5(b"");
107        assert_eq!(
108            hash,
109            [
110                0xd4, 0x1d, 0x8c, 0xd9, 0x8f, 0x00, 0xb2, 0x04, 0xe9, 0x80, 0x09, 0x98, 0xec, 0xf8,
111                0x42, 0x7e
112            ]
113        );
114    }
115
116    #[test]
117    fn md5_abc() {
118        let hash = md5(b"abc");
119        assert_eq!(
120            hash,
121            [
122                0x90, 0x01, 0x50, 0x98, 0x3c, 0xd2, 0x4f, 0xb0, 0xd6, 0x96, 0x3f, 0x7d, 0x28, 0xe1,
123                0x7f, 0x72
124            ]
125        );
126    }
127
128    // ---- MD5 long data (upstream: MD5GenerateLongData, 10MB+1) ----
129
130    #[test]
131    fn md5_long_data() {
132        let length = 10 * 1024 * 1024 + 1;
133        let data: Vec<u8> = (0..length).map(|i| (i & 0xFF) as u8).collect();
134        let hash = md5(&data);
135        assert_eq!(
136            hash,
137            [
138                0x90, 0xbd, 0x6a, 0xd9, 0x0a, 0xce, 0xf5, 0xad, 0xaa, 0x92, 0x20, 0x3e, 0x21, 0xc7,
139                0xa1, 0x3e
140            ]
141        );
142    }
143
144    #[test]
145    fn rc4_round_trip() {
146        let key = b"secret";
147        let plaintext = b"Hello, PDF encryption!";
148        let ciphertext = rc4_crypt(key, plaintext);
149        assert_ne!(ciphertext, plaintext);
150        let decrypted = rc4_crypt(key, &ciphertext);
151        assert_eq!(decrypted, plaintext);
152    }
153
154    #[test]
155    fn rc4_known_vector() {
156        let key = b"Key";
157        let plaintext = b"Plaintext";
158        let ciphertext = rc4_crypt(key, plaintext);
159        assert_eq!(
160            ciphertext,
161            [0xBB, 0xF3, 0x16, 0xE8, 0xD9, 0x40, 0xAF, 0x0A, 0xD3]
162        );
163    }
164
165    // ---- R5: MD5 RFC 1321 full test suite (upstream: fx_crypt_unittest.cpp) ----
166
167    #[test]
168    fn md5_rfc1321_a() {
169        assert_eq!(
170            md5(b"a"),
171            [
172                0x0c, 0xc1, 0x75, 0xb9, 0xc0, 0xf1, 0xb6, 0xa8, 0x31, 0xc3, 0x99, 0xe2, 0x69, 0x77,
173                0x26, 0x61
174            ]
175        );
176    }
177
178    #[test]
179    fn md5_rfc1321_message_digest() {
180        assert_eq!(
181            md5(b"message digest"),
182            [
183                0xf9, 0x6b, 0x69, 0x7d, 0x7c, 0xb7, 0x93, 0x8d, 0x52, 0x5a, 0x2f, 0x31, 0xaa, 0xf1,
184                0x61, 0xd0
185            ]
186        );
187    }
188
189    #[test]
190    fn md5_rfc1321_alphabet() {
191        assert_eq!(
192            md5(b"abcdefghijklmnopqrstuvwxyz"),
193            [
194                0xc3, 0xfc, 0xd3, 0xd7, 0x61, 0x92, 0xe4, 0x00, 0x7d, 0xfb, 0x49, 0x6c, 0xca, 0x67,
195                0xe1, 0x3b
196            ]
197        );
198    }
199
200    #[test]
201    fn md5_rfc1321_alphanumeric() {
202        assert_eq!(
203            md5(b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"),
204            [
205                0xd1, 0x74, 0xab, 0x98, 0xd2, 0x77, 0xd9, 0xf5, 0xa5, 0x61, 0x1c, 0x2c, 0x9f, 0x41,
206                0x9d, 0x9f
207            ]
208        );
209    }
210
211    #[test]
212    fn md5_rfc1321_numeric_repeated() {
213        assert_eq!(
214            md5(
215                b"12345678901234567890123456789012345678901234567890123456789012345678901234567890"
216            ),
217            [
218                0x57, 0xed, 0xf4, 0xa2, 0x2b, 0xe3, 0xc9, 0x55, 0xac, 0x49, 0xda, 0x2e, 0x21, 0x07,
219                0xb6, 0x7a
220            ]
221        );
222    }
223
224    // ---- R3: RC4 upstream vector (upstream: fx_crypt_unittest.cpp, foobar key) ----
225
226    #[test]
227    fn rc4_foobar_key() {
228        let key = b"foobar";
229        // Upstream includes null terminator in data (C++ std::begin/std::end on char array)
230        let data = b"The Quick Fox Jumped Over The Lazy Brown Dog.\0";
231        let ciphertext = rc4_crypt(key, data);
232        #[rustfmt::skip]
233        let expected: &[u8] = &[
234            59,  193, 117, 206, 167, 54,  218, 7,   229, 214, 188, 55,
235            90,  205, 196, 25,  36,  114, 199, 218, 161, 107, 122, 119,
236            106, 167, 44,  175, 240, 123, 192, 102, 174, 167, 105, 187,
237            202, 70,  121, 81,  17,  30,  5,   138, 116, 166,
238        ];
239        assert_eq!(ciphertext, expected);
240    }
241}