File size: 3,450 Bytes
7d134e4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
"""

PostScript Type 1 fonts make use of two types of encryption: charstring

encryption and ``eexec`` encryption. Charstring encryption is used for

the charstrings themselves, while ``eexec`` is used to encrypt larger

sections of the font program, such as the ``Private`` and ``CharStrings``

dictionaries. Despite the different names, the algorithm is the same,

although ``eexec`` encryption uses a fixed initial key R=55665.



The algorithm uses cipher feedback, meaning that the ciphertext is used

to modify the key. Because of this, the routines in this module return

the new key at the end of the operation.



"""

from fontTools.misc.textTools import bytechr, bytesjoin, byteord


def _decryptChar(cipher, R):
    cipher = byteord(cipher)
    plain = ((cipher ^ (R >> 8))) & 0xFF
    R = ((cipher + R) * 52845 + 22719) & 0xFFFF
    return bytechr(plain), R


def _encryptChar(plain, R):
    plain = byteord(plain)
    cipher = ((plain ^ (R >> 8))) & 0xFF
    R = ((cipher + R) * 52845 + 22719) & 0xFFFF
    return bytechr(cipher), R


def decrypt(cipherstring, R):
    r"""

    Decrypts a string using the Type 1 encryption algorithm.



    Args:

            cipherstring: String of ciphertext.

            R: Initial key.



    Returns:

            decryptedStr: Plaintext string.

            R: Output key for subsequent decryptions.



    Examples::



            >>> testStr = b"\0\0asdadads asds\265"

            >>> decryptedStr, R = decrypt(testStr, 12321)

            >>> decryptedStr == b'0d\nh\x15\xe8\xc4\xb2\x15\x1d\x108\x1a<6\xa1'

            True

            >>> R == 36142

            True

    """
    plainList = []
    for cipher in cipherstring:
        plain, R = _decryptChar(cipher, R)
        plainList.append(plain)
    plainstring = bytesjoin(plainList)
    return plainstring, int(R)


def encrypt(plainstring, R):
    r"""

    Encrypts a string using the Type 1 encryption algorithm.



    Note that the algorithm as described in the Type 1 specification requires the

    plaintext to be prefixed with a number of random bytes. (For ``eexec`` the

    number of random bytes is set to 4.) This routine does *not* add the random

    prefix to its input.



    Args:

            plainstring: String of plaintext.

            R: Initial key.



    Returns:

            cipherstring: Ciphertext string.

            R: Output key for subsequent encryptions.



    Examples::



            >>> testStr = b"\0\0asdadads asds\265"

            >>> decryptedStr, R = decrypt(testStr, 12321)

            >>> decryptedStr == b'0d\nh\x15\xe8\xc4\xb2\x15\x1d\x108\x1a<6\xa1'

            True

            >>> R == 36142

            True



    >>> testStr = b'0d\nh\x15\xe8\xc4\xb2\x15\x1d\x108\x1a<6\xa1'

    >>> encryptedStr, R = encrypt(testStr, 12321)

    >>> encryptedStr == b"\0\0asdadads asds\265"

    True

    >>> R == 36142

    True

    """
    cipherList = []
    for plain in plainstring:
        cipher, R = _encryptChar(plain, R)
        cipherList.append(cipher)
    cipherstring = bytesjoin(cipherList)
    return cipherstring, int(R)


def hexString(s):
    import binascii

    return binascii.hexlify(s)


def deHexString(h):
    import binascii

    h = bytesjoin(h.split())
    return binascii.unhexlify(h)


if __name__ == "__main__":
    import sys
    import doctest

    sys.exit(doctest.testmod().failed)