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, and RC4 primitives.
7//!
8//! Upstream: `fx_crypt.h` + `fx_crypt.cpp`
9
10use md5::{Digest, Md5};
11use sha1::Sha1;
12
13/// Errors arising from cryptographic operations.
14#[derive(Debug, thiserror::Error)]
15pub enum CryptoError {
16    /// The supplied key has an invalid length for the algorithm.
17    #[error("invalid key length")]
18    InvalidKeyLength,
19    /// The ciphertext length is not a multiple of the block size.
20    #[error("invalid data length (must be multiple of block size)")]
21    InvalidDataLength,
22    /// PKCS#7 padding is invalid after decryption.
23    #[error("invalid padding")]
24    InvalidPadding,
25    /// The data is too short to contain the required IV prefix.
26    #[error("data too short for IV")]
27    DataTooShort,
28    /// The requested algorithm is not supported.
29    #[error("not supported: {0}")]
30    NotSupported(String),
31}
32
33/// Compute the MD5 hash of `data`.
34pub fn md5(data: &[u8]) -> [u8; 16] {
35    let mut hasher = Md5::new();
36    hasher.update(data);
37    hasher.finalize().into()
38}
39
40/// Compute the SHA-1 hash of `data`.
41///
42/// SHA-1 is used in digital signature verification (PKCS#7/CMS with
43/// `sha1WithRSAEncryption`) and some legacy PDF encryption extensions.
44/// Upstream: `CRYPT_SHA1Generate`.
45pub fn sha1(data: &[u8]) -> [u8; 20] {
46    let mut hasher = Sha1::new();
47    hasher.update(data);
48    hasher.finalize().into()
49}
50
51/// RC4 encrypt/decrypt (symmetric — same operation for both).
52///
53/// Accepts key lengths from 1 to 256 bytes, as used in PDF encryption
54/// (typically 5 bytes for 40-bit or 16 bytes for 128-bit).
55///
56/// Returns `Err(CryptoError::InvalidKeyLength)` if the key is empty or
57/// longer than 256 bytes.
58pub fn rc4_crypt(key: &[u8], data: &[u8]) -> Result<Vec<u8>, CryptoError> {
59    if key.is_empty() || key.len() > 256 {
60        return Err(CryptoError::InvalidKeyLength);
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    Ok(data
78        .iter()
79        .map(|&byte| {
80            i = i.wrapping_add(1);
81            j = j.wrapping_add(s[i as usize]);
82            s.swap(i as usize, j as usize);
83            let k = s[(s[i as usize].wrapping_add(s[j as usize])) as usize];
84            byte ^ k
85        })
86        .collect())
87}
88
89#[cfg(test)]
90mod tests {
91    use super::*;
92
93    // ---- SHA-1 upstream vectors (upstream: fx_crypt_unittest.cpp, FIPS 180-2) ----
94
95    /// SHA-1("") from upstream Sha1TestEmpty.
96    /// Upstream: TEST(FXCRYPT, Sha1Empty)
97    #[test]
98    fn test_sha1_empty() {
99        assert_eq!(
100            sha1(b""),
101            [
102                0xda, 0x39, 0xa3, 0xee, 0x5e, 0x6b, 0x4b, 0x0d, 0x32, 0x55, 0xbf, 0xef, 0x95, 0x60,
103                0x18, 0x90, 0xaf, 0xd8, 0x07, 0x09
104            ]
105        );
106    }
107
108    /// SHA-1("abc") — FIPS 180-2 Example A.1 / upstream Sha1TestA1.
109    /// Upstream: TEST(FXCRYPT, Sha1TestA1)
110    #[test]
111    fn test_sha1_known_vector_abc() {
112        assert_eq!(
113            sha1(b"abc"),
114            [
115                0xa9, 0x99, 0x3e, 0x36, 0x47, 0x06, 0x81, 0x6a, 0xba, 0x3e, 0x25, 0x71, 0x78, 0x50,
116                0xc2, 0x6c, 0x9c, 0xd0, 0xd8, 0x9d
117            ]
118        );
119    }
120
121    /// SHA-1 multi-block — FIPS 180-2 Example A.2 / upstream Sha1TestA2.
122    /// Upstream: TEST(FXCRYPT, Sha1TestA2)
123    #[test]
124    fn test_sha1_fips_a2_multi_block() {
125        assert_eq!(
126            sha1(b"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"),
127            [
128                0x84, 0x98, 0x3e, 0x44, 0x1c, 0x3b, 0xd2, 0x6e, 0xba, 0xae, 0x4a, 0xa1, 0xf9, 0x51,
129                0x29, 0xe5, 0xe5, 0x46, 0x70, 0xf1
130            ]
131        );
132    }
133
134    /// Upstream: TEST(FXCRYPT, MD5GenerateEmtpyData)
135    #[test]
136    fn test_md5_empty() {
137        let hash = md5(b"");
138        assert_eq!(
139            hash,
140            [
141                0xd4, 0x1d, 0x8c, 0xd9, 0x8f, 0x00, 0xb2, 0x04, 0xe9, 0x80, 0x09, 0x98, 0xec, 0xf8,
142                0x42, 0x7e
143            ]
144        );
145    }
146
147    /// Upstream: TEST(FXCRYPT, MD5StringTestSuite3)
148    #[test]
149    fn test_md5_abc() {
150        let hash = md5(b"abc");
151        assert_eq!(
152            hash,
153            [
154                0x90, 0x01, 0x50, 0x98, 0x3c, 0xd2, 0x4f, 0xb0, 0xd6, 0x96, 0x3f, 0x7d, 0x28, 0xe1,
155                0x7f, 0x72
156            ]
157        );
158    }
159
160    // ---- MD5 long data (upstream: MD5GenerateLongData, 10MB+1) ----
161
162    /// Upstream: TEST(FXCRYPT, MD5GenerateLongData)
163    #[test]
164    fn test_md5_long_data() {
165        let length = 10 * 1024 * 1024 + 1;
166        let data: Vec<u8> = (0..length).map(|i| (i & 0xFF) as u8).collect();
167        let hash = md5(&data);
168        assert_eq!(
169            hash,
170            [
171                0x90, 0xbd, 0x6a, 0xd9, 0x0a, 0xce, 0xf5, 0xad, 0xaa, 0x92, 0x20, 0x3e, 0x21, 0xc7,
172                0xa1, 0x3e
173            ]
174        );
175    }
176
177    #[test]
178    fn test_rc4_round_trip() {
179        let key = b"secret";
180        let plaintext = b"Hello, PDF encryption!";
181        let ciphertext = rc4_crypt(key, plaintext).unwrap();
182        assert_ne!(ciphertext, plaintext);
183        let decrypted = rc4_crypt(key, &ciphertext).unwrap();
184        assert_eq!(decrypted, plaintext);
185    }
186
187    /// Upstream: TEST(FXCRYPT, CRYPTArcFourSetup)
188    #[test]
189    fn test_rc4_known_vector() {
190        let key = b"Key";
191        let plaintext = b"Plaintext";
192        let ciphertext = rc4_crypt(key, plaintext).unwrap();
193        assert_eq!(
194            ciphertext,
195            [0xBB, 0xF3, 0x16, 0xE8, 0xD9, 0x40, 0xAF, 0x0A, 0xD3]
196        );
197    }
198
199    #[test]
200    fn test_rc4_empty_key_returns_error() {
201        let result = rc4_crypt(b"", b"data");
202        assert!(result.is_err());
203        assert!(matches!(result.unwrap_err(), CryptoError::InvalidKeyLength));
204    }
205
206    #[test]
207    fn test_rc4_oversized_key_returns_error() {
208        let key = vec![0u8; 257];
209        let result = rc4_crypt(&key, b"data");
210        assert!(result.is_err());
211        assert!(matches!(result.unwrap_err(), CryptoError::InvalidKeyLength));
212    }
213
214    // ---- R5: MD5 RFC 1321 full test suite (upstream: fx_crypt_unittest.cpp) ----
215
216    /// Upstream: TEST(FXCRYPT, MD5StringTestSuite2)
217    #[test]
218    fn test_md5_rfc1321_a() {
219        assert_eq!(
220            md5(b"a"),
221            [
222                0x0c, 0xc1, 0x75, 0xb9, 0xc0, 0xf1, 0xb6, 0xa8, 0x31, 0xc3, 0x99, 0xe2, 0x69, 0x77,
223                0x26, 0x61
224            ]
225        );
226    }
227
228    /// Upstream: TEST(FXCRYPT, MD5StringTestSuite4)
229    #[test]
230    fn test_md5_rfc1321_message_digest() {
231        assert_eq!(
232            md5(b"message digest"),
233            [
234                0xf9, 0x6b, 0x69, 0x7d, 0x7c, 0xb7, 0x93, 0x8d, 0x52, 0x5a, 0x2f, 0x31, 0xaa, 0xf1,
235                0x61, 0xd0
236            ]
237        );
238    }
239
240    /// Upstream: TEST(FXCRYPT, MD5StringTestSuite5)
241    #[test]
242    fn test_md5_rfc1321_alphabet() {
243        assert_eq!(
244            md5(b"abcdefghijklmnopqrstuvwxyz"),
245            [
246                0xc3, 0xfc, 0xd3, 0xd7, 0x61, 0x92, 0xe4, 0x00, 0x7d, 0xfb, 0x49, 0x6c, 0xca, 0x67,
247                0xe1, 0x3b
248            ]
249        );
250    }
251
252    /// Upstream: TEST(FXCRYPT, MD5StringTestSuite6)
253    #[test]
254    fn test_md5_rfc1321_alphanumeric() {
255        assert_eq!(
256            md5(b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"),
257            [
258                0xd1, 0x74, 0xab, 0x98, 0xd2, 0x77, 0xd9, 0xf5, 0xa5, 0x61, 0x1c, 0x2c, 0x9f, 0x41,
259                0x9d, 0x9f
260            ]
261        );
262    }
263
264    /// Upstream: TEST(FXCRYPT, MD5StringTestSuite7)
265    #[test]
266    fn test_md5_rfc1321_numeric_repeated() {
267        assert_eq!(
268            md5(
269                b"12345678901234567890123456789012345678901234567890123456789012345678901234567890"
270            ),
271            [
272                0x57, 0xed, 0xf4, 0xa2, 0x2b, 0xe3, 0xc9, 0x55, 0xac, 0x49, 0xda, 0x2e, 0x21, 0x07,
273                0xb6, 0x7a
274            ]
275        );
276    }
277
278    // ---- R3: RC4 upstream vector (upstream: fx_crypt_unittest.cpp, foobar key) ----
279
280    /// Upstream: TEST(FXCRYPT, CRYPTArcFourCrypt)
281    #[test]
282    fn test_rc4_foobar_key() {
283        let key = b"foobar";
284        // Upstream includes null terminator in data (C++ std::begin/std::end on char array)
285        let data = b"The Quick Fox Jumped Over The Lazy Brown Dog.\0";
286        let ciphertext = rc4_crypt(key, data).unwrap();
287        #[rustfmt::skip]
288        let expected: &[u8] = &[
289            59,  193, 117, 206, 167, 54,  218, 7,   229, 214, 188, 55,
290            90,  205, 196, 25,  36,  114, 199, 218, 161, 107, 122, 119,
291            106, 167, 44,  175, 240, 123, 192, 102, 174, 167, 105, 187,
292            202, 70,  121, 81,  17,  30,  5,   138, 116, 166,
293        ];
294        assert_eq!(ciphertext, expected);
295    }
296}