Module: Fontisan::Type1::Decryptor

Defined in:
lib/fontisan/type1/decryptor.rb

Overview

Decryption utilities for Type 1 fonts

Decryptor handles the two types of encryption used in Adobe Type 1 fonts:

  1. eexec encryption: Protects the font's private dictionary and CharStrings with key 55665
  2. CharString encryption: Individual CharStrings within the eexec portion with key 4330

Both use the same cipher algorithm but with different keys. The algorithm uses cipher feedback, where the ciphertext is used to modify the key for subsequent bytes.

Algorithm (from Adobe Type 1 Font Format):

plain = (cipher XOR (R >> 8)) AND 0xFF
R = ((cipher + R) * 52845 + 22719) AND 0xFFFF

Examples:

Decrypt eexec portion

encrypted = Fontisan::Type1::PFAParser.new.parse(data).encrypted_binary
decrypted = Fontisan::Type1::Decryptor.eexec_decrypt(encrypted)

Decrypt CharString

encrypted_charstring = "\x80\x00\x01..."
decrypted = Fontisan::Type1::Decryptor.charstring_decrypt(encrypted_charstring, len_iv: 4)

See Also:

Constant Summary collapse

DEFAULT_LEN_IV =

Default lenIV value (number of random bytes at start of encrypted CharString)

4
EEXEC_KEY =

eexec encryption key

55665
CHARSTRING_KEY =

CharString encryption key

4330
CIPHER_MULT =

Cipher update constants

52845
CIPHER_ADD =
22719
CIPHER_MASK =
0xFFFF

Class Method Summary collapse

Class Method Details

.charstring_decrypt(data, len_iv: DEFAULT_LEN_IV) ⇒ String

Decrypt Type 1 CharString

CharStrings are encrypted within the eexec portion using a different key (4330). The lenIV parameter specifies how many random bytes precede the actual CharString data (default is 4).

Examples:

Decrypt CharString with default lenIV

decrypted = Fontisan::Type1::Decryptor.charstring_decrypt(encrypted)

Decrypt CharString with custom lenIV

decrypted = Fontisan::Type1::Decryptor.charstring_decrypt(encrypted, len_iv: 0)

Parameters:

  • data (String)

    Encrypted CharString data

  • len_iv (Integer) (defaults to: DEFAULT_LEN_IV)

    Number of bytes to skip after decryption

Returns:

  • (String)

    Decrypted CharString data



88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
# File 'lib/fontisan/type1/decryptor.rb', line 88

def self.charstring_decrypt(data, len_iv: DEFAULT_LEN_IV)
  return "" if data.nil? || data.empty?

  decrypted = decrypt(data, CHARSTRING_KEY)

  # Skip lenIV bytes (random bytes added during encryption)
  if len_iv.positive?
    if len_iv >= decrypted.length
      # lenIV is larger than data - return empty
      return ""
    end

    decrypted = decrypted.byteslice(len_iv..-1)
  end

  decrypted
end

.charstring_encrypt(data, len_iv: DEFAULT_LEN_IV) ⇒ String

Encrypt data using CharString cipher

Parameters:

  • data (String)

    Plain CharString data

  • len_iv (Integer) (defaults to: DEFAULT_LEN_IV)

    Number of random bytes to prepend

Returns:

  • (String)

    Encrypted CharString data



111
112
113
114
115
116
117
118
119
120
121
122
123
124
# File 'lib/fontisan/type1/decryptor.rb', line 111

def self.charstring_encrypt(data, len_iv: DEFAULT_LEN_IV)
  return "" if data.nil? || data.empty?

  # Ensure binary encoding before concatenation
  data = data.b if data.respond_to?(:b)

  # Prepend lenIV random bytes
  if len_iv.positive?
    random_bytes = Array.new(len_iv) { rand(256) }.pack("C*")
    data = random_bytes + data
  end

  encrypt(data, CHARSTRING_KEY)
end

.decrypt(data, key) ⇒ String

Decrypt using Type 1 cipher

The cipher processes data byte by byte, maintaining state across iterations using cipher feedback.

Algorithm:

plain = (cipher XOR (R >> 8)) AND 0xFF
R = ((cipher + R) * 52845 + 22719) AND 0xFFFF

Parameters:

  • data (String)

    Encrypted data

  • key (Integer)

    Cipher key (55665 for eexec, 4330 for CharString)

Returns:

  • (String)

    Decrypted data



138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
# File 'lib/fontisan/type1/decryptor.rb', line 138

def self.decrypt(data, key)
  result = String.new(capacity: data.length)
  r = key

  data.each_byte do |cipher|
    # plain = (cipher XOR (R >> 8)) AND 0xFF
    plain = (cipher ^ (r >> 8)) & 0xFF
    result << plain

    # R = ((cipher + R) * 52845 + 22719) AND 0xFFFF
    r = ((cipher + r) * CIPHER_MULT + CIPHER_ADD) & CIPHER_MASK
  end

  result
end

.eexec_decrypt(data) ⇒ String

Decrypt eexec encrypted data

The eexec cipher is used to encrypt the private dictionary and CharStrings portion of a Type 1 font.

Examples:

Decrypt eexec portion

decrypted = Fontisan::Type1::Decryptor.eexec_decrypt(encrypted_data)

Parameters:

  • data (String)

    Encrypted binary data

Returns:

  • (String)

    Decrypted data



57
58
59
60
61
# File 'lib/fontisan/type1/decryptor.rb', line 57

def self.eexec_decrypt(data)
  return "" if data.nil? || data.empty?

  decrypt(data, EEXEC_KEY)
end

.eexec_encrypt(data) ⇒ String

Encrypt data using eexec cipher

Parameters:

  • data (String)

    Plain text data

Returns:

  • (String)

    Encrypted data



67
68
69
70
71
# File 'lib/fontisan/type1/decryptor.rb', line 67

def self.eexec_encrypt(data)
  return "" if data.nil? || data.empty?

  encrypt(data, EEXEC_KEY)
end

.encrypt(data, key) ⇒ String

Encrypt using Type 1 cipher

The encryption algorithm is symmetric with decryption, producing the ciphertext that decrypts to the original plaintext.

Algorithm:

cipher = (plain XOR (R >> 8)) AND 0xFF
R = ((cipher + R) * 52845 + 22719) AND 0xFFFF

Parameters:

  • data (String)

    Plain data

  • key (Integer)

    Cipher key

Returns:

  • (String)

    Encrypted data



166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
# File 'lib/fontisan/type1/decryptor.rb', line 166

def self.encrypt(data, key)
  result = String.new(capacity: data.length)
  r = key

  data.each_byte do |plain|
    # cipher = (plain XOR (R >> 8)) AND 0xFF
    cipher = (plain ^ (r >> 8)) & 0xFF
    result << cipher

    # R = ((cipher + R) * 52845 + 22719) AND 0xFFFF
    r = ((cipher + r) * CIPHER_MULT + CIPHER_ADD) & CIPHER_MASK
  end

  result
end