Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 3863a75

Browse files
committed
Implement Type-1 decryption
This is a prerequisite of subsetting.
1 parent 73b7abf commit 3863a75

File tree

3 files changed

+71
-1
lines changed

3 files changed

+71
-1
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
``Type1Font`` objects now decrypt the encrypted part
2+
----------------------------------------------------
3+
4+
Type 1 fonts have a large part of their code encrypted as an obsolete
5+
copy-protection measure. This part is now available decrypted as the
6+
``decrypted`` attribute of :class:`~matplotlib.type1font.Type1Font`.
7+
This decrypted data is not yet parsed, but this is a prerequisite for
8+
implementing subsetting.

lib/matplotlib/tests/test_type1font.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ def test_Type1Font():
1515
assert font.parts[2] == rawdata[0x8985:0x8ba6]
1616
assert font.parts[1:] == slanted.parts[1:]
1717
assert font.parts[1:] == condensed.parts[1:]
18+
assert font.decrypted.startswith(b'dup\n/Private 18 dict dup begin')
19+
assert font.decrypted.endswith(b'mark currentfile closefile\n')
1820

1921
differ = difflib.Differ()
2022
diff = list(differ.compare(
@@ -67,3 +69,11 @@ def test_overprecision():
6769
assert matrix == '0.001 0 0.000167 0.001 0 0'
6870
# and here we had -9.48090361795083
6971
assert angle == '-9.4809'
72+
73+
74+
def test_encrypt_decrypt_roundtrip():
75+
data = b'this is my plaintext \0\1\2\3'
76+
encrypted = t1f.Type1Font._encrypt(data, 'eexec')
77+
decrypted = t1f.Type1Font._decrypt(encrypted, 'eexec')
78+
assert encrypted != decrypted
79+
assert data == decrypted

lib/matplotlib/type1font.py

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import numpy as np
3131

3232
from matplotlib.cbook import _format_approx
33+
from . import _api
3334

3435

3536
# token types
@@ -46,10 +47,12 @@ class Type1Font:
4647
parts : tuple
4748
A 3-tuple of the cleartext part, the encrypted part, and the finale of
4849
zeros.
50+
decrypted : bytes
51+
The decrypted form of parts[1].
4952
prop : dict[str, Any]
5053
A dictionary of font properties.
5154
"""
52-
__slots__ = ('parts', 'prop')
55+
__slots__ = ('parts', 'decrypted', 'prop')
5356

5457
def __init__(self, input):
5558
"""
@@ -68,6 +71,7 @@ def __init__(self, input):
6871
data = self._read(file)
6972
self.parts = self._split(data)
7073

74+
self.decrypted = self._decrypt(self.parts[1], 'eexec')
7175
self._parse()
7276

7377
def _read(self, file):
@@ -139,6 +143,54 @@ def _split(self, data):
139143
_token_re = re.compile(br'/{0,2}[^]\0\t\r\v\n ()<>{}/%[]+')
140144
_instring_re = re.compile(br'[()\\]')
141145

146+
@staticmethod
147+
def _decrypt(ciphertext, key, ndiscard=4):
148+
"""
149+
Decrypt ciphertext using the Type-1 font algorithm
150+
151+
The algorithm is described in Adobe's "Adobe Type 1 Font Format".
152+
The key argument can be an integer, or one of the strings
153+
'eexec' and 'charstring', which map to the key specified for the
154+
corresponding part of Type-1 fonts.
155+
156+
The ndiscard argument should be an integer, usually 4.
157+
That number of bytes is discarded from the beginning of plaintext.
158+
"""
159+
160+
key = _api.check_getitem({'eexec': 55665, 'charstring': 4330}, key=key)
161+
plaintext = []
162+
for byte in ciphertext:
163+
plaintext.append(byte ^ (key >> 8))
164+
key = ((key+byte) * 52845 + 22719) & 0xffff
165+
166+
return bytes(plaintext[ndiscard:])
167+
168+
@staticmethod
169+
def _encrypt(plaintext, key, ndiscard=4):
170+
"""
171+
Encrypt plaintext using the Type-1 font algorithm
172+
173+
The algorithm is described in Adobe's "Adobe Type 1 Font Format".
174+
The key argument can be an integer, or one of the strings
175+
'eexec' and 'charstring', which map to the key specified for the
176+
corresponding part of Type-1 fonts.
177+
178+
The ndiscard argument should be an integer, usually 4. That
179+
number of bytes is prepended to the plaintext before encryption.
180+
This function prepends NUL bytes for reproducibility, even though
181+
the original algorithm uses random bytes, presumably to avoid
182+
cryptanalysis.
183+
"""
184+
185+
key = _api.check_getitem({'eexec': 55665, 'charstring': 4330}, key=key)
186+
ciphertext = []
187+
for byte in b'\0' * ndiscard + plaintext:
188+
c = byte ^ (key >> 8)
189+
ciphertext.append(c)
190+
key = ((key + c) * 52845 + 22719) & 0xffff
191+
192+
return bytes(ciphertext)
193+
142194
@classmethod
143195
def _tokens(cls, text):
144196
"""

0 commit comments

Comments
 (0)