origami 1.0.2
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.
- data/COPYING.LESSER +165 -0
- data/README +77 -0
- data/VERSION +1 -0
- data/bin/config/pdfcop.conf.yml +237 -0
- data/bin/gui/about.rb +46 -0
- data/bin/gui/config.rb +132 -0
- data/bin/gui/file.rb +385 -0
- data/bin/gui/hexdump.rb +74 -0
- data/bin/gui/hexview.rb +91 -0
- data/bin/gui/imgview.rb +72 -0
- data/bin/gui/menu.rb +392 -0
- data/bin/gui/properties.rb +132 -0
- data/bin/gui/signing.rb +635 -0
- data/bin/gui/textview.rb +107 -0
- data/bin/gui/treeview.rb +409 -0
- data/bin/gui/walker.rb +282 -0
- data/bin/gui/xrefs.rb +79 -0
- data/bin/pdf2graph +121 -0
- data/bin/pdf2ruby +353 -0
- data/bin/pdfcocoon +104 -0
- data/bin/pdfcop +455 -0
- data/bin/pdfdecompress +104 -0
- data/bin/pdfdecrypt +95 -0
- data/bin/pdfencrypt +112 -0
- data/bin/pdfextract +221 -0
- data/bin/pdfmetadata +123 -0
- data/bin/pdfsh +13 -0
- data/bin/pdfwalker +7 -0
- data/bin/shell/.irbrc +104 -0
- data/bin/shell/console.rb +136 -0
- data/bin/shell/hexdump.rb +83 -0
- data/origami.rb +36 -0
- data/origami/3d.rb +239 -0
- data/origami/acroform.rb +321 -0
- data/origami/actions.rb +299 -0
- data/origami/adobe/fdf.rb +259 -0
- data/origami/adobe/ppklite.rb +489 -0
- data/origami/annotations.rb +775 -0
- data/origami/array.rb +187 -0
- data/origami/boolean.rb +101 -0
- data/origami/catalog.rb +486 -0
- data/origami/destinations.rb +213 -0
- data/origami/dictionary.rb +188 -0
- data/origami/docmdp.rb +96 -0
- data/origami/encryption.rb +1293 -0
- data/origami/export.rb +283 -0
- data/origami/file.rb +222 -0
- data/origami/filters.rb +250 -0
- data/origami/filters/ascii.rb +189 -0
- data/origami/filters/ccitt.rb +515 -0
- data/origami/filters/crypt.rb +47 -0
- data/origami/filters/dct.rb +61 -0
- data/origami/filters/flate.rb +112 -0
- data/origami/filters/jbig2.rb +63 -0
- data/origami/filters/jpx.rb +53 -0
- data/origami/filters/lzw.rb +195 -0
- data/origami/filters/predictors.rb +276 -0
- data/origami/filters/runlength.rb +117 -0
- data/origami/font.rb +209 -0
- data/origami/functions.rb +93 -0
- data/origami/graphics.rb +33 -0
- data/origami/graphics/colors.rb +191 -0
- data/origami/graphics/instruction.rb +126 -0
- data/origami/graphics/path.rb +154 -0
- data/origami/graphics/patterns.rb +180 -0
- data/origami/graphics/state.rb +164 -0
- data/origami/graphics/text.rb +224 -0
- data/origami/graphics/xobject.rb +493 -0
- data/origami/header.rb +90 -0
- data/origami/linearization.rb +318 -0
- data/origami/metadata.rb +114 -0
- data/origami/name.rb +170 -0
- data/origami/null.rb +75 -0
- data/origami/numeric.rb +188 -0
- data/origami/obfuscation.rb +233 -0
- data/origami/object.rb +527 -0
- data/origami/outline.rb +59 -0
- data/origami/page.rb +559 -0
- data/origami/parser.rb +268 -0
- data/origami/parsers/fdf.rb +45 -0
- data/origami/parsers/pdf.rb +27 -0
- data/origami/parsers/pdf/linear.rb +113 -0
- data/origami/parsers/ppklite.rb +86 -0
- data/origami/pdf.rb +1144 -0
- data/origami/reference.rb +113 -0
- data/origami/signature.rb +474 -0
- data/origami/stream.rb +575 -0
- data/origami/string.rb +416 -0
- data/origami/trailer.rb +173 -0
- data/origami/webcapture.rb +87 -0
- data/origami/xfa.rb +3027 -0
- data/origami/xreftable.rb +447 -0
- data/templates/patterns.rb +66 -0
- data/templates/widgets.rb +173 -0
- data/templates/xdp.rb +92 -0
- data/tests/dataset/test.dummycrt +28 -0
- data/tests/dataset/test.dummykey +27 -0
- data/tests/tc_actions.rb +32 -0
- data/tests/tc_annotations.rb +85 -0
- data/tests/tc_pages.rb +37 -0
- data/tests/tc_pdfattach.rb +24 -0
- data/tests/tc_pdfencrypt.rb +110 -0
- data/tests/tc_pdfnew.rb +32 -0
- data/tests/tc_pdfparse.rb +98 -0
- data/tests/tc_pdfsig.rb +37 -0
- data/tests/tc_streams.rb +129 -0
- data/tests/ts_pdf.rb +45 -0
- metadata +193 -0
@@ -0,0 +1,1293 @@
|
|
1
|
+
=begin
|
2
|
+
|
3
|
+
= File
|
4
|
+
encryption.rb
|
5
|
+
|
6
|
+
= Info
|
7
|
+
This file is part of Origami, PDF manipulation framework for Ruby
|
8
|
+
Copyright (C) 2010 Guillaume DelugrÈ <guillaume@security-labs.org>
|
9
|
+
All right reserved.
|
10
|
+
|
11
|
+
Origami is free software: you can redistribute it and/or modify
|
12
|
+
it under the terms of the GNU Lesser General Public License as published by
|
13
|
+
the Free Software Foundation, either version 3 of the License, or
|
14
|
+
(at your option) any later version.
|
15
|
+
|
16
|
+
Origami is distributed in the hope that it will be useful,
|
17
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
18
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
19
|
+
GNU Lesser General Public License for more details.
|
20
|
+
|
21
|
+
You should have received a copy of the GNU Lesser General Public License
|
22
|
+
along with Origami. If not, see <http://www.gnu.org/licenses/>.
|
23
|
+
|
24
|
+
=end
|
25
|
+
|
26
|
+
require 'digest/md5'
|
27
|
+
require 'digest/sha2'
|
28
|
+
require 'iconv'
|
29
|
+
|
30
|
+
module Origami
|
31
|
+
|
32
|
+
class EncryptionError < Exception #:nodoc:
|
33
|
+
end
|
34
|
+
|
35
|
+
class EncryptionInvalidPasswordError < EncryptionError #:nodoc:
|
36
|
+
end
|
37
|
+
|
38
|
+
class EncryptionNotSupportedError < EncryptionError #:nodoc:
|
39
|
+
end
|
40
|
+
|
41
|
+
class PDF
|
42
|
+
|
43
|
+
#
|
44
|
+
# Returns whether the PDF file is encrypted.
|
45
|
+
#
|
46
|
+
def is_encrypted?
|
47
|
+
has_attr? :Encrypt
|
48
|
+
end
|
49
|
+
|
50
|
+
#
|
51
|
+
# Decrypts the current document (only RC4 40..128 bits).
|
52
|
+
# TODO: AESv3
|
53
|
+
# _passwd_:: The password to decrypt the document.
|
54
|
+
#
|
55
|
+
def decrypt(passwd = "")
|
56
|
+
|
57
|
+
unless self.is_encrypted?
|
58
|
+
raise EncryptionError, "PDF is not encrypted"
|
59
|
+
end
|
60
|
+
|
61
|
+
encrypt_dict = get_doc_attr(:Encrypt)
|
62
|
+
handler = Encryption::Standard::Dictionary.new(encrypt_dict.copy)
|
63
|
+
|
64
|
+
unless handler.Filter == :Standard
|
65
|
+
raise EncryptionNotSupportedError, "Unknown security handler : '#{handler.Filter.to_s}'"
|
66
|
+
end
|
67
|
+
|
68
|
+
case handler.V.to_i
|
69
|
+
when 1,2 then str_algo = stm_algo = Encryption::ARC4
|
70
|
+
when 4,5
|
71
|
+
if handler[:CF].is_a?(Dictionary)
|
72
|
+
cfs = handler[:CF]
|
73
|
+
|
74
|
+
if handler[:StrF].is_a?(Name) and cfs[handler[:StrF]].is_a?(Dictionary)
|
75
|
+
cfdict = cfs[handler[:StrF]]
|
76
|
+
|
77
|
+
str_algo =
|
78
|
+
if cfdict[:CFM] == :V2 then Encryption::ARC4
|
79
|
+
elsif cfdict[:CFM] == :AESV2 then Encryption::AES
|
80
|
+
elsif cfdict[:CFM] == :None then Encryption::Identity
|
81
|
+
elsif cfdict[:CFM] == :AESV3 and handler.V.to_i == 5 then Encryption::AES
|
82
|
+
else
|
83
|
+
raise EncryptionNotSupportedError, "Unsupported encryption version : #{handler.V}"
|
84
|
+
end
|
85
|
+
else
|
86
|
+
str_algo = Encryption::Identity
|
87
|
+
end
|
88
|
+
|
89
|
+
if handler[:StmF].is_a?(Name) and cfs[handler[:StmF]].is_a?(Dictionary)
|
90
|
+
cfdict = cfs[handler[:StmF]]
|
91
|
+
|
92
|
+
stm_algo =
|
93
|
+
if cfdict[:CFM] == :V2 then Encryption::ARC4
|
94
|
+
elsif cfdict[:CFM] == :AESV2 then Encryption::AES
|
95
|
+
elsif cfdict[:CFM] == :None then Encryption::Identity
|
96
|
+
elsif cfdict[:CFM] == :AESV3 and handler.V.to_i == 5 then Encryption::AES
|
97
|
+
else
|
98
|
+
raise EncryptionNotSupportedError, "Unsupported encryption version : #{handler.V}"
|
99
|
+
end
|
100
|
+
else
|
101
|
+
stm_algo = Encryption::Identity
|
102
|
+
end
|
103
|
+
|
104
|
+
else
|
105
|
+
str_algo = stm_algo = Encryption::Identity
|
106
|
+
end
|
107
|
+
|
108
|
+
else
|
109
|
+
raise EncryptionNotSupportedError, "Unsupported encryption version : #{handler.V}"
|
110
|
+
end
|
111
|
+
|
112
|
+
doc_id = get_doc_attr(:ID)
|
113
|
+
unless doc_id.is_a?(Array)
|
114
|
+
raise EncryptionError, "Document ID was not found or is invalid" unless handler.V.to_i == 5
|
115
|
+
else
|
116
|
+
doc_id = doc_id.first
|
117
|
+
end
|
118
|
+
|
119
|
+
if handler.is_owner_password?(passwd, doc_id)
|
120
|
+
if handler.V.to_i < 5
|
121
|
+
user_passwd = handler.retrieve_user_password(passwd)
|
122
|
+
encryption_key = handler.compute_user_encryption_key(user_passwd, doc_id)
|
123
|
+
else
|
124
|
+
encryption_key = handler.compute_owner_encryption_key(passwd)
|
125
|
+
end
|
126
|
+
|
127
|
+
elsif handler.is_user_password?(passwd, doc_id)
|
128
|
+
encryption_key = handler.compute_user_encryption_key(passwd, doc_id)
|
129
|
+
else
|
130
|
+
raise EncryptionInvalidPasswordError
|
131
|
+
end
|
132
|
+
|
133
|
+
|
134
|
+
#self.extend(Encryption::EncryptedDocument)
|
135
|
+
#self.encryption_dict = encrypt_dict
|
136
|
+
#self.encryption_key = encryption_key
|
137
|
+
#self.stm_algo = self.str_algo = algorithm
|
138
|
+
|
139
|
+
encrypt_metadata = (handler.EncryptMetadata != false)
|
140
|
+
|
141
|
+
self.extend(Encryption::EncryptedDocument)
|
142
|
+
self.encryption_dict = handler
|
143
|
+
self.encryption_key = encryption_key
|
144
|
+
self.stm_algo,self.str_algo = stm_algo,str_algo
|
145
|
+
|
146
|
+
#
|
147
|
+
# Should be fixed to exclude only the active XRefStream
|
148
|
+
#
|
149
|
+
metadata = self.Catalog.Metadata
|
150
|
+
|
151
|
+
self.indirect_objects.each do |indobj|
|
152
|
+
encrypted_objects = []
|
153
|
+
case indobj
|
154
|
+
when String,Stream then encrypted_objects << indobj
|
155
|
+
when Dictionary,Array then encrypted_objects |= indobj.strings_cache
|
156
|
+
end
|
157
|
+
|
158
|
+
encrypted_objects.each do |obj|
|
159
|
+
|
160
|
+
case obj
|
161
|
+
when String
|
162
|
+
next if obj.equal?(encrypt_dict[:U]) or
|
163
|
+
obj.equal?(encrypt_dict[:O]) or
|
164
|
+
obj.equal?(encrypt_dict[:UE]) or
|
165
|
+
obj.equal?(encrypt_dict[:OE]) or
|
166
|
+
obj.equal?(encrypt_dict[:Perms]) or
|
167
|
+
(obj.parent.is_a?(Signature::DigitalSignature) and obj.equal?(obj.parent[:Contents]))
|
168
|
+
|
169
|
+
obj.extend(Encryption::EncryptedString)
|
170
|
+
obj.encryption_handler = handler
|
171
|
+
obj.encryption_key = encryption_key
|
172
|
+
obj.algorithm = stm_algo
|
173
|
+
obj.decrypted = false
|
174
|
+
obj.decrypt!
|
175
|
+
|
176
|
+
when Stream
|
177
|
+
next if obj.is_a?(XRefStream) or (not encrypt_metadata and obj.equal?(metadata))
|
178
|
+
obj.extend(Encryption::EncryptedStream)
|
179
|
+
obj.encryption_handler = handler
|
180
|
+
obj.encryption_key = encryption_key
|
181
|
+
obj.algorithm = stm_algo
|
182
|
+
obj.decrypted = false
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
self
|
188
|
+
end
|
189
|
+
|
190
|
+
#
|
191
|
+
# Encrypts the current document with the provided passwords.
|
192
|
+
# The document will be encrypted at writing-on-disk time.
|
193
|
+
# _userpasswd_:: The user password.
|
194
|
+
# _ownerpasswd_:: The owner password.
|
195
|
+
# _options_:: A set of options to configure encryption.
|
196
|
+
#
|
197
|
+
def encrypt(options = {})
|
198
|
+
|
199
|
+
if self.is_encrypted?
|
200
|
+
raise EncryptionError, "PDF is already encrypted"
|
201
|
+
end
|
202
|
+
|
203
|
+
#
|
204
|
+
# Default encryption options.
|
205
|
+
#
|
206
|
+
params =
|
207
|
+
{
|
208
|
+
:user_passwd => '',
|
209
|
+
:owner_passwd => '',
|
210
|
+
:cipher => 'rc4', # :RC4 or :AES
|
211
|
+
:key_size => 128, # Key size in bits
|
212
|
+
:encrypt_metadata => true, # Metadata shall be encrypted?
|
213
|
+
:permissions => Encryption::Standard::Permissions::ALL # Document permissions
|
214
|
+
}.update(options)
|
215
|
+
|
216
|
+
userpasswd, ownerpasswd = params[:user_passwd], params[:owner_passwd]
|
217
|
+
|
218
|
+
case params[:cipher].upcase
|
219
|
+
when 'RC4'
|
220
|
+
algorithm = Encryption::ARC4
|
221
|
+
if (40..128) === params[:key_size] and params[:key_size] % 8 == 0
|
222
|
+
if params[:key_size] > 40
|
223
|
+
version = 2
|
224
|
+
revision = 3
|
225
|
+
else
|
226
|
+
version = 1
|
227
|
+
revision = 2
|
228
|
+
end
|
229
|
+
else
|
230
|
+
raise EncryptionError, "Invalid RC4 key length"
|
231
|
+
end
|
232
|
+
when 'AES'
|
233
|
+
algorithm = Encryption::AES
|
234
|
+
if params[:key_size] == 128
|
235
|
+
version = revision = 4
|
236
|
+
elsif params[:key_size] == 256
|
237
|
+
version = revision = 5
|
238
|
+
else
|
239
|
+
raise EncryptionError, "Invalid AES key length (Only 128 and 256 bits keys are supported)"
|
240
|
+
end
|
241
|
+
else
|
242
|
+
raise EncryptionNotSupportedError, "Cipher not supported : #{params[:cipher]}"
|
243
|
+
end
|
244
|
+
|
245
|
+
doc_id = (get_doc_attr(:ID) || gen_id).first
|
246
|
+
|
247
|
+
handler = Encryption::Standard::Dictionary.new
|
248
|
+
handler.Filter = :Standard #:nodoc:
|
249
|
+
handler.V = version
|
250
|
+
handler.R = revision
|
251
|
+
handler.Length = params[:key_size]
|
252
|
+
handler.P = -1 # params[:Permissions]
|
253
|
+
|
254
|
+
if revision >= 4
|
255
|
+
handler.EncryptMetadata = params[:encrypt_metadata]
|
256
|
+
handler.CF = Dictionary.new
|
257
|
+
cryptfilter = Encryption::CryptFilterDictionary.new
|
258
|
+
cryptfilter.AuthEvent = :DocOpen
|
259
|
+
|
260
|
+
if revision == 4
|
261
|
+
cryptfilter.CFM = :AESV2
|
262
|
+
else
|
263
|
+
cryptfilter.CFM = :AESV3
|
264
|
+
end
|
265
|
+
|
266
|
+
cryptfilter.Length = params[:key_size] >> 3
|
267
|
+
|
268
|
+
handler.CF[:StdCF] = cryptfilter
|
269
|
+
handler.StmF = handler.StrF = :StdCF
|
270
|
+
end
|
271
|
+
|
272
|
+
handler.set_passwords(ownerpasswd, userpasswd, doc_id)
|
273
|
+
encryption_key = handler.compute_user_encryption_key(userpasswd, doc_id)
|
274
|
+
|
275
|
+
fileInfo = get_trailer_info
|
276
|
+
fileInfo[:Encrypt] = self << handler
|
277
|
+
|
278
|
+
self.extend(Encryption::EncryptedDocument)
|
279
|
+
self.encryption_dict = handler
|
280
|
+
self.encryption_key = encryption_key
|
281
|
+
self.stm_algo = self.str_algo = algorithm
|
282
|
+
|
283
|
+
self
|
284
|
+
end
|
285
|
+
|
286
|
+
end
|
287
|
+
|
288
|
+
#
|
289
|
+
# Module to provide support for encrypting and decrypting PDF documents.
|
290
|
+
#
|
291
|
+
module Encryption
|
292
|
+
|
293
|
+
module EncryptedDocument
|
294
|
+
|
295
|
+
attr_writer :encryption_key
|
296
|
+
attr_writer :encryption_dict
|
297
|
+
attr_writer :stm_algo
|
298
|
+
attr_writer :str_algo
|
299
|
+
|
300
|
+
def physicalize(options = {})
|
301
|
+
|
302
|
+
def build(obj, revision, options) #:nodoc:
|
303
|
+
if obj.is_a?(EncryptedObject) # already built
|
304
|
+
if options[:decrypt] == true
|
305
|
+
obj.pre_build
|
306
|
+
obj.decrypt!
|
307
|
+
obj.decrypted = false # makes it believe no encryption pass is required
|
308
|
+
obj.post_build
|
309
|
+
end
|
310
|
+
|
311
|
+
return
|
312
|
+
end
|
313
|
+
|
314
|
+
if obj.is_a?(ObjectStream)
|
315
|
+
obj.each do |subobj|
|
316
|
+
build(subobj, revision, options)
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
320
|
+
obj.pre_build
|
321
|
+
|
322
|
+
case obj
|
323
|
+
when String
|
324
|
+
if not obj.equal?(@encryption_dict[:U]) and
|
325
|
+
not obj.equal?(@encryption_dict[:O]) and
|
326
|
+
not obj.equal?(@encryption_dict[:UE]) and
|
327
|
+
not obj.equal?(@encryption_dict[:OE]) and
|
328
|
+
not obj.equal?(@encryption_dict[:Perms]) and
|
329
|
+
not (obj.parent.is_a?(Signature::DigitalSignature) and obj.equal?(obj.parent[:Contents])) and
|
330
|
+
not obj.indirect_parent.parent.is_a?(ObjectStream)
|
331
|
+
|
332
|
+
obj.extend(EncryptedString)
|
333
|
+
obj.decrypted = true
|
334
|
+
obj.encryption_handler = @encryption_dict
|
335
|
+
obj.encryption_key = @encryption_key
|
336
|
+
obj.algorithm = @str_algo
|
337
|
+
end
|
338
|
+
|
339
|
+
when Stream
|
340
|
+
return if obj.is_a?(XRefStream)
|
341
|
+
return if obj.equal?(self.Catalog.Metadata) and not @encryption_dict.EncryptMetadata
|
342
|
+
obj.extend(EncryptedStream)
|
343
|
+
obj.decrypted = true
|
344
|
+
obj.encryption_handler = @encryption_dict
|
345
|
+
obj.encryption_key = @encryption_key
|
346
|
+
obj.algorithm = @stm_algo
|
347
|
+
|
348
|
+
when Dictionary, Array
|
349
|
+
obj.map! do |subobj|
|
350
|
+
if subobj.is_indirect?
|
351
|
+
if get_object(subobj.reference)
|
352
|
+
subobj.reference
|
353
|
+
else
|
354
|
+
ref = add_to_revision(subobj, revision)
|
355
|
+
build(subobj, revision, options)
|
356
|
+
ref
|
357
|
+
end
|
358
|
+
else
|
359
|
+
subobj
|
360
|
+
end
|
361
|
+
end
|
362
|
+
|
363
|
+
obj.each do |subobj|
|
364
|
+
build(subobj, revision, options)
|
365
|
+
end
|
366
|
+
end
|
367
|
+
|
368
|
+
obj.post_build
|
369
|
+
end
|
370
|
+
|
371
|
+
# stack up every root objects
|
372
|
+
indirect_objects_by_rev.each do |obj, revision|
|
373
|
+
build(obj, revision, options)
|
374
|
+
end
|
375
|
+
|
376
|
+
# remove encrypt dictionary if requested
|
377
|
+
if options[:decrypt]
|
378
|
+
delete_object(get_trailer_info[:Encrypt])
|
379
|
+
get_trailer_info[:Encrypt] = nil
|
380
|
+
end
|
381
|
+
|
382
|
+
self
|
383
|
+
end
|
384
|
+
|
385
|
+
end
|
386
|
+
|
387
|
+
#
|
388
|
+
# Module for encrypted PDF objects.
|
389
|
+
#
|
390
|
+
module EncryptedObject #:nodoc
|
391
|
+
|
392
|
+
attr_writer :encryption_key
|
393
|
+
attr_writer :algorithm
|
394
|
+
attr_writer :encryption_handler
|
395
|
+
attr_accessor :decrypted
|
396
|
+
|
397
|
+
def self.extended(obj)
|
398
|
+
obj.decrypted = false
|
399
|
+
end
|
400
|
+
|
401
|
+
def post_build
|
402
|
+
encrypt!
|
403
|
+
|
404
|
+
super
|
405
|
+
end
|
406
|
+
|
407
|
+
private
|
408
|
+
|
409
|
+
def compute_object_key
|
410
|
+
if @encryption_handler.V < 5
|
411
|
+
parent = self.indirect_parent
|
412
|
+
no, gen = parent.no, parent.generation
|
413
|
+
k = @encryption_key + [no].pack("I")[0..2] + [gen].pack("I")[0..1]
|
414
|
+
|
415
|
+
key_len = (k.length > 16) ? 16 : k.length
|
416
|
+
k << "sAlT" if @algorithm == Encryption::AES
|
417
|
+
|
418
|
+
Digest::MD5.digest(k)[0, key_len]
|
419
|
+
else
|
420
|
+
@encryption_key
|
421
|
+
end
|
422
|
+
end
|
423
|
+
|
424
|
+
end
|
425
|
+
|
426
|
+
#
|
427
|
+
# Module for encrypted String.
|
428
|
+
#
|
429
|
+
module EncryptedString
|
430
|
+
include EncryptedObject
|
431
|
+
|
432
|
+
def encrypt!
|
433
|
+
if @decrypted
|
434
|
+
key = compute_object_key
|
435
|
+
|
436
|
+
encrypted_data =
|
437
|
+
if @algorithm == ARC4 or @algorithm == Identity
|
438
|
+
@algorithm.encrypt(key, self.value)
|
439
|
+
else
|
440
|
+
iv = ::Array.new(AES::BLOCKSIZE) { rand(256) }.pack('C*')
|
441
|
+
@algorithm.encrypt(key, iv, self.value)
|
442
|
+
end
|
443
|
+
|
444
|
+
@decrypted = false
|
445
|
+
|
446
|
+
self.replace(encrypted_data)
|
447
|
+
self.freeze
|
448
|
+
end
|
449
|
+
|
450
|
+
self
|
451
|
+
end
|
452
|
+
|
453
|
+
def decrypt!
|
454
|
+
unless @decrypted
|
455
|
+
key = compute_object_key
|
456
|
+
self.replace(@algorithm.decrypt(key, self.to_str))
|
457
|
+
@decrypted = true
|
458
|
+
end
|
459
|
+
|
460
|
+
self
|
461
|
+
end
|
462
|
+
end
|
463
|
+
|
464
|
+
#
|
465
|
+
# Module for encrypted Stream.
|
466
|
+
#
|
467
|
+
module EncryptedStream
|
468
|
+
include EncryptedObject
|
469
|
+
|
470
|
+
def encrypt!
|
471
|
+
if @decrypted
|
472
|
+
encode!
|
473
|
+
|
474
|
+
key = compute_object_key
|
475
|
+
|
476
|
+
@rawdata =
|
477
|
+
if @algorithm == ARC4 or @algorithm == Identity
|
478
|
+
@algorithm.encrypt(key, self.rawdata)
|
479
|
+
else
|
480
|
+
iv = ::Array.new(AES::BLOCKSIZE) { rand(256) }.pack('C*')
|
481
|
+
@algorithm.encrypt(key, iv, @rawdata)
|
482
|
+
end
|
483
|
+
|
484
|
+
@decrypted = false
|
485
|
+
|
486
|
+
@rawdata.freeze
|
487
|
+
self.freeze
|
488
|
+
end
|
489
|
+
|
490
|
+
self
|
491
|
+
end
|
492
|
+
|
493
|
+
def decrypt!
|
494
|
+
unless @decrypted
|
495
|
+
key = compute_object_key
|
496
|
+
|
497
|
+
self.rawdata = @algorithm.decrypt(key, @rawdata)
|
498
|
+
@decrypted = true
|
499
|
+
end
|
500
|
+
|
501
|
+
self
|
502
|
+
end
|
503
|
+
|
504
|
+
end
|
505
|
+
|
506
|
+
#
|
507
|
+
# Identity transformation.
|
508
|
+
#
|
509
|
+
module Identity
|
510
|
+
def Identity.encrypt(key, data)
|
511
|
+
data
|
512
|
+
end
|
513
|
+
|
514
|
+
def Identity.decrypt(key, data)
|
515
|
+
data
|
516
|
+
end
|
517
|
+
end
|
518
|
+
|
519
|
+
#
|
520
|
+
# Pure Ruby implementation of the aRC4 symmetric algorithm
|
521
|
+
#
|
522
|
+
class ARC4
|
523
|
+
|
524
|
+
#
|
525
|
+
# Encrypts data using the given key
|
526
|
+
#
|
527
|
+
def ARC4.encrypt(key, data)
|
528
|
+
ARC4.new(key).encrypt(data)
|
529
|
+
end
|
530
|
+
|
531
|
+
#
|
532
|
+
# Decrypts data using the given key
|
533
|
+
#
|
534
|
+
def ARC4.decrypt(key, data)
|
535
|
+
ARC4.new(key).decrypt(data)
|
536
|
+
end
|
537
|
+
|
538
|
+
#
|
539
|
+
# Creates and initialises a new aRC4 generator using given key
|
540
|
+
#
|
541
|
+
def initialize(key)
|
542
|
+
if Origami::OPTIONS[:use_openssl]
|
543
|
+
@key = key
|
544
|
+
else
|
545
|
+
@state = init(key)
|
546
|
+
end
|
547
|
+
end
|
548
|
+
|
549
|
+
#
|
550
|
+
# Encrypt/decrypt data with the aRC4 encryption algorithm
|
551
|
+
#
|
552
|
+
def cipher(data)
|
553
|
+
return "" if data.empty?
|
554
|
+
|
555
|
+
if Origami::OPTIONS[:use_openssl]
|
556
|
+
rc4 = OpenSSL::Cipher::RC4.new.encrypt
|
557
|
+
rc4.key_len = @key.length
|
558
|
+
rc4.key = @key
|
559
|
+
|
560
|
+
output = rc4.update(data) << rc4.final
|
561
|
+
else
|
562
|
+
output = ""
|
563
|
+
i, j = 0, 0
|
564
|
+
data.each_byte do |byte|
|
565
|
+
i = i.succ & 0xFF
|
566
|
+
j = (j + @state[i]) & 0xFF
|
567
|
+
|
568
|
+
@state[i], @state[j] = @state[j], @state[i]
|
569
|
+
|
570
|
+
output << (@state[@state[i] + @state[j] & 0xFF] ^ byte).chr
|
571
|
+
end
|
572
|
+
end
|
573
|
+
|
574
|
+
output
|
575
|
+
end
|
576
|
+
|
577
|
+
alias encrypt cipher
|
578
|
+
alias decrypt cipher
|
579
|
+
|
580
|
+
private
|
581
|
+
|
582
|
+
def init(key) #:nodoc:
|
583
|
+
|
584
|
+
state = (0..255).to_a
|
585
|
+
|
586
|
+
j = 0
|
587
|
+
256.times do |i|
|
588
|
+
j = ( j + state[i] + key[i % key.size].ord ) & 0xFF
|
589
|
+
state[i], state[j] = state[j], state[i]
|
590
|
+
end
|
591
|
+
|
592
|
+
state
|
593
|
+
end
|
594
|
+
|
595
|
+
end
|
596
|
+
|
597
|
+
#
|
598
|
+
# Pure Ruby implementation of the AES symmetric algorithm.
|
599
|
+
# Using mode CBC.
|
600
|
+
#
|
601
|
+
class AES
|
602
|
+
|
603
|
+
NROWS = 4
|
604
|
+
NCOLS = 4
|
605
|
+
BLOCKSIZE = NROWS * NCOLS
|
606
|
+
|
607
|
+
ROUNDS =
|
608
|
+
{
|
609
|
+
16 => 10,
|
610
|
+
24 => 12,
|
611
|
+
32 => 14
|
612
|
+
}
|
613
|
+
|
614
|
+
#
|
615
|
+
# Rijndael S-box
|
616
|
+
#
|
617
|
+
SBOX =
|
618
|
+
[
|
619
|
+
0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76,
|
620
|
+
0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0,
|
621
|
+
0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15,
|
622
|
+
0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75,
|
623
|
+
0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84,
|
624
|
+
0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf,
|
625
|
+
0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8,
|
626
|
+
0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2,
|
627
|
+
0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73,
|
628
|
+
0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb,
|
629
|
+
0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79,
|
630
|
+
0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08,
|
631
|
+
0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a,
|
632
|
+
0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e,
|
633
|
+
0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf,
|
634
|
+
0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16
|
635
|
+
]
|
636
|
+
|
637
|
+
#
|
638
|
+
# Inverse of the Rijndael S-box
|
639
|
+
#
|
640
|
+
RSBOX =
|
641
|
+
[
|
642
|
+
0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb,
|
643
|
+
0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb,
|
644
|
+
0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e,
|
645
|
+
0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25,
|
646
|
+
0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92,
|
647
|
+
0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84,
|
648
|
+
0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06,
|
649
|
+
0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b,
|
650
|
+
0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73,
|
651
|
+
0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e,
|
652
|
+
0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b,
|
653
|
+
0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4,
|
654
|
+
0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f,
|
655
|
+
0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef,
|
656
|
+
0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61,
|
657
|
+
0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d
|
658
|
+
]
|
659
|
+
|
660
|
+
RCON =
|
661
|
+
[
|
662
|
+
0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a,
|
663
|
+
0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39,
|
664
|
+
0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a,
|
665
|
+
0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8,
|
666
|
+
0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef,
|
667
|
+
0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc,
|
668
|
+
0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b,
|
669
|
+
0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3,
|
670
|
+
0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94,
|
671
|
+
0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20,
|
672
|
+
0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35,
|
673
|
+
0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f,
|
674
|
+
0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04,
|
675
|
+
0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63,
|
676
|
+
0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd,
|
677
|
+
0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb
|
678
|
+
]
|
679
|
+
|
680
|
+
attr_writer :iv
|
681
|
+
|
682
|
+
def AES.encrypt(key, iv, data)
|
683
|
+
AES.new(key, iv).encrypt(data)
|
684
|
+
end
|
685
|
+
|
686
|
+
def AES.decrypt(key, data)
|
687
|
+
AES.new(key, nil).decrypt(data)
|
688
|
+
end
|
689
|
+
|
690
|
+
def initialize(key, iv, use_padding = true)
|
691
|
+
unless key.size == 16 or key.size == 24 or key.size == 32
|
692
|
+
raise EncryptionError, "Key must have a length of 128, 192 or 256 bits."
|
693
|
+
end
|
694
|
+
|
695
|
+
if not iv.nil? and iv.size != BLOCKSIZE
|
696
|
+
raise EncryptionError, "Initialization vector must have a length of #{BLOCKSIZE} bytes."
|
697
|
+
end
|
698
|
+
|
699
|
+
@key = key
|
700
|
+
@iv = iv
|
701
|
+
@use_padding = use_padding
|
702
|
+
end
|
703
|
+
|
704
|
+
def encrypt(data)
|
705
|
+
|
706
|
+
if @iv.nil?
|
707
|
+
raise EncryptionError, "No initialization vector has been set."
|
708
|
+
end
|
709
|
+
|
710
|
+
if @use_padding
|
711
|
+
padlen = BLOCKSIZE - (data.size % BLOCKSIZE)
|
712
|
+
data << (padlen.chr * padlen)
|
713
|
+
end
|
714
|
+
|
715
|
+
if Origami::OPTIONS[:use_openssl]
|
716
|
+
aes = OpenSSL::Cipher::Cipher.new("aes-#{@key.length << 3}-cbc").encrypt
|
717
|
+
aes.iv = @iv
|
718
|
+
aes.key = @key
|
719
|
+
aes.padding = 0
|
720
|
+
|
721
|
+
@iv + aes.update(data) + aes.final
|
722
|
+
else
|
723
|
+
cipher = []
|
724
|
+
cipherblock = []
|
725
|
+
nblocks = data.size / BLOCKSIZE
|
726
|
+
|
727
|
+
first_round = true
|
728
|
+
nblocks.times do |n|
|
729
|
+
plainblock = data[n * BLOCKSIZE, BLOCKSIZE].unpack("C*")
|
730
|
+
|
731
|
+
if first_round
|
732
|
+
BLOCKSIZE.times do |i| plainblock[i] ^= @iv[i].ord end
|
733
|
+
else
|
734
|
+
BLOCKSIZE.times do |i| plainblock[i] ^= cipherblock[i] end
|
735
|
+
end
|
736
|
+
|
737
|
+
first_round = false
|
738
|
+
cipherblock = aesEncrypt(plainblock)
|
739
|
+
cipher.concat(cipherblock)
|
740
|
+
end
|
741
|
+
|
742
|
+
@iv + cipher.pack("C*")
|
743
|
+
end
|
744
|
+
end
|
745
|
+
|
746
|
+
def decrypt(data)
|
747
|
+
unless data.size % BLOCKSIZE == 0
|
748
|
+
raise EncryptionError,
|
749
|
+
"Data must be 16-bytes padded (data size = #{data.size} bytes)"
|
750
|
+
end
|
751
|
+
|
752
|
+
@iv = data.slice!(0, BLOCKSIZE)
|
753
|
+
|
754
|
+
if Origami::OPTIONS[:use_openssl]
|
755
|
+
aes = OpenSSL::Cipher::Cipher.new("aes-#{@key.length << 3}-cbc").decrypt
|
756
|
+
aes.iv = @iv
|
757
|
+
aes.key = @key
|
758
|
+
aes.padding = 0
|
759
|
+
|
760
|
+
plain = (aes.update(data) + aes.final).unpack("C*")
|
761
|
+
else
|
762
|
+
plain = []
|
763
|
+
plainblock = []
|
764
|
+
prev_cipherblock = []
|
765
|
+
nblocks = data.size / BLOCKSIZE
|
766
|
+
|
767
|
+
first_round = true
|
768
|
+
nblocks.times do |n|
|
769
|
+
cipherblock = data[n * BLOCKSIZE, BLOCKSIZE].unpack("C*")
|
770
|
+
|
771
|
+
plainblock = aesDecrypt(cipherblock)
|
772
|
+
|
773
|
+
if first_round
|
774
|
+
BLOCKSIZE.times do |i| plainblock[i] ^= @iv[i].ord end
|
775
|
+
else
|
776
|
+
BLOCKSIZE.times do |i| plainblock[i] ^= prev_cipherblock[i] end
|
777
|
+
end
|
778
|
+
|
779
|
+
first_round = false
|
780
|
+
prev_cipherblock = cipherblock
|
781
|
+
plain.concat(plainblock)
|
782
|
+
end
|
783
|
+
end
|
784
|
+
|
785
|
+
if @use_padding
|
786
|
+
padlen = plain[-1]
|
787
|
+
unless (1..16) === padlen
|
788
|
+
raise EncryptionError, "Incorrect padding length : #{padlen}"
|
789
|
+
end
|
790
|
+
|
791
|
+
padlen.times do
|
792
|
+
pad = plain.pop
|
793
|
+
raise EncryptionError,
|
794
|
+
"Incorrect padding byte : 0x#{pad.to_s 16}" if pad != padlen
|
795
|
+
end
|
796
|
+
end
|
797
|
+
|
798
|
+
plain.pack("C*")
|
799
|
+
end
|
800
|
+
|
801
|
+
private
|
802
|
+
|
803
|
+
def rol(row, n = 1) #:nodoc
|
804
|
+
n.times do row.push row.shift end ; row
|
805
|
+
end
|
806
|
+
|
807
|
+
def ror(row, n = 1) #:nodoc:
|
808
|
+
n.times do row.unshift row.pop end ; row
|
809
|
+
end
|
810
|
+
|
811
|
+
def galoisMult(a, b) #:nodoc:
|
812
|
+
p = 0
|
813
|
+
|
814
|
+
8.times do
|
815
|
+
p ^= a if b[0] == 1
|
816
|
+
highBit = a[7]
|
817
|
+
a <<= 1
|
818
|
+
a ^= 0x1b if highBit == 1
|
819
|
+
b >>= 1
|
820
|
+
end
|
821
|
+
|
822
|
+
p % 256
|
823
|
+
end
|
824
|
+
|
825
|
+
def scheduleCore(word, iter) #:nodoc:
|
826
|
+
rol(word)
|
827
|
+
word.map! do |byte| SBOX[byte] end
|
828
|
+
word[0] ^= RCON[iter]
|
829
|
+
|
830
|
+
word
|
831
|
+
end
|
832
|
+
|
833
|
+
def transpose(m) #:nodoc:
|
834
|
+
[
|
835
|
+
m[NROWS * 0, NROWS],
|
836
|
+
m[NROWS * 1, NROWS],
|
837
|
+
m[NROWS * 2, NROWS],
|
838
|
+
m[NROWS * 3, NROWS]
|
839
|
+
].transpose.flatten
|
840
|
+
end
|
841
|
+
|
842
|
+
#
|
843
|
+
# AES round methods.
|
844
|
+
#
|
845
|
+
|
846
|
+
def createRoundKey(expandedKey, round = 0) #:nodoc:
|
847
|
+
transpose(expandedKey[round * BLOCKSIZE, BLOCKSIZE])
|
848
|
+
end
|
849
|
+
|
850
|
+
def addRoundKey(roundKey) #:nodoc:
|
851
|
+
BLOCKSIZE.times do |i| @state[i] ^= roundKey[i] end
|
852
|
+
end
|
853
|
+
|
854
|
+
def subBytes #:nodoc:
|
855
|
+
BLOCKSIZE.times do |i| @state[i] = SBOX[ @state[i] ] end
|
856
|
+
end
|
857
|
+
|
858
|
+
def rsubBytes #:nodoc:
|
859
|
+
BLOCKSIZE.times do |i| @state[i] = RSBOX[ @state[i] ] end
|
860
|
+
end
|
861
|
+
|
862
|
+
def shiftRows #:nodoc:
|
863
|
+
NROWS.times do |i|
|
864
|
+
@state[i * NCOLS, NCOLS] = rol(@state[i * NCOLS, NCOLS], i)
|
865
|
+
end
|
866
|
+
end
|
867
|
+
|
868
|
+
def rshiftRows #:nodoc:
|
869
|
+
NROWS.times do |i|
|
870
|
+
@state[i * NCOLS, NCOLS] = ror(@state[i * NCOLS, NCOLS], i)
|
871
|
+
end
|
872
|
+
end
|
873
|
+
|
874
|
+
def mixColumnWithField(column, field) #:nodoc:
|
875
|
+
p = field
|
876
|
+
|
877
|
+
column[0], column[1], column[2], column[3] =
|
878
|
+
galoisMult(column[0], p[0]) ^ galoisMult(column[3], p[1]) ^ galoisMult(column[2], p[2]) ^ galoisMult(column[1], p[3]),
|
879
|
+
galoisMult(column[1], p[0]) ^ galoisMult(column[0], p[1]) ^ galoisMult(column[3], p[2]) ^ galoisMult(column[2], p[3]),
|
880
|
+
galoisMult(column[2], p[0]) ^ galoisMult(column[1], p[1]) ^ galoisMult(column[0], p[2]) ^ galoisMult(column[3], p[3]),
|
881
|
+
galoisMult(column[3], p[0]) ^ galoisMult(column[2], p[1]) ^ galoisMult(column[1], p[2]) ^ galoisMult(column[0], p[3])
|
882
|
+
end
|
883
|
+
|
884
|
+
def mixColumn(column) #:nodoc:
|
885
|
+
mixColumnWithField(column, [ 2, 1, 1, 3 ])
|
886
|
+
end
|
887
|
+
|
888
|
+
def rmixColumn(column) #:nodoc:
|
889
|
+
mixColumnWithField(column, [ 14, 9, 13, 11 ])
|
890
|
+
end
|
891
|
+
|
892
|
+
def mixColumns #:nodoc:
|
893
|
+
NCOLS.times do |c|
|
894
|
+
column = []
|
895
|
+
NROWS.times do |r| column << @state[c + r * NCOLS] end
|
896
|
+
mixColumn(column)
|
897
|
+
NROWS.times do |r| @state[c + r * NCOLS] = column[r] end
|
898
|
+
end
|
899
|
+
end
|
900
|
+
|
901
|
+
def rmixColumns #:nodoc:
|
902
|
+
NCOLS.times do |c|
|
903
|
+
column = []
|
904
|
+
NROWS.times do |r| column << @state[c + r * NCOLS] end
|
905
|
+
rmixColumn(column)
|
906
|
+
NROWS.times do |r| @state[c + r * NCOLS] = column[r] end
|
907
|
+
end
|
908
|
+
end
|
909
|
+
|
910
|
+
def expandKey(key) #:nodoc:
|
911
|
+
|
912
|
+
key = key.unpack("C*")
|
913
|
+
size = key.size
|
914
|
+
expandedSize = 16 * (ROUNDS[key.size] + 1)
|
915
|
+
rconIter = 1
|
916
|
+
expandedKey = key[0, size]
|
917
|
+
|
918
|
+
while expandedKey.size < expandedSize
|
919
|
+
temp = expandedKey[-4, 4]
|
920
|
+
|
921
|
+
if expandedKey.size % size == 0
|
922
|
+
scheduleCore(temp, rconIter)
|
923
|
+
rconIter = rconIter.succ
|
924
|
+
end
|
925
|
+
|
926
|
+
temp.map! do |b| SBOX[b] end if size == 32 and expandedKey.size % size == 16
|
927
|
+
|
928
|
+
temp.each do |b| expandedKey << (expandedKey[-size] ^ b) end
|
929
|
+
end
|
930
|
+
|
931
|
+
expandedKey
|
932
|
+
end
|
933
|
+
|
934
|
+
def aesRound(roundKey) #:nodoc:
|
935
|
+
subBytes
|
936
|
+
#puts "after subBytes: #{@state.inspect}"
|
937
|
+
shiftRows
|
938
|
+
#puts "after shiftRows: #{@state.inspect}"
|
939
|
+
mixColumns
|
940
|
+
#puts "after mixColumns: #{@state.inspect}"
|
941
|
+
addRoundKey(roundKey)
|
942
|
+
#puts "roundKey = #{roundKey.inspect}"
|
943
|
+
#puts "after addRoundKey: #{@state.inspect}"
|
944
|
+
end
|
945
|
+
|
946
|
+
def raesRound(roundKey) #:nodoc:
|
947
|
+
addRoundKey(roundKey)
|
948
|
+
rmixColumns
|
949
|
+
rshiftRows
|
950
|
+
rsubBytes
|
951
|
+
end
|
952
|
+
|
953
|
+
def aesEncrypt(block) #:nodoc:
|
954
|
+
@state = transpose(block)
|
955
|
+
expandedKey = expandKey(@key)
|
956
|
+
rounds = ROUNDS[@key.size]
|
957
|
+
|
958
|
+
aesMain(expandedKey, rounds)
|
959
|
+
end
|
960
|
+
|
961
|
+
def aesDecrypt(block) #:nodoc:
|
962
|
+
@state = transpose(block)
|
963
|
+
expandedKey = expandKey(@key)
|
964
|
+
rounds = ROUNDS[@key.size]
|
965
|
+
|
966
|
+
raesMain(expandedKey, rounds)
|
967
|
+
end
|
968
|
+
|
969
|
+
def aesMain(expandedKey, rounds) #:nodoc:
|
970
|
+
#puts "expandedKey: #{expandedKey.inspect}"
|
971
|
+
roundKey = createRoundKey(expandedKey)
|
972
|
+
addRoundKey(roundKey)
|
973
|
+
|
974
|
+
for i in 1..rounds-1
|
975
|
+
roundKey = createRoundKey(expandedKey, i)
|
976
|
+
aesRound(roundKey)
|
977
|
+
end
|
978
|
+
|
979
|
+
roundKey = createRoundKey(expandedKey, rounds)
|
980
|
+
subBytes
|
981
|
+
shiftRows
|
982
|
+
addRoundKey(roundKey)
|
983
|
+
|
984
|
+
transpose(@state)
|
985
|
+
end
|
986
|
+
|
987
|
+
def raesMain(expandedKey, rounds) #:nodoc:
|
988
|
+
|
989
|
+
roundKey = createRoundKey(expandedKey, rounds)
|
990
|
+
addRoundKey(roundKey)
|
991
|
+
rshiftRows
|
992
|
+
rsubBytes
|
993
|
+
|
994
|
+
(rounds - 1).downto(1) do |i|
|
995
|
+
roundKey = createRoundKey(expandedKey, i)
|
996
|
+
raesRound(roundKey)
|
997
|
+
end
|
998
|
+
|
999
|
+
roundKey = createRoundKey(expandedKey)
|
1000
|
+
addRoundKey(roundKey)
|
1001
|
+
|
1002
|
+
transpose(@state)
|
1003
|
+
end
|
1004
|
+
|
1005
|
+
end
|
1006
|
+
|
1007
|
+
#
|
1008
|
+
# Class representing a crypt filter Dictionary
|
1009
|
+
#
|
1010
|
+
class CryptFilterDictionary < Dictionary
|
1011
|
+
include StandardObject
|
1012
|
+
|
1013
|
+
field :Type, :Type => Name, :Default => :CryptFilter
|
1014
|
+
field :CFM, :Type => Name, :Default => :None
|
1015
|
+
field :AuthEvent, :Type => Name, :Default => :DocOpen
|
1016
|
+
field :Length, :Type => Integer
|
1017
|
+
end
|
1018
|
+
|
1019
|
+
#
|
1020
|
+
# Common class for encryption dictionaries.
|
1021
|
+
#
|
1022
|
+
class EncryptionDictionary < Dictionary
|
1023
|
+
include StandardObject
|
1024
|
+
|
1025
|
+
field :Filter, :Type => Name, :Default => :Standard, :Required => true
|
1026
|
+
field :SubFilter, :Type => Name, :Version => "1.3"
|
1027
|
+
field :V, :Type => Integer, :Default => 0
|
1028
|
+
field :Length, :Type => Integer, :Default => 40, :Version => "1.4"
|
1029
|
+
field :CF, :Type => Dictionary, :Version => "1.5"
|
1030
|
+
field :StmF, :Type => Name, :Default => :Identity, :Version => "1.5"
|
1031
|
+
field :StrF, :Type => Name, :Default => :Identity, :Version => "1.5"
|
1032
|
+
field :EFF, :Type => Name, :Version => "1.6"
|
1033
|
+
end
|
1034
|
+
|
1035
|
+
#
|
1036
|
+
# The standard security handler for PDF encryption.
|
1037
|
+
#
|
1038
|
+
module Standard
|
1039
|
+
|
1040
|
+
PADDING = "\x28\xBF\x4E\x5E\x4E\x75\x8A\x41\x64\x00\x4E\x56\xFF\xFA\x01\x08\x2E\x2E\x00\xB6\xD0\x68\x3E\x80\x2F\x0C\xA9\xFE\x64\x53\x69\x7A" #:nodoc:
|
1041
|
+
|
1042
|
+
#
|
1043
|
+
# Permission constants for encrypted documents.
|
1044
|
+
#
|
1045
|
+
module Permissions
|
1046
|
+
RESERVED = 1 << 6 | 1 << 7 | 0xFFFFF000
|
1047
|
+
PRINT = 1 << 2 | RESERVED
|
1048
|
+
MODIFY_CONTENTS = 1 << 3 | RESERVED
|
1049
|
+
COPY_CONTENTS = 1 << 4 | RESERVED
|
1050
|
+
MODIFY_ANNOTATIONS = 1 << 5 | RESERVED
|
1051
|
+
FILLIN_FORMS = 1 << 8 | RESERVED
|
1052
|
+
EXTRACT_CONTENTS = 1 << 9 | RESERVED
|
1053
|
+
ASSEMBLE_DOC = 1 << 10 | RESERVED
|
1054
|
+
HIGH_QUALITY_PRINT = 1 << 11 | RESERVED
|
1055
|
+
|
1056
|
+
ALL = PRINT | MODIFY_CONTENTS | COPY_CONTENTS | MODIFY_ANNOTATIONS | FILLIN_FORMS | EXTRACT_CONTENTS | ASSEMBLE_DOC | HIGH_QUALITY_PRINT
|
1057
|
+
end
|
1058
|
+
|
1059
|
+
#
|
1060
|
+
# Class defining a standard encryption dictionary.
|
1061
|
+
#
|
1062
|
+
class Dictionary < EncryptionDictionary
|
1063
|
+
|
1064
|
+
field :R, :Type => Number, :Required => true
|
1065
|
+
field :O, :Type => String, :Required => true
|
1066
|
+
field :U, :Type => String, :Required => true
|
1067
|
+
field :OE, :Type => String, :Version => '1.7', :ExtensionLevel => 3
|
1068
|
+
field :UE, :Type => String, :Version => '1.7', :ExtensionLevel => 3
|
1069
|
+
field :Perms, :Type => String, :Version => '1.7', :ExtensionLevel => 3
|
1070
|
+
field :P, :Type => Integer, :Default => 0, :Required => true
|
1071
|
+
field :EncryptMetadata, :Type => Boolean, :Default => true, :Version => "1.5"
|
1072
|
+
|
1073
|
+
#
|
1074
|
+
# Computes the key that will be used to encrypt/decrypt the document contents with user password.
|
1075
|
+
#
|
1076
|
+
def compute_user_encryption_key(userpassword, fileid)
|
1077
|
+
|
1078
|
+
if self.R < 5
|
1079
|
+
padded = pad_password(userpassword)
|
1080
|
+
|
1081
|
+
padded << self.O
|
1082
|
+
padded << [ self.P ].pack("i")
|
1083
|
+
|
1084
|
+
padded << fileid
|
1085
|
+
|
1086
|
+
encrypt_metadata = self.EncryptMetadata != false
|
1087
|
+
padded << "\xFF\xFF\xFF\xFF" if self.R >= 4 and not encrypt_metadata
|
1088
|
+
|
1089
|
+
key = Digest::MD5.digest(padded)
|
1090
|
+
|
1091
|
+
50.times { key = Digest::MD5.digest(key[0, self.Length / 8]) } if self.R >= 3
|
1092
|
+
|
1093
|
+
if self.R == 2
|
1094
|
+
key[0, 5]
|
1095
|
+
elsif self.R >= 3
|
1096
|
+
key[0, self.Length / 8]
|
1097
|
+
end
|
1098
|
+
else
|
1099
|
+
passwd = password_to_utf8(userpassword)
|
1100
|
+
|
1101
|
+
uks = self.U[40, 8]
|
1102
|
+
ukey = Digest::SHA256.digest(passwd + uks)
|
1103
|
+
|
1104
|
+
iv = ::Array.new(AES::BLOCKSIZE, 0).pack("C*")
|
1105
|
+
AES.new(ukey, nil, false).decrypt(iv + self.UE.value)
|
1106
|
+
end
|
1107
|
+
end
|
1108
|
+
|
1109
|
+
#
|
1110
|
+
# Computes the key that will be used to encrypt/decrypt the document contents with owner password.
|
1111
|
+
# Revision 5 only.
|
1112
|
+
#
|
1113
|
+
def compute_owner_encryption_key(ownerpassword)
|
1114
|
+
if self.R == 5
|
1115
|
+
passwd = password_to_utf8(ownerpassword)
|
1116
|
+
|
1117
|
+
oks = self.O[40, 8]
|
1118
|
+
okey = Digest::SHA256.digest(passwd + oks)
|
1119
|
+
|
1120
|
+
iv = ::Array.new(AES::BLOCKSIZE, 0).pack("C*")
|
1121
|
+
AES.new(okey, nil, false).decrypt(iv + self.OE.value)
|
1122
|
+
end
|
1123
|
+
end
|
1124
|
+
|
1125
|
+
#
|
1126
|
+
# Set up document passwords.
|
1127
|
+
#
|
1128
|
+
def set_passwords(ownerpassword, userpassword, salt = nil)
|
1129
|
+
if self.R < 5
|
1130
|
+
key = compute_owner_key(ownerpassword)
|
1131
|
+
upadded = pad_password(userpassword)
|
1132
|
+
|
1133
|
+
owner_key = ARC4.encrypt(key, upadded)
|
1134
|
+
19.times { |i| owner_key = ARC4.encrypt(xor(key,i+1), owner_key) } if self.R >= 3
|
1135
|
+
|
1136
|
+
self.O = owner_key
|
1137
|
+
self.U = compute_user_password(userpassword, salt)
|
1138
|
+
|
1139
|
+
elsif self.R == 5
|
1140
|
+
upass = password_to_utf8(userpassword)
|
1141
|
+
opass = password_to_utf8(ownerpassword)
|
1142
|
+
|
1143
|
+
uvs, uks, ovs, oks = ::Array.new(4) { ::Array.new(8) { rand(255) }.pack("C*") }
|
1144
|
+
file_key = ::Array.new(32) { rand(256) }.pack("C*")
|
1145
|
+
iv = ::Array.new(AES::BLOCKSIZE, 0).pack("C*")
|
1146
|
+
|
1147
|
+
ukey = Digest::SHA256.digest(upass + uks)
|
1148
|
+
okey = Digest::SHA256.digest(opass + oks)
|
1149
|
+
|
1150
|
+
self.UE = AES.new(ukey, iv, false).encrypt(file_key)[iv.size, 32]
|
1151
|
+
self.OE = AES.new(okey, iv, false).encrypt(file_key)[iv.size, 32]
|
1152
|
+
self.U = Digest::SHA256.digest(upass + uvs) + uvs + uks
|
1153
|
+
self.O = Digest::SHA256.digest(opass + ovs + self.U) + ovs + oks
|
1154
|
+
|
1155
|
+
perms =
|
1156
|
+
[ self.P ].pack("V") + # 0-3
|
1157
|
+
"\xff" * 4 + # 4-7
|
1158
|
+
(self.EncryptMetadata == true ? "T" : "F") + # 8
|
1159
|
+
"adb" + # 9-11
|
1160
|
+
"\x00" * 4 # 12-15
|
1161
|
+
|
1162
|
+
self.Perms = AES.new(file_key, iv, false).encrypt(perms)[iv.size, 16]
|
1163
|
+
|
1164
|
+
file_key
|
1165
|
+
end
|
1166
|
+
end
|
1167
|
+
|
1168
|
+
#
|
1169
|
+
# Checks user password.
|
1170
|
+
# For version 2,3 and 4, _salt_ is the document ID.
|
1171
|
+
# For version 5, _salt_ is the User Key Salt.
|
1172
|
+
#
|
1173
|
+
def is_user_password?(pass, salt)
|
1174
|
+
|
1175
|
+
if self.R == 2
|
1176
|
+
compute_user_password(pass, salt) == self.U
|
1177
|
+
elsif self.R == 3 or self.R == 4
|
1178
|
+
compute_user_password(pass, salt)[0, 16] == self.U[0, 16]
|
1179
|
+
elsif self.R == 5
|
1180
|
+
uvs = self.U[32, 8]
|
1181
|
+
Digest::SHA256.digest(pass + uvs) == self.U[0, 32]
|
1182
|
+
end
|
1183
|
+
end
|
1184
|
+
|
1185
|
+
#
|
1186
|
+
# Checks owner password.
|
1187
|
+
# For version 2,3 and 4, _salt_ is the document ID.
|
1188
|
+
# For version 5, _salt_ is (Owner Key Salt + U)
|
1189
|
+
#
|
1190
|
+
def is_owner_password?(pass, salt)
|
1191
|
+
|
1192
|
+
if self.R < 5
|
1193
|
+
user_password = retrieve_user_password(pass)
|
1194
|
+
is_user_password?(user_password, salt)
|
1195
|
+
elsif self.R == 5
|
1196
|
+
ovs = self.O[32, 8]
|
1197
|
+
Digest::SHA256.digest(pass + ovs + self.U) == self.O[0, 32]
|
1198
|
+
end
|
1199
|
+
end
|
1200
|
+
|
1201
|
+
#
|
1202
|
+
# Retrieve user password from owner password.
|
1203
|
+
# Cannot be used with revision 5.
|
1204
|
+
#
|
1205
|
+
def retrieve_user_password(ownerpassword)
|
1206
|
+
key = compute_owner_key(ownerpassword)
|
1207
|
+
|
1208
|
+
if self.R == 2
|
1209
|
+
ARC4.decrypt(key, self.O)
|
1210
|
+
elsif self.R == 3 or self.R == 4
|
1211
|
+
user_password = ARC4.decrypt(xor(key, 19), self.O)
|
1212
|
+
19.times { |i| user_password = ARC4.decrypt(xor(key, 18-i), user_password) }
|
1213
|
+
|
1214
|
+
user_password
|
1215
|
+
end
|
1216
|
+
end
|
1217
|
+
|
1218
|
+
private
|
1219
|
+
|
1220
|
+
#
|
1221
|
+
# Used to encrypt/decrypt the O field.
|
1222
|
+
# Rev 2,3,4: O = crypt(user_pass, owner_key).
|
1223
|
+
# Rev 5: unused.
|
1224
|
+
#
|
1225
|
+
def compute_owner_key(ownerpassword) #:nodoc:
|
1226
|
+
|
1227
|
+
opadded = pad_password(ownerpassword)
|
1228
|
+
|
1229
|
+
hash = Digest::MD5.digest(opadded)
|
1230
|
+
50.times { hash = Digest::MD5.digest(hash) } if self.R >= 3
|
1231
|
+
|
1232
|
+
if self.R == 2
|
1233
|
+
hash[0, 5]
|
1234
|
+
elsif self.R >= 3
|
1235
|
+
hash[0, self.Length / 8]
|
1236
|
+
end
|
1237
|
+
end
|
1238
|
+
|
1239
|
+
#
|
1240
|
+
# Compute the value of the U field.
|
1241
|
+
# Cannot be used with revision 5.
|
1242
|
+
#
|
1243
|
+
def compute_user_password(userpassword, salt) #:nodoc:
|
1244
|
+
|
1245
|
+
if self.R == 2
|
1246
|
+
key = compute_user_encryption_key(userpassword, salt)
|
1247
|
+
user_key = ARC4.encrypt(key, PADDING)
|
1248
|
+
elsif self.R == 3 or self.R == 4
|
1249
|
+
key = compute_user_encryption_key(userpassword, salt)
|
1250
|
+
|
1251
|
+
upadded = PADDING + salt
|
1252
|
+
hash = Digest::MD5.digest(upadded)
|
1253
|
+
|
1254
|
+
user_key = ARC4.encrypt(key, hash)
|
1255
|
+
|
1256
|
+
19.times { |i| user_key = ARC4.encrypt(xor(key,i+1), user_key) }
|
1257
|
+
|
1258
|
+
user_key.ljust(32, "\xFF")
|
1259
|
+
end
|
1260
|
+
end
|
1261
|
+
|
1262
|
+
def xor(str, byte) #:nodoc:
|
1263
|
+
str.split(//).map!{|c| (c[0].ord ^ byte).chr }.join
|
1264
|
+
end
|
1265
|
+
|
1266
|
+
def pad_password(password) #:nodoc:
|
1267
|
+
return PADDING.dup if password.empty? # Fix for Ruby 1.9 bug
|
1268
|
+
password[0,32].ljust(32, PADDING)
|
1269
|
+
end
|
1270
|
+
|
1271
|
+
def password_to_utf8(passwd) #:nodoc:
|
1272
|
+
Origami::ByteString.new(passwd).to_utf8[0, 127]
|
1273
|
+
end
|
1274
|
+
|
1275
|
+
end
|
1276
|
+
|
1277
|
+
end
|
1278
|
+
|
1279
|
+
end
|
1280
|
+
|
1281
|
+
end
|
1282
|
+
|
1283
|
+
def hexprint(str)
|
1284
|
+
hex = ""
|
1285
|
+
str.each_byte do |b|
|
1286
|
+
digit = b.to_s(16)
|
1287
|
+
digit = "0" + digit if digit.size == 1
|
1288
|
+
hex << digit
|
1289
|
+
end
|
1290
|
+
|
1291
|
+
puts hex.upcase
|
1292
|
+
end
|
1293
|
+
|