pdfrum_type1/eexec.rs
1//! The two Type 1 stream ciphers (Type 1 specification ยง7).
2//!
3//! Both are the same three-line feedback cipher differing only in their seed:
4//! `eexec` (seed 55665) wraps the whole private dictionary, and each individual
5//! charstring is separately encrypted with seed 4330. Neither offers any
6//! security โ the seeds are published constants โ so this module is a codec,
7//! not cryptography, and lives here rather than in `pdfrum-crypt`.
8//!
9//! Both forms discard a number of leading plaintext bytes that were random
10//! padding at encryption time: 4 for `eexec`, and `lenIV` (default 4, but the
11//! Private dictionary may set anything including 0) for charstrings.
12
13/// Seed for the `eexec` envelope around the private dictionary.
14pub const EEXEC_SEED: u16 = 55665;
15/// Seed for an individual charstring or subroutine.
16pub const CHARSTRING_SEED: u16 = 4330;
17/// Plaintext bytes `eexec` discards; fixed by the specification.
18pub const EEXEC_SKIP: usize = 4;
19/// Default `lenIV` when the Private dictionary does not say.
20pub const DEFAULT_LEN_IV: i32 = 4;
21
22const MULT: u16 = 52845;
23const ADD: u16 = 22719;
24
25/// Decrypt `cipher` with `seed`, dropping the first `skip` plaintext bytes.
26///
27/// A `skip` larger than the plaintext yields an empty result rather than
28/// panicking โ a `lenIV` of 16 against a 3-byte charstring is a real thing
29/// broken fonts do.
30///
31/// ```
32/// use pdfrum_type1::{decrypt, CHARSTRING_SEED};
33///
34/// // Round-trip: encrypting then decrypting with the same seed is identity.
35/// let plain = b"\0\0\0\0hsbw-ish payload";
36/// let cipher = pdfrum_type1::encrypt(plain, CHARSTRING_SEED);
37/// assert_eq!(decrypt(&cipher, CHARSTRING_SEED, 4), b"hsbw-ish payload");
38/// ```
39#[must_use]
40pub fn decrypt(cipher: &[u8], seed: u16, skip: usize) -> Vec<u8> {
41 let mut r = seed;
42 let mut out = Vec::with_capacity(cipher.len().saturating_sub(skip));
43 for (i, &c) in cipher.iter().enumerate() {
44 let plain = c ^ (r >> 8) as u8;
45 r = (u16::from(c).wrapping_add(r))
46 .wrapping_mul(MULT)
47 .wrapping_add(ADD);
48 if i >= skip {
49 out.push(plain);
50 }
51 }
52 out
53}
54
55/// The inverse of [`decrypt`], without the skip: `plain` is emitted as-is
56/// through the cipher.
57///
58/// Exists so tests can build encrypted fixtures from readable charstrings
59/// instead of hand-assembled byte tables. It is also the operation an encoder
60/// would need, which is why it is public rather than `#[cfg(test)]`.
61#[must_use]
62pub fn encrypt(plain: &[u8], seed: u16) -> Vec<u8> {
63 let mut r = seed;
64 let mut out = Vec::with_capacity(plain.len());
65 for &p in plain {
66 let c = p ^ (r >> 8) as u8;
67 out.push(c);
68 r = (u16::from(c).wrapping_add(r))
69 .wrapping_mul(MULT)
70 .wrapping_add(ADD);
71 }
72 out
73}
74
75/// Turn a Private-dictionary `lenIV` into a byte count.
76///
77/// The value is signed in the source and fonts do write negatives; a negative
78/// or absurd `lenIV` means "discard nothing", which is FreeType's reading and
79/// the only one that keeps such a font renderable.
80#[must_use]
81pub fn len_iv_skip(len_iv: i32) -> usize {
82 usize::try_from(len_iv).unwrap_or(0)
83}
84
85#[cfg(test)]
86mod tests {
87 use super::{CHARSTRING_SEED, DEFAULT_LEN_IV, EEXEC_SEED, EEXEC_SKIP, decrypt, encrypt};
88
89 #[test]
90 fn known_eexec_vector() {
91 // Encrypting 32 zero bytes with the eexec seed is a fully determined
92 // sequence; recomputing it here from the published recurrence pins the
93 // constants against a typo in either direction.
94 let cipher = encrypt(&[0u8; 32], EEXEC_SEED);
95 let mut r: u16 = EEXEC_SEED;
96 let expected: Vec<u8> = (0..32)
97 .map(|_| {
98 let c = (r >> 8) as u8; // plaintext byte is 0
99 r = (u16::from(c).wrapping_add(r))
100 .wrapping_mul(52845)
101 .wrapping_add(22719);
102 c
103 })
104 .collect();
105 assert_eq!(cipher, expected);
106 assert_eq!(cipher.len(), 32);
107 // And the first four bytes are the padding eexec throws away.
108 assert_eq!(decrypt(&cipher, EEXEC_SEED, EEXEC_SKIP), vec![0u8; 28]);
109 }
110
111 #[test]
112 fn len_iv_of_zero_four_and_eight() {
113 let plain: Vec<u8> = (0u8..24).collect();
114 let cipher = encrypt(&plain, CHARSTRING_SEED);
115 for skip in [0usize, DEFAULT_LEN_IV as usize, 8] {
116 assert_eq!(
117 decrypt(&cipher, CHARSTRING_SEED, skip),
118 plain.get(skip..).unwrap_or_default(),
119 "lenIV {skip}"
120 );
121 }
122 }
123
124 #[test]
125 fn skip_past_the_end_is_empty_not_a_panic() {
126 let cipher = encrypt(b"abc", CHARSTRING_SEED);
127 assert!(decrypt(&cipher, CHARSTRING_SEED, 99).is_empty());
128 }
129
130 #[test]
131 fn negative_len_iv_discards_nothing() {
132 assert_eq!(super::len_iv_skip(-1), 0);
133 assert_eq!(super::len_iv_skip(4), 4);
134 }
135}