combine_pdf 0.2.5 → 0.2.37
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +2 -0
- data/CHANGELOG.md +273 -27
- data/LICENSE.txt +2 -1
- data/README.md +69 -4
- data/lib/combine_pdf/api.rb +156 -153
- data/lib/combine_pdf/basic_writer.rb +41 -53
- data/lib/combine_pdf/decrypt.rb +238 -228
- data/lib/combine_pdf/exceptions.rb +4 -0
- data/lib/combine_pdf/filter.rb +79 -85
- data/lib/combine_pdf/fonts.rb +451 -462
- data/lib/combine_pdf/page_methods.rb +891 -946
- data/lib/combine_pdf/parser.rb +663 -531
- data/lib/combine_pdf/pdf_protected.rb +341 -126
- data/lib/combine_pdf/pdf_public.rb +492 -454
- data/lib/combine_pdf/renderer.rb +146 -141
- data/lib/combine_pdf/version.rb +1 -2
- data/lib/combine_pdf.rb +14 -18
- data/test/automated +132 -0
- data/test/console +4 -4
- data/test/named_dest +84 -0
- metadata +8 -5
- data/lib/combine_pdf/operations.rb +0 -416
data/lib/combine_pdf/decrypt.rb
CHANGED
@@ -5,258 +5,268 @@
|
|
5
5
|
## is subject to the same license.
|
6
6
|
########################################################
|
7
7
|
|
8
|
+
module CombinePDF
|
9
|
+
#:nodoc: all
|
8
10
|
|
11
|
+
protected
|
9
12
|
|
10
|
-
|
11
|
-
|
13
|
+
# @private
|
14
|
+
# @!visibility private
|
12
15
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
# @!visibility private
|
16
|
+
# This is an internal class. you don't need it.
|
17
|
+
class PDFDecrypt
|
18
|
+
# include CombinePDF::Renderer
|
17
19
|
|
18
|
-
|
19
|
-
class PDFDecrypt
|
20
|
-
include CombinePDF::Renderer
|
20
|
+
# @!visibility private
|
21
21
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
0x2E, 0x2E, 0x00, 0xB6, 0xD0, 0x68, 0x3E, 0x80,
|
35
|
-
0x2F, 0x0C, 0xA9, 0xFE, 0x64, 0x53, 0x69, 0x7A ]
|
36
|
-
@key_crypt_first_iv_store = nil
|
37
|
-
@encryption_iv = nil
|
38
|
-
change_references_to_actual_values @encryption_dictionary
|
39
|
-
end
|
22
|
+
# make a new Decrypt object. requires:
|
23
|
+
# objects:: an array containing the encrypted objects.
|
24
|
+
# root_dictionary:: the root PDF dictionary, containing the Encrypt dictionary.
|
25
|
+
def initialize(objects = [], root_dictionary = {})
|
26
|
+
@objects = objects
|
27
|
+
@encryption_dictionary = actual_object(root_dictionary[:Encrypt])
|
28
|
+
raise EncryptionError, 'Cannot decrypt an encrypted file without an encryption dictionary!' unless @encryption_dictionary
|
29
|
+
@root_dictionary = actual_object(root_dictionary)
|
30
|
+
@padding_key = [0x28, 0xBF, 0x4E, 0x5E, 0x4E, 0x75, 0x8A, 0x41,
|
31
|
+
0x64, 0x00, 0x4E, 0x56, 0xFF, 0xFA, 0x01, 0x08,
|
32
|
+
0x2E, 0x2E, 0x00, 0xB6, 0xD0, 0x68, 0x3E, 0x80,
|
33
|
+
0x2F, 0x0C, 0xA9, 0xFE, 0x64, 0x53, 0x69, 0x7A]
|
40
34
|
|
41
|
-
|
42
|
-
|
43
|
-
raise_encrypted_error @encryption_dictionary unless @encryption_dictionary[:Filter] == :Standard
|
44
|
-
@key = set_general_key
|
45
|
-
case actual_object(@encryption_dictionary[:V])
|
46
|
-
when 1,2
|
47
|
-
# raise_encrypted_error
|
48
|
-
_perform_decrypt_proc_ @objects, self.method(:decrypt_RC4)
|
49
|
-
when 4
|
50
|
-
# raise unsupported error for now
|
51
|
-
raise_encrypted_error
|
52
|
-
# make sure CF is a Hash (as required by the PDF standard for this type of encryption).
|
53
|
-
raise_encrypted_error unless actual_object(@encryption_dictionary[:CF]).is_a?(Hash)
|
35
|
+
change_references_to_actual_values @encryption_dictionary
|
36
|
+
end
|
54
37
|
|
55
|
-
|
56
|
-
|
38
|
+
# call this to start the decryption.
|
39
|
+
def decrypt
|
40
|
+
raise_encrypted_error @encryption_dictionary unless @encryption_dictionary[:Filter] == :Standard
|
41
|
+
@key = set_general_key
|
42
|
+
case actual_object(@encryption_dictionary[:V])
|
43
|
+
when 1, 2
|
44
|
+
# raise_encrypted_error
|
45
|
+
_perform_decrypt_proc_ @objects, method(:decrypt_RC4)
|
46
|
+
when 4
|
47
|
+
# make sure CF is a Hash (as required by the PDF standard for this type of encryption).
|
48
|
+
raise_encrypted_error unless actual_object(@encryption_dictionary[:CF]).is_a?(Hash)
|
57
49
|
|
58
|
-
|
59
|
-
|
60
|
-
|
50
|
+
# support trivial case for now
|
51
|
+
# - same filter for streams (Stmf) and strings(Strf)
|
52
|
+
# - AND :CFM == :V2 (use algorithm 1)
|
53
|
+
raise_encrypted_error unless (@encryption_dictionary[:StmF] == @encryption_dictionary[:StrF])
|
61
54
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
55
|
+
cfilter = actual_object(@encryption_dictionary[:CF])[@encryption_dictionary[:StrF]]
|
56
|
+
raise_encrypted_error unless cfilter
|
57
|
+
raise_encrypted_error unless (cfilter[:AuthEvent] == :DocOpen)
|
58
|
+
if (cfilter[:CFM] == :V2)
|
59
|
+
_perform_decrypt_proc_ @objects, method(:decrypt_RC4)
|
60
|
+
elsif (cfilter[:CFM] == :AESV2)
|
61
|
+
_perform_decrypt_proc_ @objects, method(:decrypt_AES)
|
62
|
+
else
|
63
|
+
raise_encrypted_error
|
64
|
+
end
|
65
|
+
end
|
66
|
+
# rebuild stream lengths?
|
67
|
+
@objects
|
68
|
+
rescue => e
|
69
|
+
puts e
|
70
|
+
puts e.message
|
71
|
+
puts e.backtrace.join("\n")
|
72
|
+
raise_encrypted_error
|
73
|
+
end
|
70
74
|
|
71
|
-
|
75
|
+
protected
|
72
76
|
|
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
|
-
@key
|
115
|
-
end
|
116
|
-
def decrypt_none(encrypted, encrypted_id, encrypted_generation, encrypted_filter)
|
117
|
-
"encrypted"
|
118
|
-
end
|
119
|
-
def decrypt_RC4(encrypted, encrypted_id, encrypted_generation, encrypted_filter)
|
120
|
-
## start decryption using padding strings
|
121
|
-
object_key = @key.dup
|
122
|
-
object_key << [encrypted_id].pack('i')[0..2]
|
123
|
-
object_key << [encrypted_generation].pack('i')[0..1]
|
124
|
-
# (0..2).each { |e| object_key << (encrypted_id >> e*8 & 0xFF ) }
|
125
|
-
# (0..1).each { |e| object_key << (encrypted_generation >> e*8 & 0xFF ) }
|
126
|
-
key_length = object_key.length < 16 ? object_key.length : 16
|
127
|
-
rc4 = ::RC4.new( Digest::MD5.digest(object_key)[(0...key_length)] )
|
128
|
-
rc4.decrypt(encrypted)
|
129
|
-
end
|
130
|
-
def decrypt_AES(encrypted, encrypted_id, encrypted_generation, encrypted_filter)
|
131
|
-
## extract encryption_iv if it wasn't extracted yet
|
132
|
-
unless @encryption_iv
|
133
|
-
@encryption_iv = encrypted[0..15].to_i
|
134
|
-
#raise "Tryed decrypting using AES and couldn't extract iv" if @encryption_iv == 0
|
135
|
-
@encryption_iv = 0.chr * 16
|
136
|
-
#encrypted = encrypted[16..-1]
|
137
|
-
end
|
138
|
-
## start decryption using padding strings
|
139
|
-
object_key = @key.dup
|
140
|
-
(0..2).each { |e| object_key << (encrypted_id >> e*8 & 0xFF ) }
|
141
|
-
(0..1).each { |e| object_key << (encrypted_generation >> e*8 & 0xFF ) }
|
142
|
-
object_key << "sAlT"
|
143
|
-
key_length = object_key.length < 16 ? object_key.length : 16
|
144
|
-
cipher = OpenSSL::Cipher::Cipher.new("aes-#{object_key.length << 3}-cbc").decrypt
|
145
|
-
cipher.padding = 0
|
146
|
-
(cipher.update(encrypted) + cipher.final).unpack("C*")
|
147
|
-
end
|
77
|
+
def set_general_key(password = '')
|
78
|
+
# 1) make sure the initial key is 32 byte long (if no password, uses padding).
|
79
|
+
key = (password.bytes[0..32].to_a + @padding_key)[0..31].to_a.pack('C*').force_encoding(Encoding::ASCII_8BIT)
|
80
|
+
# 2) add the value of the encryption dictionary’s O entry
|
81
|
+
key << actual_object(@encryption_dictionary[:O]).to_s
|
82
|
+
# 3) Convert the integer value of the P entry to a 32-bit unsigned binary number
|
83
|
+
# and pass these bytes low-order byte first
|
84
|
+
key << [actual_object(@encryption_dictionary[:P])].pack('i')
|
85
|
+
# 4) Pass the first element of the file’s file identifier array
|
86
|
+
# (the value of the ID entry in the document’s trailer dictionary
|
87
|
+
key << actual_object(@root_dictionary[:ID])[0]
|
88
|
+
# # 4(a) (Security handlers of revision 4 or greater)
|
89
|
+
# # if document metadata is not being encrypted, add 4 bytes with the value 0xFFFFFFFF.
|
90
|
+
if actual_object(@encryption_dictionary[:R]) >= 4
|
91
|
+
if actual_object(@encryption_dictionary)[:EncryptMetadata] == false
|
92
|
+
key << "\xFF\xFF\xFF\xFF".force_encoding(Encoding::ASCII_8BIT)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
# 5) pass everything as a MD5 hash
|
96
|
+
key = Digest::MD5.digest(key)
|
97
|
+
# 5(a) h) (Security handlers of revision 3 or greater) Do the following 50 times:
|
98
|
+
# Take the output from the previous MD5 hash and
|
99
|
+
# pass the first n bytes of the output as input into a new MD5 hash,
|
100
|
+
# where n is the number of bytes of the encryption key as defined by the value of
|
101
|
+
# the encryption dictionary’s Length entry.
|
102
|
+
if actual_object(@encryption_dictionary[:R]) >= 3
|
103
|
+
50.times do |_i|
|
104
|
+
key = Digest::MD5.digest(key[0...actual_object(@encryption_dictionary[:Length])])
|
105
|
+
end
|
106
|
+
end
|
107
|
+
# 6) Set the encryption key to the first n bytes of the output from the final MD5 hash,
|
108
|
+
# where n shall always be 5 for security handlers of revision 2 but,
|
109
|
+
# for security handlers of revision 3 or greater,
|
110
|
+
# shall depend on the value of the encryption dictionary’s Length entry.
|
111
|
+
@key = if actual_object(@encryption_dictionary[:R]) >= 3
|
112
|
+
key[0..(actual_object(@encryption_dictionary[:Length]) / 8)]
|
113
|
+
else
|
114
|
+
key[0..4]
|
115
|
+
end
|
116
|
+
@key
|
117
|
+
end
|
148
118
|
|
149
|
-
|
119
|
+
def decrypt_none(_encrypted, _encrypted_id, _encrypted_generation, _encrypted_filter)
|
120
|
+
'encrypted'
|
121
|
+
end
|
150
122
|
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
warn "Stream registeded length was #{object[:Length].to_s} and the actual length was #{actual_length}." if actual_length < stream_length
|
163
|
-
length = [ stream_length, actual_length].min
|
164
|
-
object[:raw_stream_content] = decrypt_proc.call( (object[:raw_stream_content][0...length]), encrypted_id, encrypted_generation, encrypted_filter)
|
165
|
-
end
|
166
|
-
object.each {|k, v| object[k] = _perform_decrypt_proc_(v, decrypt_proc, encrypted_id, encrypted_generation, encrypted_filter) if k != :raw_stream_content && (v.is_a?(Hash) || v.is_a?(Array) || v.is_a?(String))} # assumes no decrypting is ever performed on keys
|
167
|
-
when object.is_a?(String)
|
168
|
-
return decrypt_proc.call(object, encrypted_id, encrypted_generation, encrypted_filter)
|
169
|
-
else
|
170
|
-
return object
|
171
|
-
end
|
172
|
-
|
173
|
-
end
|
123
|
+
def decrypt_RC4(encrypted, encrypted_id, encrypted_generation, _encrypted_filter)
|
124
|
+
## start decryption using padding strings
|
125
|
+
object_key = @key.dup
|
126
|
+
object_key << [encrypted_id].pack('i')[0..2]
|
127
|
+
object_key << [encrypted_generation].pack('i')[0..1]
|
128
|
+
# (0..2).each { |e| object_key << (encrypted_id >> e*8 & 0xFF ) }
|
129
|
+
# (0..1).each { |e| object_key << (encrypted_generation >> e*8 & 0xFF ) }
|
130
|
+
key_length = object_key.length < 16 ? object_key.length : 16
|
131
|
+
rc4 = ::RC4.new(Digest::MD5.digest(object_key)[(0...key_length)])
|
132
|
+
rc4.decrypt(encrypted)
|
133
|
+
end
|
174
134
|
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
135
|
+
def decrypt_AES(encrypted, encrypted_id, encrypted_generation, _encrypted_filter)
|
136
|
+
## start decryption using padding strings
|
137
|
+
object_key = @key.dup
|
138
|
+
object_key << [encrypted_id].pack('i')[0..2]
|
139
|
+
object_key << [encrypted_generation].pack('i')[0..1]
|
140
|
+
object_key << 'sAlT'.force_encoding(Encoding::ASCII_8BIT)
|
141
|
+
key_length = object_key.length < 16 ? object_key.length : 16
|
180
142
|
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
@objects.each do |stored_object|
|
194
|
-
return stored_object if ( stored_object.is_a?(Hash) &&
|
195
|
-
reference_hash[:indirect_reference_id] == stored_object[:indirect_reference_id] &&
|
196
|
-
reference_hash[:indirect_generation_number] == stored_object[:indirect_generation_number] )
|
197
|
-
end
|
198
|
-
warn "didn't find reference #{reference_hash}"
|
199
|
-
nil
|
200
|
-
end
|
143
|
+
begin
|
144
|
+
cipher = OpenSSL::Cipher.new("aes-#{key_length << 3}-cbc")
|
145
|
+
cipher.decrypt
|
146
|
+
cipher.key = Digest::MD5.digest(object_key)[(0...key_length)]
|
147
|
+
cipher.iv = encrypted[0..15]
|
148
|
+
cipher.padding = 0
|
149
|
+
cipher.update(encrypted[16..-1]) + cipher.final
|
150
|
+
rescue StandardError => e
|
151
|
+
# puts e.class.name
|
152
|
+
encrypted
|
153
|
+
end
|
154
|
+
end
|
201
155
|
|
156
|
+
protected
|
202
157
|
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
158
|
+
def _perform_decrypt_proc_(object, decrypt_proc, encrypted_id = nil, encrypted_generation = nil, encrypted_filter = nil)
|
159
|
+
if object.is_a?(Array)
|
160
|
+
object.map! { |item| _perform_decrypt_proc_(item, decrypt_proc, encrypted_id, encrypted_generation, encrypted_filter) }
|
161
|
+
elsif object.is_a?(Hash)
|
162
|
+
encrypted_id ||= actual_object(object[:indirect_reference_id])
|
163
|
+
encrypted_generation ||= actual_object(object[:indirect_generation_number])
|
164
|
+
encrypted_filter ||= actual_object(object[:Filter])
|
165
|
+
if object[:raw_stream_content] && !object[:raw_stream_content].empty?
|
166
|
+
stream_length = actual_object(object[:Length])
|
167
|
+
actual_length = object[:raw_stream_content].bytesize
|
168
|
+
# p stream_length
|
169
|
+
# p actual_length
|
170
|
+
# p object[:Length]
|
171
|
+
# p object
|
172
|
+
warn "Stream registered length was #{object[:Length]} and the actual length was #{actual_length}." if actual_length < stream_length
|
173
|
+
length = [stream_length, actual_length].min
|
174
|
+
object[:raw_stream_content] = decrypt_proc.call((object[:raw_stream_content][0...length]), encrypted_id, encrypted_generation, encrypted_filter)
|
175
|
+
end
|
176
|
+
object.each { |k, v| object[k] = _perform_decrypt_proc_(v, decrypt_proc, encrypted_id, encrypted_generation, encrypted_filter) if k != :raw_stream_content && (v.is_a?(Hash) || v.is_a?(Array) || v.is_a?(String)) } # assumes no decrypting is never performed on keys
|
177
|
+
elsif object.is_a?(String)
|
178
|
+
return decrypt_proc.call(object, encrypted_id, encrypted_generation, encrypted_filter)
|
179
|
+
else
|
180
|
+
return object
|
181
|
+
end
|
182
|
+
end
|
209
183
|
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
## ... I had a bad internet connection so I ended up
|
216
|
-
## copying it from the web page I had in my cache.
|
217
|
-
## This wonderful work was done by Caige Nichols.
|
218
|
-
#####################################################
|
219
|
-
# class RC4
|
220
|
-
# def initialize(str)
|
221
|
-
# begin
|
222
|
-
# raise SyntaxError, "RC4: Key supplied is blank" if str.eql?('')
|
184
|
+
def raise_encrypted_error(object = nil)
|
185
|
+
object ||= @encryption_dictionary.to_s.split(',').join("\n")
|
186
|
+
warn "Data raising exception:\n #{object.to_s.split(',').join("\n")}"
|
187
|
+
raise EncryptionError, 'File is encrypted - not supported.'
|
188
|
+
end
|
223
189
|
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
# end
|
235
|
-
# end
|
190
|
+
def change_references_to_actual_values(hash_with_references = {})
|
191
|
+
hash_with_references.each do |k, v|
|
192
|
+
next unless v.is_a?(Hash) && v[:is_reference_only]
|
193
|
+
hash_with_references[k] = get_refernced_object(v)
|
194
|
+
hash_with_references[k] = hash_with_references[k][:indirect_without_dictionary] if hash_with_references[k].is_a?(Hash) && hash_with_references[k][:indirect_without_dictionary]
|
195
|
+
warn "Couldn't connect all values from references - didn't find reference #{hash_with_references}!!!" if hash_with_references[k].nil?
|
196
|
+
hash_with_references[k] = v unless hash_with_references[k]
|
197
|
+
end
|
198
|
+
hash_with_references
|
199
|
+
end
|
236
200
|
|
237
|
-
|
238
|
-
|
239
|
-
|
201
|
+
def get_refernced_object(reference_hash = {})
|
202
|
+
@objects.each do |stored_object|
|
203
|
+
return (stored_object[:indirect_without_dictionary] || stored_object) if stored_object.is_a?(Hash) &&
|
204
|
+
reference_hash[:indirect_reference_id] == stored_object[:indirect_reference_id] &&
|
205
|
+
reference_hash[:indirect_generation_number] == stored_object[:indirect_generation_number]
|
206
|
+
end
|
207
|
+
warn "didn't find reference #{reference_hash}"
|
208
|
+
nil
|
209
|
+
end
|
240
210
|
|
241
|
-
|
242
|
-
|
243
|
-
|
211
|
+
def actual_object(obj)
|
212
|
+
return get_refernced_object(obj) if obj.is_a?(Hash) && obj[:indirect_reference_id]
|
213
|
+
obj
|
214
|
+
end
|
244
215
|
|
245
|
-
|
216
|
+
# # returns the PDF Object Hash holding the acutal data (if exists) or the original hash (if it wasn't a reference)
|
217
|
+
# #
|
218
|
+
# # works only AFTER references have been connected.
|
219
|
+
# def get_referenced object
|
220
|
+
# object[:referenced_object] || object
|
221
|
+
# end
|
222
|
+
end
|
223
|
+
#####################################################
|
224
|
+
## The following isn't my code!!!!
|
225
|
+
## It is subject to a different license and copyright.
|
226
|
+
## This was the code for the RC4 Gem,
|
227
|
+
## ... I had a bad internet connection so I ended up
|
228
|
+
## copying it from the web page I had in my cache.
|
229
|
+
## This wonderful work was done by Caige Nichols.
|
230
|
+
#####################################################
|
231
|
+
# class RC4
|
232
|
+
# def initialize(str)
|
233
|
+
# begin
|
234
|
+
# raise SyntaxError, "RC4: Key supplied is blank" if str.eql?('')
|
246
235
|
|
247
|
-
|
236
|
+
# @q1, @q2 = 0, 0
|
237
|
+
# @key = []
|
238
|
+
# str.each_byte { |elem| @key << elem } while @key.size < 256
|
239
|
+
# @key.slice!(256..@key.size-1) if @key.size >= 256
|
240
|
+
# @s = (0..255).to_a
|
241
|
+
# j = 0
|
242
|
+
# 0.upto(255) do |i|
|
243
|
+
# j = (j + @s[i] + @key[i] ) % 256
|
244
|
+
# @s[i], @s[j] = @s[j], @s[i]
|
245
|
+
# end
|
246
|
+
# end
|
247
|
+
# end
|
248
248
|
|
249
|
-
|
250
|
-
|
251
|
-
|
249
|
+
# def encrypt!(text)
|
250
|
+
# process text
|
251
|
+
# end
|
252
252
|
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
# @s[@q1], @s[@q2] = @s[@q2], @s[@q1]
|
257
|
-
# @s[(@s[@q1]+@s[@q2]) % 256]
|
258
|
-
# end
|
259
|
-
# end
|
253
|
+
# def encrypt(text)
|
254
|
+
# process text.dup
|
255
|
+
# end
|
260
256
|
|
261
|
-
|
257
|
+
# alias_method :decrypt, :encrypt
|
262
258
|
|
259
|
+
# private
|
260
|
+
|
261
|
+
# def process(text)
|
262
|
+
# text.unpack("C*").map { |c| c ^ round }.pack("C*")
|
263
|
+
# end
|
264
|
+
|
265
|
+
# def round
|
266
|
+
# @q1 = (@q1 + 1) % 256
|
267
|
+
# @q2 = (@q2 + @s[@q1]) % 256
|
268
|
+
# @s[@q1], @s[@q2] = @s[@q2], @s[@q1]
|
269
|
+
# @s[(@s[@q1]+@s[@q2]) % 256]
|
270
|
+
# end
|
271
|
+
# end
|
272
|
+
end
|