xref: /aosp_15_r20/external/fonttools/Lib/fontTools/misc/eexec.py (revision e1fe3e4ad2793916b15cccdc4a7da52a7e1dd0e9)
1"""
2PostScript Type 1 fonts make use of two types of encryption: charstring
3encryption and ``eexec`` encryption. Charstring encryption is used for
4the charstrings themselves, while ``eexec`` is used to encrypt larger
5sections of the font program, such as the ``Private`` and ``CharStrings``
6dictionaries. Despite the different names, the algorithm is the same,
7although ``eexec`` encryption uses a fixed initial key R=55665.
8
9The algorithm uses cipher feedback, meaning that the ciphertext is used
10to modify the key. Because of this, the routines in this module return
11the new key at the end of the operation.
12
13"""
14
15from fontTools.misc.textTools import bytechr, bytesjoin, byteord
16
17
18def _decryptChar(cipher, R):
19    cipher = byteord(cipher)
20    plain = ((cipher ^ (R >> 8))) & 0xFF
21    R = ((cipher + R) * 52845 + 22719) & 0xFFFF
22    return bytechr(plain), R
23
24
25def _encryptChar(plain, R):
26    plain = byteord(plain)
27    cipher = ((plain ^ (R >> 8))) & 0xFF
28    R = ((cipher + R) * 52845 + 22719) & 0xFFFF
29    return bytechr(cipher), R
30
31
32def decrypt(cipherstring, R):
33    r"""
34    Decrypts a string using the Type 1 encryption algorithm.
35
36    Args:
37            cipherstring: String of ciphertext.
38            R: Initial key.
39
40    Returns:
41            decryptedStr: Plaintext string.
42            R: Output key for subsequent decryptions.
43
44    Examples::
45
46            >>> testStr = b"\0\0asdadads asds\265"
47            >>> decryptedStr, R = decrypt(testStr, 12321)
48            >>> decryptedStr == b'0d\nh\x15\xe8\xc4\xb2\x15\x1d\x108\x1a<6\xa1'
49            True
50            >>> R == 36142
51            True
52    """
53    plainList = []
54    for cipher in cipherstring:
55        plain, R = _decryptChar(cipher, R)
56        plainList.append(plain)
57    plainstring = bytesjoin(plainList)
58    return plainstring, int(R)
59
60
61def encrypt(plainstring, R):
62    r"""
63    Encrypts a string using the Type 1 encryption algorithm.
64
65    Note that the algorithm as described in the Type 1 specification requires the
66    plaintext to be prefixed with a number of random bytes. (For ``eexec`` the
67    number of random bytes is set to 4.) This routine does *not* add the random
68    prefix to its input.
69
70    Args:
71            plainstring: String of plaintext.
72            R: Initial key.
73
74    Returns:
75            cipherstring: Ciphertext string.
76            R: Output key for subsequent encryptions.
77
78    Examples::
79
80            >>> testStr = b"\0\0asdadads asds\265"
81            >>> decryptedStr, R = decrypt(testStr, 12321)
82            >>> decryptedStr == b'0d\nh\x15\xe8\xc4\xb2\x15\x1d\x108\x1a<6\xa1'
83            True
84            >>> R == 36142
85            True
86
87    >>> testStr = b'0d\nh\x15\xe8\xc4\xb2\x15\x1d\x108\x1a<6\xa1'
88    >>> encryptedStr, R = encrypt(testStr, 12321)
89    >>> encryptedStr == b"\0\0asdadads asds\265"
90    True
91    >>> R == 36142
92    True
93    """
94    cipherList = []
95    for plain in plainstring:
96        cipher, R = _encryptChar(plain, R)
97        cipherList.append(cipher)
98    cipherstring = bytesjoin(cipherList)
99    return cipherstring, int(R)
100
101
102def hexString(s):
103    import binascii
104
105    return binascii.hexlify(s)
106
107
108def deHexString(h):
109    import binascii
110
111    h = bytesjoin(h.split())
112    return binascii.unhexlify(h)
113
114
115if __name__ == "__main__":
116    import sys
117    import doctest
118
119    sys.exit(doctest.testmod().failed)
120