origamindee 3.0.0
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 +7 -0
- data/CHANGELOG.md +89 -0
- data/COPYING.LESSER +165 -0
- data/README.md +131 -0
- data/bin/config/pdfcop.conf.yml +236 -0
- data/bin/pdf2pdfa +87 -0
- data/bin/pdf2ruby +333 -0
- data/bin/pdfcop +476 -0
- data/bin/pdfdecompress +97 -0
- data/bin/pdfdecrypt +91 -0
- data/bin/pdfencrypt +113 -0
- data/bin/pdfexplode +223 -0
- data/bin/pdfextract +277 -0
- data/bin/pdfmetadata +143 -0
- data/bin/pdfsh +12 -0
- data/bin/shell/console.rb +128 -0
- data/bin/shell/hexdump.rb +59 -0
- data/bin/shell/irbrc +69 -0
- data/examples/README.md +34 -0
- data/examples/attachments/attachment.rb +38 -0
- data/examples/attachments/nested_document.rb +51 -0
- data/examples/encryption/encryption.rb +28 -0
- data/examples/events/events.rb +72 -0
- data/examples/flash/flash.rb +37 -0
- data/examples/flash/helloworld.swf +0 -0
- data/examples/forms/javascript.rb +54 -0
- data/examples/forms/xfa.rb +115 -0
- data/examples/javascript/hello_world.rb +22 -0
- data/examples/javascript/js_emulation.rb +54 -0
- data/examples/loop/goto.rb +32 -0
- data/examples/loop/named.rb +33 -0
- data/examples/signature/signature.rb +65 -0
- data/examples/uri/javascript.rb +56 -0
- data/examples/uri/open-uri.rb +21 -0
- data/examples/uri/submitform.rb +47 -0
- data/lib/origami/3d.rb +364 -0
- data/lib/origami/acroform.rb +321 -0
- data/lib/origami/actions.rb +318 -0
- data/lib/origami/annotations.rb +711 -0
- data/lib/origami/array.rb +242 -0
- data/lib/origami/boolean.rb +90 -0
- data/lib/origami/catalog.rb +418 -0
- data/lib/origami/collections.rb +144 -0
- data/lib/origami/compound.rb +161 -0
- data/lib/origami/destinations.rb +252 -0
- data/lib/origami/dictionary.rb +192 -0
- data/lib/origami/encryption.rb +1084 -0
- data/lib/origami/extensions/fdf.rb +347 -0
- data/lib/origami/extensions/ppklite.rb +422 -0
- data/lib/origami/filespec.rb +197 -0
- data/lib/origami/filters/ascii.rb +211 -0
- data/lib/origami/filters/ccitt/tables.rb +267 -0
- data/lib/origami/filters/ccitt.rb +357 -0
- data/lib/origami/filters/crypt.rb +38 -0
- data/lib/origami/filters/dct.rb +54 -0
- data/lib/origami/filters/flate.rb +69 -0
- data/lib/origami/filters/jbig2.rb +57 -0
- data/lib/origami/filters/jpx.rb +47 -0
- data/lib/origami/filters/lzw.rb +170 -0
- data/lib/origami/filters/predictors.rb +292 -0
- data/lib/origami/filters/runlength.rb +129 -0
- data/lib/origami/filters.rb +364 -0
- data/lib/origami/font.rb +196 -0
- data/lib/origami/functions.rb +79 -0
- data/lib/origami/graphics/colors.rb +230 -0
- data/lib/origami/graphics/instruction.rb +98 -0
- data/lib/origami/graphics/path.rb +182 -0
- data/lib/origami/graphics/patterns.rb +174 -0
- data/lib/origami/graphics/render.rb +62 -0
- data/lib/origami/graphics/state.rb +149 -0
- data/lib/origami/graphics/text.rb +225 -0
- data/lib/origami/graphics/xobject.rb +918 -0
- data/lib/origami/graphics.rb +38 -0
- data/lib/origami/header.rb +75 -0
- data/lib/origami/javascript.rb +713 -0
- data/lib/origami/linearization.rb +330 -0
- data/lib/origami/metadata.rb +172 -0
- data/lib/origami/name.rb +135 -0
- data/lib/origami/null.rb +65 -0
- data/lib/origami/numeric.rb +181 -0
- data/lib/origami/obfuscation.rb +245 -0
- data/lib/origami/object.rb +760 -0
- data/lib/origami/optionalcontent.rb +183 -0
- data/lib/origami/outline.rb +54 -0
- data/lib/origami/outputintents.rb +85 -0
- data/lib/origami/page.rb +722 -0
- data/lib/origami/parser.rb +269 -0
- data/lib/origami/parsers/fdf.rb +56 -0
- data/lib/origami/parsers/pdf/lazy.rb +176 -0
- data/lib/origami/parsers/pdf/linear.rb +122 -0
- data/lib/origami/parsers/pdf.rb +118 -0
- data/lib/origami/parsers/ppklite.rb +57 -0
- data/lib/origami/pdf.rb +1108 -0
- data/lib/origami/reference.rb +134 -0
- data/lib/origami/signature.rb +702 -0
- data/lib/origami/stream.rb +705 -0
- data/lib/origami/string.rb +444 -0
- data/lib/origami/template/patterns.rb +56 -0
- data/lib/origami/template/widgets.rb +151 -0
- data/lib/origami/trailer.rb +190 -0
- data/lib/origami/tree.rb +62 -0
- data/lib/origami/version.rb +23 -0
- data/lib/origami/webcapture.rb +100 -0
- data/lib/origami/xfa/config.rb +453 -0
- data/lib/origami/xfa/connectionset.rb +146 -0
- data/lib/origami/xfa/datasets.rb +49 -0
- data/lib/origami/xfa/localeset.rb +42 -0
- data/lib/origami/xfa/package.rb +59 -0
- data/lib/origami/xfa/pdf.rb +73 -0
- data/lib/origami/xfa/signature.rb +42 -0
- data/lib/origami/xfa/sourceset.rb +43 -0
- data/lib/origami/xfa/stylesheet.rb +44 -0
- data/lib/origami/xfa/template.rb +1691 -0
- data/lib/origami/xfa/xdc.rb +42 -0
- data/lib/origami/xfa/xfa.rb +146 -0
- data/lib/origami/xfa/xfdf.rb +43 -0
- data/lib/origami/xfa/xmpmeta.rb +43 -0
- data/lib/origami/xfa.rb +62 -0
- data/lib/origami/xreftable.rb +557 -0
- data/lib/origami.rb +47 -0
- data/test/dataset/calc.pdf +85 -0
- data/test/dataset/crypto.pdf +36 -0
- data/test/dataset/empty.pdf +49 -0
- data/test/test_actions.rb +27 -0
- data/test/test_annotations.rb +68 -0
- data/test/test_forms.rb +30 -0
- data/test/test_native_types.rb +83 -0
- data/test/test_object_tree.rb +33 -0
- data/test/test_pages.rb +60 -0
- data/test/test_pdf.rb +20 -0
- data/test/test_pdf_attachment.rb +34 -0
- data/test/test_pdf_create.rb +24 -0
- data/test/test_pdf_encrypt.rb +102 -0
- data/test/test_pdf_parse.rb +134 -0
- data/test/test_pdf_parse_lazy.rb +69 -0
- data/test/test_pdf_sign.rb +97 -0
- data/test/test_streams.rb +184 -0
- data/test/test_xrefs.rb +67 -0
- metadata +280 -0
@@ -0,0 +1,1084 @@
|
|
1
|
+
=begin
|
2
|
+
|
3
|
+
This file is part of Origami, PDF manipulation framework for Ruby
|
4
|
+
Copyright (C) 2016 Guillaume Delugré.
|
5
|
+
|
6
|
+
Origami is free software: you can redistribute it and/or modify
|
7
|
+
it under the terms of the GNU Lesser General Public License as published by
|
8
|
+
the Free Software Foundation, either version 3 of the License, or
|
9
|
+
(at your option) any later version.
|
10
|
+
|
11
|
+
Origami is distributed in the hope that it will be useful,
|
12
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
13
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
14
|
+
GNU Lesser General Public License for more details.
|
15
|
+
|
16
|
+
You should have received a copy of the GNU Lesser General Public License
|
17
|
+
along with Origami. If not, see <http://www.gnu.org/licenses/>.
|
18
|
+
|
19
|
+
=end
|
20
|
+
|
21
|
+
require 'openssl'
|
22
|
+
require 'securerandom'
|
23
|
+
require 'digest/md5'
|
24
|
+
require 'digest/sha2'
|
25
|
+
|
26
|
+
module Origami
|
27
|
+
|
28
|
+
class EncryptionError < Error #:nodoc:
|
29
|
+
end
|
30
|
+
|
31
|
+
class EncryptionInvalidPasswordError < EncryptionError #:nodoc:
|
32
|
+
end
|
33
|
+
|
34
|
+
class EncryptionNotSupportedError < EncryptionError #:nodoc:
|
35
|
+
end
|
36
|
+
|
37
|
+
class PDF
|
38
|
+
|
39
|
+
#
|
40
|
+
# Returns whether the PDF file is encrypted.
|
41
|
+
#
|
42
|
+
def encrypted?
|
43
|
+
trailer_key? :Encrypt
|
44
|
+
end
|
45
|
+
|
46
|
+
#
|
47
|
+
# Decrypts the current document.
|
48
|
+
# _passwd_:: The password to decrypt the document.
|
49
|
+
#
|
50
|
+
def decrypt(passwd = "")
|
51
|
+
raise EncryptionError, "PDF is not encrypted" unless self.encrypted?
|
52
|
+
|
53
|
+
# Turn the encryption dictionary into a standard encryption dictionary.
|
54
|
+
handler = trailer_key(:Encrypt)
|
55
|
+
handler = self.cast_object(handler.reference, Encryption::Standard::Dictionary)
|
56
|
+
|
57
|
+
unless handler.Filter == :Standard
|
58
|
+
raise EncryptionNotSupportedError, "Unknown security handler : '#{handler.Filter}'"
|
59
|
+
end
|
60
|
+
|
61
|
+
doc_id = trailer_key(:ID)
|
62
|
+
unless doc_id.is_a?(Array)
|
63
|
+
raise EncryptionError, "Document ID was not found or is invalid" unless handler.V.to_i == 5
|
64
|
+
else
|
65
|
+
doc_id = doc_id.first
|
66
|
+
end
|
67
|
+
|
68
|
+
encryption_key = handler.derive_encryption_key(passwd, doc_id)
|
69
|
+
|
70
|
+
self.extend(Encryption::EncryptedDocument)
|
71
|
+
self.encryption_handler = handler
|
72
|
+
self.encryption_key = encryption_key
|
73
|
+
|
74
|
+
decrypt_objects
|
75
|
+
|
76
|
+
self
|
77
|
+
end
|
78
|
+
|
79
|
+
#
|
80
|
+
# Encrypts the current document with the provided passwords.
|
81
|
+
# The document will be encrypted at writing-on-disk time.
|
82
|
+
# _userpasswd_:: The user password.
|
83
|
+
# _ownerpasswd_:: The owner password.
|
84
|
+
# _options_:: A set of options to configure encryption.
|
85
|
+
#
|
86
|
+
def encrypt(options = {})
|
87
|
+
raise EncryptionError, "PDF is already encrypted" if self.encrypted?
|
88
|
+
|
89
|
+
#
|
90
|
+
# Default encryption options.
|
91
|
+
#
|
92
|
+
params = {
|
93
|
+
:user_passwd => '',
|
94
|
+
:owner_passwd => '',
|
95
|
+
:cipher => 'aes', # :RC4 or :AES
|
96
|
+
:key_size => 256, # Key size in bits
|
97
|
+
:hardened => false, # Use newer password validation (since Reader X)
|
98
|
+
:encrypt_metadata => true, # Metadata shall be encrypted?
|
99
|
+
:permissions => Encryption::Standard::Permissions::ALL # Document permissions
|
100
|
+
}.update(options)
|
101
|
+
|
102
|
+
# Get the cryptographic parameters.
|
103
|
+
version, revision = crypto_revision_from_options(params)
|
104
|
+
|
105
|
+
# Create the security handler.
|
106
|
+
handler, encryption_key = create_security_handler(version, revision, params)
|
107
|
+
|
108
|
+
# Turn this document into an EncryptedDocument instance.
|
109
|
+
self.extend(Encryption::EncryptedDocument)
|
110
|
+
self.encryption_handler = handler
|
111
|
+
self.encryption_key = encryption_key
|
112
|
+
|
113
|
+
self
|
114
|
+
end
|
115
|
+
|
116
|
+
private
|
117
|
+
|
118
|
+
#
|
119
|
+
# Installs the standard security dictionary, marking the document as being encrypted.
|
120
|
+
# Returns the handler and the encryption key used for protecting contents.
|
121
|
+
#
|
122
|
+
def create_security_handler(version, revision, params)
|
123
|
+
|
124
|
+
# Ensure the document has an ID.
|
125
|
+
doc_id = (trailer_key(:ID) || generate_id).first
|
126
|
+
|
127
|
+
# Create the standard encryption dictionary.
|
128
|
+
handler = Encryption::Standard::Dictionary.new
|
129
|
+
handler.Filter = :Standard
|
130
|
+
handler.V = version
|
131
|
+
handler.R = revision
|
132
|
+
handler.Length = params[:key_size]
|
133
|
+
handler.P = -1 # params[:Permissions]
|
134
|
+
|
135
|
+
# Build the crypt filter dictionary.
|
136
|
+
if revision >= 4
|
137
|
+
handler.EncryptMetadata = params[:encrypt_metadata]
|
138
|
+
handler.CF = Dictionary.new
|
139
|
+
crypt_filter = Encryption::CryptFilterDictionary.new
|
140
|
+
crypt_filter.AuthEvent = :DocOpen
|
141
|
+
|
142
|
+
if revision == 4
|
143
|
+
crypt_filter.CFM = :AESV2
|
144
|
+
else
|
145
|
+
crypt_filter.CFM = :AESV3
|
146
|
+
end
|
147
|
+
|
148
|
+
crypt_filter.Length = params[:key_size] >> 3
|
149
|
+
|
150
|
+
handler.CF[:StdCF] = crypt_filter
|
151
|
+
handler.StmF = handler.StrF = :StdCF
|
152
|
+
end
|
153
|
+
|
154
|
+
user_passwd, owner_passwd = params[:user_passwd], params[:owner_passwd]
|
155
|
+
|
156
|
+
# Setup keys.
|
157
|
+
handler.set_passwords(owner_passwd, user_passwd, doc_id)
|
158
|
+
encryption_key = handler.compute_user_encryption_key(user_passwd, doc_id)
|
159
|
+
|
160
|
+
# Install the encryption dictionary to the document.
|
161
|
+
self.trailer.Encrypt = self << handler
|
162
|
+
|
163
|
+
[ handler, encryption_key ]
|
164
|
+
end
|
165
|
+
|
166
|
+
#
|
167
|
+
# Converts the parameters passed to PDF#encrypt.
|
168
|
+
# Returns [ version, revision, crypt_filters ]
|
169
|
+
#
|
170
|
+
def crypto_revision_from_options(params)
|
171
|
+
case params[:cipher].upcase
|
172
|
+
when 'RC4'
|
173
|
+
crypto_revision_from_rc4_key(params[:key_size])
|
174
|
+
when 'AES'
|
175
|
+
crypto_revision_from_aes_key(params[:key_size], params[:hardened])
|
176
|
+
else
|
177
|
+
raise EncryptionNotSupportedError, "Cipher not supported : #{params[:cipher]}"
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
#
|
182
|
+
# Compute the required standard security handler version based on the RC4 key size.
|
183
|
+
# _key_size_:: Key size in bits.
|
184
|
+
# Returns [ version, revision ].
|
185
|
+
#
|
186
|
+
def crypto_revision_from_rc4_key(key_size)
|
187
|
+
raise EncryptionError, "Invalid RC4 key length" unless key_size.between?(40, 128) and key_size % 8 == 0
|
188
|
+
|
189
|
+
if key_size > 40
|
190
|
+
version = 2
|
191
|
+
revision = 3
|
192
|
+
else
|
193
|
+
version = 1
|
194
|
+
revision = 2
|
195
|
+
end
|
196
|
+
|
197
|
+
[ version, revision ]
|
198
|
+
end
|
199
|
+
|
200
|
+
#
|
201
|
+
# Compute the required standard security handler version based on the AES key size.
|
202
|
+
# _key_size_:: Key size in bits.
|
203
|
+
# _hardened_:: Use the extension level 8 hardened derivation algorithm.
|
204
|
+
# Returns [ version, revision ].
|
205
|
+
#
|
206
|
+
def crypto_revision_from_aes_key(key_size, hardened)
|
207
|
+
if key_size == 128
|
208
|
+
version = revision = 4
|
209
|
+
elsif key_size == 256
|
210
|
+
version = 5
|
211
|
+
if hardened
|
212
|
+
revision = 6
|
213
|
+
else
|
214
|
+
revision = 5
|
215
|
+
end
|
216
|
+
else
|
217
|
+
raise EncryptionError, "Invalid AES key length (Only 128 and 256 bits keys are supported)"
|
218
|
+
end
|
219
|
+
|
220
|
+
[ version, revision ]
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
#
|
225
|
+
# Module to provide support for encrypting and decrypting PDF documents.
|
226
|
+
#
|
227
|
+
module Encryption
|
228
|
+
|
229
|
+
#
|
230
|
+
# Generates _n_ random bytes from a fast PRNG.
|
231
|
+
#
|
232
|
+
def self.rand_bytes(n)
|
233
|
+
Random.new.bytes(n)
|
234
|
+
end
|
235
|
+
|
236
|
+
#
|
237
|
+
# Generates _n_ random bytes from a crypto PRNG.
|
238
|
+
#
|
239
|
+
def self.strong_rand_bytes(n)
|
240
|
+
SecureRandom.random_bytes(n)
|
241
|
+
end
|
242
|
+
|
243
|
+
module EncryptedDocument
|
244
|
+
attr_accessor :encryption_key
|
245
|
+
attr_accessor :encryption_handler
|
246
|
+
|
247
|
+
# Get the encryption cipher from the crypt filter name.
|
248
|
+
def encryption_cipher(name)
|
249
|
+
@encryption_handler.encryption_cipher(name)
|
250
|
+
end
|
251
|
+
|
252
|
+
# Get the default string encryption cipher.
|
253
|
+
def string_encryption_cipher
|
254
|
+
@encryption_handler.string_encryption_cipher
|
255
|
+
end
|
256
|
+
|
257
|
+
# Get the default stream encryption cipher.
|
258
|
+
def stream_encryption_cipher
|
259
|
+
@encryption_handler.stream_encryption_cipher
|
260
|
+
end
|
261
|
+
|
262
|
+
private
|
263
|
+
|
264
|
+
#
|
265
|
+
# For each object subject to encryption, convert it to an EncryptedObject and decrypt it if necessary.
|
266
|
+
#
|
267
|
+
def decrypt_objects
|
268
|
+
each_encryptable_object do |object|
|
269
|
+
case object
|
270
|
+
when String
|
271
|
+
object.extend(EncryptedString) unless object.is_a?(EncryptedString)
|
272
|
+
object.decrypt!
|
273
|
+
|
274
|
+
when Stream
|
275
|
+
object.extend(EncryptedStream) unless object.is_a?(EncryptedStream)
|
276
|
+
end
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
280
|
+
#
|
281
|
+
# For each object subject to encryption, convert it to an EncryptedObject and mark it as not encrypted yet.
|
282
|
+
#
|
283
|
+
def encrypt_objects
|
284
|
+
each_encryptable_object do |object|
|
285
|
+
case object
|
286
|
+
when String
|
287
|
+
unless object.is_a?(EncryptedString)
|
288
|
+
object.extend(EncryptedString)
|
289
|
+
object.decrypted = true
|
290
|
+
end
|
291
|
+
|
292
|
+
when Stream
|
293
|
+
unless object.is_a?(EncryptedStream)
|
294
|
+
object.extend(EncryptedStream)
|
295
|
+
object.decrypted = true
|
296
|
+
end
|
297
|
+
end
|
298
|
+
end
|
299
|
+
end
|
300
|
+
|
301
|
+
#
|
302
|
+
# Iterates over each encryptable objects in the document.
|
303
|
+
#
|
304
|
+
def each_encryptable_object(&b)
|
305
|
+
|
306
|
+
# Metadata may not be encrypted depending on the security handler configuration.
|
307
|
+
encrypt_metadata = (@encryption_handler.EncryptMetadata != false)
|
308
|
+
metadata = self.Catalog.Metadata
|
309
|
+
|
310
|
+
self.each_object(recursive: true)
|
311
|
+
.lazy
|
312
|
+
.select { |object|
|
313
|
+
case object
|
314
|
+
when Stream
|
315
|
+
not object.is_a?(XRefStream) or (encrypt_metadata and object.equal?(metadata))
|
316
|
+
when String
|
317
|
+
not object.parent.equal?(@encryption_handler)
|
318
|
+
end
|
319
|
+
}
|
320
|
+
.each(&b)
|
321
|
+
end
|
322
|
+
|
323
|
+
def physicalize(options = {})
|
324
|
+
encrypt_objects
|
325
|
+
|
326
|
+
super
|
327
|
+
|
328
|
+
# remove encrypt dictionary if requested
|
329
|
+
if options[:decrypt]
|
330
|
+
delete_object(self.trailer[:Encrypt])
|
331
|
+
self.trailer[:Encrypt] = nil
|
332
|
+
end
|
333
|
+
|
334
|
+
self
|
335
|
+
end
|
336
|
+
|
337
|
+
def build_object(object, revision, options)
|
338
|
+
if object.is_a?(EncryptedObject) and options[:decrypt]
|
339
|
+
object.pre_build
|
340
|
+
object.decrypt!
|
341
|
+
object.decrypted = false # makes it believe no encryption pass is required
|
342
|
+
object.post_build
|
343
|
+
|
344
|
+
return
|
345
|
+
end
|
346
|
+
|
347
|
+
super
|
348
|
+
end
|
349
|
+
end
|
350
|
+
|
351
|
+
#
|
352
|
+
# Module for encrypted PDF objects.
|
353
|
+
#
|
354
|
+
module EncryptedObject #:nodoc
|
355
|
+
attr_accessor :decrypted
|
356
|
+
|
357
|
+
def post_build
|
358
|
+
encrypt!
|
359
|
+
|
360
|
+
super
|
361
|
+
end
|
362
|
+
|
363
|
+
private
|
364
|
+
|
365
|
+
def compute_object_key(cipher)
|
366
|
+
doc = self.document
|
367
|
+
raise EncryptionError, "Document is not encrypted" unless doc.is_a?(EncryptedDocument)
|
368
|
+
|
369
|
+
encryption_key = doc.encryption_key
|
370
|
+
|
371
|
+
if doc.encryption_handler.V < 5
|
372
|
+
parent = self.indirect_parent
|
373
|
+
no, gen = parent.no, parent.generation
|
374
|
+
k = encryption_key + [no].pack("I")[0..2] + [gen].pack("I")[0..1]
|
375
|
+
|
376
|
+
key_len = [k.length, 16].min
|
377
|
+
k << "sAlT" if cipher == Encryption::AES
|
378
|
+
|
379
|
+
Digest::MD5.digest(k)[0, key_len]
|
380
|
+
else
|
381
|
+
encryption_key
|
382
|
+
end
|
383
|
+
end
|
384
|
+
end
|
385
|
+
|
386
|
+
#
|
387
|
+
# Module for encrypted String.
|
388
|
+
#
|
389
|
+
module EncryptedString
|
390
|
+
include EncryptedObject
|
391
|
+
|
392
|
+
def self.extended(obj)
|
393
|
+
obj.decrypted = false
|
394
|
+
end
|
395
|
+
|
396
|
+
def encrypt!
|
397
|
+
return self unless @decrypted
|
398
|
+
|
399
|
+
cipher = self.document.string_encryption_cipher
|
400
|
+
raise EncryptionError, "Cannot find string encryption filter" if cipher.nil?
|
401
|
+
|
402
|
+
key = compute_object_key(cipher)
|
403
|
+
|
404
|
+
encrypted_data =
|
405
|
+
if cipher == RC4 or cipher == Identity
|
406
|
+
cipher.encrypt(key, self.value)
|
407
|
+
else
|
408
|
+
iv = Encryption.rand_bytes(AES::BLOCKSIZE)
|
409
|
+
cipher.encrypt(key, iv, self.value)
|
410
|
+
end
|
411
|
+
|
412
|
+
@decrypted = false
|
413
|
+
|
414
|
+
self.replace(encrypted_data)
|
415
|
+
self.freeze
|
416
|
+
|
417
|
+
self
|
418
|
+
end
|
419
|
+
|
420
|
+
def decrypt!
|
421
|
+
return self if @decrypted
|
422
|
+
|
423
|
+
cipher = self.document.string_encryption_cipher
|
424
|
+
raise EncryptionError, "Cannot find string encryption filter" if cipher.nil?
|
425
|
+
|
426
|
+
key = compute_object_key(cipher)
|
427
|
+
|
428
|
+
self.replace(cipher.decrypt(key, self.to_str))
|
429
|
+
@decrypted = true
|
430
|
+
|
431
|
+
self
|
432
|
+
end
|
433
|
+
end
|
434
|
+
|
435
|
+
#
|
436
|
+
# Module for encrypted Stream.
|
437
|
+
#
|
438
|
+
module EncryptedStream
|
439
|
+
include EncryptedObject
|
440
|
+
|
441
|
+
def self.extended(obj)
|
442
|
+
obj.decrypted = false
|
443
|
+
end
|
444
|
+
|
445
|
+
def encrypt!
|
446
|
+
return self unless @decrypted
|
447
|
+
|
448
|
+
encode!
|
449
|
+
|
450
|
+
cipher = get_encryption_cipher
|
451
|
+
key = compute_object_key(cipher)
|
452
|
+
|
453
|
+
@encoded_data =
|
454
|
+
if cipher == RC4 or cipher == Identity
|
455
|
+
cipher.encrypt(key, self.encoded_data)
|
456
|
+
else
|
457
|
+
iv = Encryption.rand_bytes(AES::BLOCKSIZE)
|
458
|
+
cipher.encrypt(key, iv, @encoded_data)
|
459
|
+
end
|
460
|
+
|
461
|
+
@decrypted = false
|
462
|
+
|
463
|
+
@encoded_data.freeze
|
464
|
+
self.freeze
|
465
|
+
|
466
|
+
self
|
467
|
+
end
|
468
|
+
|
469
|
+
def decrypt!
|
470
|
+
return self if @decrypted
|
471
|
+
|
472
|
+
cipher = get_encryption_cipher
|
473
|
+
key = compute_object_key(cipher)
|
474
|
+
|
475
|
+
self.encoded_data = cipher.decrypt(key, @encoded_data)
|
476
|
+
@decrypted = true
|
477
|
+
|
478
|
+
self
|
479
|
+
end
|
480
|
+
|
481
|
+
private
|
482
|
+
|
483
|
+
#
|
484
|
+
# Get the stream encryption cipher.
|
485
|
+
# The cipher used may depend on the presence of a Crypt filter.
|
486
|
+
#
|
487
|
+
def get_encryption_cipher
|
488
|
+
if self.filters.first == :Crypt
|
489
|
+
params = decode_params.first
|
490
|
+
|
491
|
+
if params.is_a?(Dictionary) and params.Name.is_a?(Name)
|
492
|
+
crypt_filter = params.Name.value
|
493
|
+
else
|
494
|
+
crypt_filter = :Identity
|
495
|
+
end
|
496
|
+
|
497
|
+
cipher = self.document.encryption_cipher(crypt_filter)
|
498
|
+
else
|
499
|
+
cipher = self.document.stream_encryption_cipher
|
500
|
+
end
|
501
|
+
|
502
|
+
raise EncryptionError, "Cannot find stream encryption filter" if cipher.nil?
|
503
|
+
|
504
|
+
cipher
|
505
|
+
end
|
506
|
+
end
|
507
|
+
|
508
|
+
#
|
509
|
+
# Identity transformation.
|
510
|
+
#
|
511
|
+
module Identity
|
512
|
+
def Identity.encrypt(_key, data)
|
513
|
+
data
|
514
|
+
end
|
515
|
+
|
516
|
+
def Identity.decrypt(_key, data)
|
517
|
+
data
|
518
|
+
end
|
519
|
+
end
|
520
|
+
|
521
|
+
#
|
522
|
+
# Class wrapper for the RC4 algorithm.
|
523
|
+
#
|
524
|
+
class RC4
|
525
|
+
|
526
|
+
#
|
527
|
+
# Encrypts data using the given key
|
528
|
+
#
|
529
|
+
def RC4.encrypt(key, data)
|
530
|
+
RC4.new(key).encrypt(data)
|
531
|
+
end
|
532
|
+
|
533
|
+
#
|
534
|
+
# Decrypts data using the given key
|
535
|
+
#
|
536
|
+
def RC4.decrypt(key, data)
|
537
|
+
RC4.new(key).decrypt(data)
|
538
|
+
end
|
539
|
+
|
540
|
+
#
|
541
|
+
# Creates and initialises a new RC4 generator using given key
|
542
|
+
#
|
543
|
+
def initialize(key)
|
544
|
+
@key = key
|
545
|
+
end
|
546
|
+
|
547
|
+
#
|
548
|
+
# Encrypt/decrypt data with the RC4 encryption algorithm
|
549
|
+
#
|
550
|
+
def cipher(data)
|
551
|
+
return '' if data.empty?
|
552
|
+
|
553
|
+
rc4 = OpenSSL::Cipher::RC4.new.encrypt
|
554
|
+
rc4.key_len = @key.length
|
555
|
+
rc4.key = @key
|
556
|
+
|
557
|
+
rc4.update(data) + rc4.final
|
558
|
+
end
|
559
|
+
|
560
|
+
alias encrypt cipher
|
561
|
+
alias decrypt cipher
|
562
|
+
end
|
563
|
+
|
564
|
+
#
|
565
|
+
# Class wrapper for AES mode CBC.
|
566
|
+
#
|
567
|
+
class AES
|
568
|
+
BLOCKSIZE = 16
|
569
|
+
|
570
|
+
attr_writer :iv
|
571
|
+
|
572
|
+
def AES.encrypt(key, iv, data)
|
573
|
+
AES.new(key, iv).encrypt(data)
|
574
|
+
end
|
575
|
+
|
576
|
+
def AES.decrypt(key, data)
|
577
|
+
AES.new(key, nil).decrypt(data)
|
578
|
+
end
|
579
|
+
|
580
|
+
def initialize(key, iv, use_padding = true)
|
581
|
+
unless [16, 24, 32].include?(key.size)
|
582
|
+
raise EncryptionError, "Key must have a length of 128, 192 or 256 bits."
|
583
|
+
end
|
584
|
+
|
585
|
+
if not iv.nil? and iv.size != BLOCKSIZE
|
586
|
+
raise EncryptionError, "Initialization vector must have a length of #{BLOCKSIZE} bytes."
|
587
|
+
end
|
588
|
+
|
589
|
+
@key = key
|
590
|
+
@iv = iv
|
591
|
+
@use_padding = use_padding
|
592
|
+
end
|
593
|
+
|
594
|
+
def encrypt(data)
|
595
|
+
if @iv.nil?
|
596
|
+
raise EncryptionError, "No initialization vector has been set."
|
597
|
+
end
|
598
|
+
|
599
|
+
if @use_padding
|
600
|
+
padlen = BLOCKSIZE - (data.size % BLOCKSIZE)
|
601
|
+
data << (padlen.chr * padlen)
|
602
|
+
end
|
603
|
+
|
604
|
+
aes = OpenSSL::Cipher.new("aes-#{@key.length << 3}-cbc").encrypt
|
605
|
+
aes.iv = @iv
|
606
|
+
aes.key = @key
|
607
|
+
aes.padding = 0
|
608
|
+
|
609
|
+
@iv + aes.update(data) + aes.final
|
610
|
+
end
|
611
|
+
|
612
|
+
def decrypt(data)
|
613
|
+
unless data.size % BLOCKSIZE == 0
|
614
|
+
raise EncryptionError, "Data must be 16-bytes padded (data size = #{data.size} bytes)"
|
615
|
+
end
|
616
|
+
|
617
|
+
@iv = data.slice!(0, BLOCKSIZE)
|
618
|
+
|
619
|
+
aes = OpenSSL::Cipher.new("aes-#{@key.length << 3}-cbc").decrypt
|
620
|
+
aes.iv = @iv
|
621
|
+
aes.key = @key
|
622
|
+
aes.padding = 0
|
623
|
+
|
624
|
+
plain = (aes.update(data) + aes.final).unpack("C*")
|
625
|
+
|
626
|
+
if @use_padding
|
627
|
+
padlen = plain[-1]
|
628
|
+
unless padlen.between?(1, 16)
|
629
|
+
raise EncryptionError, "Incorrect padding length : #{padlen}"
|
630
|
+
end
|
631
|
+
|
632
|
+
padlen.times do
|
633
|
+
pad = plain.pop
|
634
|
+
raise EncryptionError, "Incorrect padding byte : 0x#{pad.to_s 16}" if pad != padlen
|
635
|
+
end
|
636
|
+
end
|
637
|
+
|
638
|
+
plain.pack("C*")
|
639
|
+
end
|
640
|
+
end
|
641
|
+
|
642
|
+
#
|
643
|
+
# Class representing a crypt filter Dictionary
|
644
|
+
#
|
645
|
+
class CryptFilterDictionary < Dictionary
|
646
|
+
include StandardObject
|
647
|
+
|
648
|
+
field :Type, :Type => Name, :Default => :CryptFilter
|
649
|
+
field :CFM, :Type => Name, :Default => :None
|
650
|
+
field :AuthEvent, :Type => Name, :Default => :DocOpen
|
651
|
+
field :Length, :Type => Integer
|
652
|
+
end
|
653
|
+
|
654
|
+
#
|
655
|
+
# Common class for encryption dictionaries.
|
656
|
+
#
|
657
|
+
class EncryptionDictionary < Dictionary
|
658
|
+
include StandardObject
|
659
|
+
|
660
|
+
field :Filter, :Type => Name, :Default => :Standard, :Required => true
|
661
|
+
field :SubFilter, :Type => Name, :Version => "1.3"
|
662
|
+
field :V, :Type => Integer, :Default => 0
|
663
|
+
field :Length, :Type => Integer, :Default => 40, :Version => "1.4"
|
664
|
+
field :CF, :Type => Dictionary, :Version => "1.5"
|
665
|
+
field :StmF, :Type => Name, :Default => :Identity, :Version => "1.5"
|
666
|
+
field :StrF, :Type => Name, :Default => :Identity, :Version => "1.5"
|
667
|
+
field :EFF, :Type => Name, :Version => "1.6"
|
668
|
+
|
669
|
+
#
|
670
|
+
# Returns the default string encryption cipher.
|
671
|
+
#
|
672
|
+
def string_encryption_cipher
|
673
|
+
encryption_cipher(self.StrF || :Identity)
|
674
|
+
end
|
675
|
+
|
676
|
+
#
|
677
|
+
# Returns the default stream encryption cipher.
|
678
|
+
#
|
679
|
+
def stream_encryption_cipher
|
680
|
+
encryption_cipher(self.StmF || :Identity)
|
681
|
+
end
|
682
|
+
|
683
|
+
#
|
684
|
+
# Returns the encryption cipher corresponding to a crypt filter name.
|
685
|
+
#
|
686
|
+
def encryption_cipher(name)
|
687
|
+
case self.V.to_i
|
688
|
+
when 1, 2
|
689
|
+
Encryption::RC4
|
690
|
+
when 4, 5
|
691
|
+
return Encryption::Identity if name == :Identity
|
692
|
+
|
693
|
+
select_cipher_by_name(name)
|
694
|
+
else
|
695
|
+
raise EncryptionNotSupportedError, "Unsupported encryption version: #{handler.V}"
|
696
|
+
end
|
697
|
+
end
|
698
|
+
|
699
|
+
private
|
700
|
+
|
701
|
+
#
|
702
|
+
# Returns the cipher associated with a crypt filter name.
|
703
|
+
#
|
704
|
+
def select_cipher_by_name(name)
|
705
|
+
raise EncryptionError, "Broken CF entry" unless self.CF.is_a?(Dictionary)
|
706
|
+
|
707
|
+
self.CF.select { |key, dict| key == name and dict.is_a?(Dictionary) }
|
708
|
+
.map { |_, dict| cipher_from_crypt_filter_method(dict[:CFM] || :None) }
|
709
|
+
.first
|
710
|
+
end
|
711
|
+
|
712
|
+
#
|
713
|
+
# Converts a crypt filter method identifier to its cipher class.
|
714
|
+
#
|
715
|
+
def cipher_from_crypt_filter_method(name)
|
716
|
+
case name.to_sym
|
717
|
+
when :None then Encryption::Identity
|
718
|
+
when :V2 then Encryption::RC4
|
719
|
+
when :AESV2 then Encryption::AES
|
720
|
+
when :AESV3
|
721
|
+
raise EncryptionNotSupportedError, "AESV3 requires a version 5 handler" if self.V.to_i != 5
|
722
|
+
Encryption::AES
|
723
|
+
else
|
724
|
+
raise EncryptionNotSupportedError, "Unsupported crypt filter method: #{name}"
|
725
|
+
end
|
726
|
+
end
|
727
|
+
end
|
728
|
+
|
729
|
+
#
|
730
|
+
# The standard security handler for PDF encryption.
|
731
|
+
#
|
732
|
+
module Standard
|
733
|
+
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".b #:nodoc:
|
734
|
+
|
735
|
+
#
|
736
|
+
# Permission constants for encrypted documents.
|
737
|
+
#
|
738
|
+
module Permissions
|
739
|
+
RESERVED = 1 << 6 | 1 << 7 | 0xFFFFF000
|
740
|
+
PRINT = 1 << 2 | RESERVED
|
741
|
+
MODIFY_CONTENTS = 1 << 3 | RESERVED
|
742
|
+
COPY_CONTENTS = 1 << 4 | RESERVED
|
743
|
+
MODIFY_ANNOTATIONS = 1 << 5 | RESERVED
|
744
|
+
FILLIN_FORMS = 1 << 8 | RESERVED
|
745
|
+
EXTRACT_CONTENTS = 1 << 9 | RESERVED
|
746
|
+
ASSEMBLE_DOC = 1 << 10 | RESERVED
|
747
|
+
HIGH_QUALITY_PRINT = 1 << 11 | RESERVED
|
748
|
+
|
749
|
+
ALL = PRINT | MODIFY_CONTENTS | COPY_CONTENTS |
|
750
|
+
MODIFY_ANNOTATIONS | FILLIN_FORMS | EXTRACT_CONTENTS |
|
751
|
+
ASSEMBLE_DOC | HIGH_QUALITY_PRINT
|
752
|
+
end
|
753
|
+
|
754
|
+
#
|
755
|
+
# Class defining a standard encryption dictionary.
|
756
|
+
#
|
757
|
+
class Dictionary < EncryptionDictionary
|
758
|
+
|
759
|
+
field :R, :Type => Number, :Required => true
|
760
|
+
field :O, :Type => String, :Required => true
|
761
|
+
field :U, :Type => String, :Required => true
|
762
|
+
field :OE, :Type => String, :Version => '1.7', :ExtensionLevel => 3
|
763
|
+
field :UE, :Type => String, :Version => '1.7', :ExtensionLevel => 3
|
764
|
+
field :Perms, :Type => String, :Version => '1.7', :ExtensionLevel => 3
|
765
|
+
field :P, :Type => Integer, :Default => 0, :Required => true
|
766
|
+
field :EncryptMetadata, :Type => Boolean, :Default => true, :Version => "1.5"
|
767
|
+
|
768
|
+
def version_required #:nodoc:
|
769
|
+
if self.R > 5
|
770
|
+
[ '1.7', 8 ]
|
771
|
+
else
|
772
|
+
super
|
773
|
+
end
|
774
|
+
end
|
775
|
+
|
776
|
+
#
|
777
|
+
# Checks the given password and derives the document encryption key.
|
778
|
+
# Raises EncryptionInvalidPasswordError on invalid password.
|
779
|
+
#
|
780
|
+
def derive_encryption_key(passwd, doc_id)
|
781
|
+
if is_user_password?(passwd, doc_id)
|
782
|
+
compute_user_encryption_key(passwd, doc_id)
|
783
|
+
elsif is_owner_password?(passwd, doc_id)
|
784
|
+
if self.V.to_i < 5
|
785
|
+
user_passwd = retrieve_user_password(passwd)
|
786
|
+
compute_user_encryption_key(user_passwd, doc_id)
|
787
|
+
else
|
788
|
+
compute_owner_encryption_key(passwd)
|
789
|
+
end
|
790
|
+
else
|
791
|
+
raise EncryptionInvalidPasswordError
|
792
|
+
end
|
793
|
+
end
|
794
|
+
|
795
|
+
#
|
796
|
+
# Computes the key that will be used to encrypt/decrypt the document contents with user password.
|
797
|
+
# Called at all revisions.
|
798
|
+
#
|
799
|
+
def compute_user_encryption_key(user_password, file_id)
|
800
|
+
return compute_legacy_user_encryption_key(user_password, file_id) if self.R < 5
|
801
|
+
|
802
|
+
passwd = password_to_utf8(user_password)
|
803
|
+
|
804
|
+
uks = self.U[40, 8]
|
805
|
+
|
806
|
+
if self.R == 5
|
807
|
+
ukey = Digest::SHA256.digest(passwd + uks)
|
808
|
+
else
|
809
|
+
ukey = compute_hardened_hash(passwd, uks)
|
810
|
+
end
|
811
|
+
|
812
|
+
iv = ::Array.new(AES::BLOCKSIZE, 0).pack("C*")
|
813
|
+
AES.new(ukey, nil, false).decrypt(iv + self.UE.value)
|
814
|
+
end
|
815
|
+
|
816
|
+
#
|
817
|
+
# Computes the key that will be used to encrypt/decrypt the document contents.
|
818
|
+
# Only for Revision 4 and less.
|
819
|
+
#
|
820
|
+
def compute_legacy_user_encryption_key(user_password, file_id)
|
821
|
+
padded = pad_password(user_password)
|
822
|
+
padded.force_encoding('binary')
|
823
|
+
|
824
|
+
padded << self.O
|
825
|
+
padded << [ self.P ].pack("i")
|
826
|
+
|
827
|
+
padded << file_id
|
828
|
+
|
829
|
+
encrypt_metadata = self.EncryptMetadata != false
|
830
|
+
padded << [ -1 ].pack("i") if self.R >= 4 and not encrypt_metadata
|
831
|
+
|
832
|
+
key = Digest::MD5.digest(padded)
|
833
|
+
|
834
|
+
50.times { key = Digest::MD5.digest(key[0, self.Length / 8]) } if self.R >= 3
|
835
|
+
|
836
|
+
truncate_key(key)
|
837
|
+
end
|
838
|
+
|
839
|
+
#
|
840
|
+
# Computes the key that will be used to encrypt/decrypt the document contents with owner password.
|
841
|
+
# Revision 5 and above.
|
842
|
+
#
|
843
|
+
def compute_owner_encryption_key(owner_password)
|
844
|
+
return if self.R < 5
|
845
|
+
|
846
|
+
passwd = password_to_utf8(owner_password)
|
847
|
+
oks = self.O[40, 8]
|
848
|
+
|
849
|
+
if self.R == 5
|
850
|
+
okey = Digest::SHA256.digest(passwd + oks + self.U)
|
851
|
+
else
|
852
|
+
okey = compute_hardened_hash(passwd, oks, self.U)
|
853
|
+
end
|
854
|
+
|
855
|
+
iv = ::Array.new(AES::BLOCKSIZE, 0).pack("C*")
|
856
|
+
AES.new(okey, nil, false).decrypt(iv + self.OE.value)
|
857
|
+
end
|
858
|
+
|
859
|
+
#
|
860
|
+
# Set up document passwords.
|
861
|
+
#
|
862
|
+
def set_passwords(owner_password, user_password, salt = nil)
|
863
|
+
return set_legacy_passwords(owner_password, user_password, salt) if self.R < 5
|
864
|
+
|
865
|
+
upass = password_to_utf8(user_password)
|
866
|
+
opass = password_to_utf8(owner_password)
|
867
|
+
|
868
|
+
uvs, uks, ovs, oks = ::Array.new(4) { Encryption.rand_bytes(8) }
|
869
|
+
file_key = Encryption.strong_rand_bytes(32)
|
870
|
+
iv = ::Array.new(AES::BLOCKSIZE, 0).pack("C*")
|
871
|
+
|
872
|
+
if self.R == 5
|
873
|
+
self.U = Digest::SHA256.digest(upass + uvs) + uvs + uks
|
874
|
+
self.O = Digest::SHA256.digest(opass + ovs + self.U) + ovs + oks
|
875
|
+
ukey = Digest::SHA256.digest(upass + uks)
|
876
|
+
okey = Digest::SHA256.digest(opass + oks + self.U)
|
877
|
+
else
|
878
|
+
self.U = compute_hardened_hash(upass, uvs) + uvs + uks
|
879
|
+
self.O = compute_hardened_hash(opass, ovs, self.U) + ovs + oks
|
880
|
+
ukey = compute_hardened_hash(upass, uks)
|
881
|
+
okey = compute_hardened_hash(opass, oks, self.U)
|
882
|
+
end
|
883
|
+
|
884
|
+
self.UE = AES.new(ukey, iv, false).encrypt(file_key)[iv.size, 32]
|
885
|
+
self.OE = AES.new(okey, iv, false).encrypt(file_key)[iv.size, 32]
|
886
|
+
|
887
|
+
perms =
|
888
|
+
[ self.P ].pack("V") + # 0-3
|
889
|
+
[ -1 ].pack("V") + # 4-7
|
890
|
+
(self.EncryptMetadata == true ? "T" : "F") + # 8
|
891
|
+
"adb" + # 9-11
|
892
|
+
[ 0 ].pack("V") # 12-15
|
893
|
+
|
894
|
+
self.Perms = AES.new(file_key, iv, false).encrypt(perms)[iv.size, 16]
|
895
|
+
|
896
|
+
file_key
|
897
|
+
end
|
898
|
+
|
899
|
+
#
|
900
|
+
# Set up document passwords.
|
901
|
+
# Only for Revision 4 and less.
|
902
|
+
#
|
903
|
+
def set_legacy_passwords(owner_password, user_password, salt)
|
904
|
+
owner_key = compute_owner_key(owner_password)
|
905
|
+
upadded = pad_password(user_password)
|
906
|
+
|
907
|
+
owner_key_hash = RC4.encrypt(owner_key, upadded)
|
908
|
+
19.times { |i| owner_key_hash = RC4.encrypt(xor(owner_key, i + 1), owner_key_hash) } if self.R >= 3
|
909
|
+
|
910
|
+
self.O = owner_key_hash
|
911
|
+
self.U = compute_user_password_hash(user_password, salt)
|
912
|
+
end
|
913
|
+
|
914
|
+
#
|
915
|
+
# Checks user password.
|
916
|
+
# For version 2, 3 and 4, _salt_ is the document ID.
|
917
|
+
# For version 5 and 6, _salt_ is the User Key Salt.
|
918
|
+
#
|
919
|
+
def is_user_password?(pass, salt)
|
920
|
+
|
921
|
+
if self.R == 2
|
922
|
+
compute_user_password_hash(pass, salt) == self.U
|
923
|
+
elsif self.R == 3 or self.R == 4
|
924
|
+
compute_user_password_hash(pass, salt)[0, 16] == self.U[0, 16]
|
925
|
+
elsif self.R == 5
|
926
|
+
uvs = self.U[32, 8]
|
927
|
+
Digest::SHA256.digest(password_to_utf8(pass) + uvs) == self.U[0, 32]
|
928
|
+
elsif self.R == 6
|
929
|
+
uvs = self.U[32, 8]
|
930
|
+
compute_hardened_hash(password_to_utf8(pass), uvs) == self.U[0, 32]
|
931
|
+
end
|
932
|
+
end
|
933
|
+
|
934
|
+
#
|
935
|
+
# Checks owner password.
|
936
|
+
# For version 2,3 and 4, _salt_ is the document ID.
|
937
|
+
# For version 5, _salt_ is (Owner Key Salt + U)
|
938
|
+
#
|
939
|
+
def is_owner_password?(pass, salt)
|
940
|
+
|
941
|
+
if self.R < 5
|
942
|
+
user_password = retrieve_user_password(pass)
|
943
|
+
is_user_password?(user_password, salt)
|
944
|
+
elsif self.R == 5
|
945
|
+
ovs = self.O[32, 8]
|
946
|
+
Digest::SHA256.digest(password_to_utf8(pass) + ovs + self.U) == self.O[0, 32]
|
947
|
+
elsif self.R == 6
|
948
|
+
ovs = self.O[32, 8]
|
949
|
+
compute_hardened_hash(password_to_utf8(pass), ovs, self.U[0,48]) == self.O[0, 32]
|
950
|
+
end
|
951
|
+
end
|
952
|
+
|
953
|
+
#
|
954
|
+
# Retrieve user password from owner password.
|
955
|
+
# Cannot be used with revision 5.
|
956
|
+
#
|
957
|
+
def retrieve_user_password(owner_password)
|
958
|
+
|
959
|
+
key = compute_owner_key(owner_password)
|
960
|
+
|
961
|
+
if self.R == 2
|
962
|
+
RC4.decrypt(key, self.O)
|
963
|
+
elsif self.R == 3 or self.R == 4
|
964
|
+
user_password = RC4.decrypt(xor(key, 19), self.O)
|
965
|
+
19.times { |i| user_password = RC4.decrypt(xor(key, 18-i), user_password) }
|
966
|
+
|
967
|
+
user_password
|
968
|
+
end
|
969
|
+
end
|
970
|
+
|
971
|
+
private
|
972
|
+
|
973
|
+
#
|
974
|
+
# Used to encrypt/decrypt the O field.
|
975
|
+
# Rev 2,3,4: O = crypt(user_pass, owner_key).
|
976
|
+
# Rev 5: unused.
|
977
|
+
#
|
978
|
+
def compute_owner_key(owner_password) #:nodoc:
|
979
|
+
|
980
|
+
opadded = pad_password(owner_password)
|
981
|
+
|
982
|
+
owner_key = Digest::MD5.digest(opadded)
|
983
|
+
50.times { owner_key = Digest::MD5.digest(owner_key) } if self.R >= 3
|
984
|
+
|
985
|
+
truncate_key(owner_key)
|
986
|
+
end
|
987
|
+
|
988
|
+
#
|
989
|
+
# Compute the value of the U field.
|
990
|
+
# Cannot be used with revision 5.
|
991
|
+
#
|
992
|
+
def compute_user_password_hash(user_password, salt) #:nodoc:
|
993
|
+
|
994
|
+
if self.R == 2
|
995
|
+
key = compute_user_encryption_key(user_password, salt)
|
996
|
+
user_key = RC4.encrypt(key, PADDING)
|
997
|
+
elsif self.R == 3 or self.R == 4
|
998
|
+
key = compute_user_encryption_key(user_password, salt)
|
999
|
+
|
1000
|
+
upadded = PADDING + salt
|
1001
|
+
hash = Digest::MD5.digest(upadded)
|
1002
|
+
|
1003
|
+
user_key = RC4.encrypt(key, hash)
|
1004
|
+
|
1005
|
+
19.times { |i| user_key = RC4.encrypt(xor(key,i+1), user_key) }
|
1006
|
+
|
1007
|
+
user_key.ljust(32, 0xFF.chr)
|
1008
|
+
end
|
1009
|
+
end
|
1010
|
+
|
1011
|
+
#
|
1012
|
+
# Computes hardened hash used in revision 6 (extension level 8).
|
1013
|
+
#
|
1014
|
+
def compute_hardened_hash(password, salt, vector = '')
|
1015
|
+
block_size = 32
|
1016
|
+
input = Digest::SHA256.digest(password + salt + vector) + "\x00" * 32
|
1017
|
+
key = input[0, 16]
|
1018
|
+
iv = input[16, 16]
|
1019
|
+
digest, aes, h, x = nil, nil, nil, nil
|
1020
|
+
|
1021
|
+
i = 0
|
1022
|
+
while i < 64 or i < x[-1].ord + 32
|
1023
|
+
|
1024
|
+
block = input[0, block_size]
|
1025
|
+
|
1026
|
+
aes = OpenSSL::Cipher.new("aes-128-cbc").encrypt
|
1027
|
+
aes.iv = iv
|
1028
|
+
aes.key = key
|
1029
|
+
aes.padding = 0
|
1030
|
+
|
1031
|
+
64.times do |j|
|
1032
|
+
x = ''
|
1033
|
+
x += aes.update(password) unless password.empty?
|
1034
|
+
x += aes.update(block)
|
1035
|
+
x += aes.update(vector) unless vector.empty?
|
1036
|
+
|
1037
|
+
if j == 0
|
1038
|
+
block_size = 32 + (x.unpack("C16").inject(0) {|a,b| a+b} % 3) * 16
|
1039
|
+
digest = Digest::SHA2.new(block_size << 3)
|
1040
|
+
end
|
1041
|
+
|
1042
|
+
digest.update(x)
|
1043
|
+
end
|
1044
|
+
|
1045
|
+
h = digest.digest
|
1046
|
+
key = h[0, 16]
|
1047
|
+
input[0, block_size] = h[0, block_size]
|
1048
|
+
iv = h[16, 16]
|
1049
|
+
|
1050
|
+
i = i + 1
|
1051
|
+
end
|
1052
|
+
|
1053
|
+
h[0, 32]
|
1054
|
+
end
|
1055
|
+
|
1056
|
+
#
|
1057
|
+
# Some revision handlers require different key sizes.
|
1058
|
+
# Revision 2 uses 40-bit keys.
|
1059
|
+
# Revisions 3 and higher rely on the Length field for the key size.
|
1060
|
+
#
|
1061
|
+
def truncate_key(key)
|
1062
|
+
if self.R == 2
|
1063
|
+
key[0, 5]
|
1064
|
+
elsif self.R >= 3
|
1065
|
+
key[0, self.Length / 8]
|
1066
|
+
end
|
1067
|
+
end
|
1068
|
+
|
1069
|
+
def xor(str, byte) #:nodoc:
|
1070
|
+
str.bytes.map!{|b| b ^ byte }.pack("C*")
|
1071
|
+
end
|
1072
|
+
|
1073
|
+
def pad_password(password) #:nodoc:
|
1074
|
+
password[0, 32].ljust(32, PADDING)
|
1075
|
+
end
|
1076
|
+
|
1077
|
+
def password_to_utf8(passwd) #:nodoc:
|
1078
|
+
LiteralString.new(passwd).to_utf8[0, 127]
|
1079
|
+
end
|
1080
|
+
end
|
1081
|
+
end
|
1082
|
+
end
|
1083
|
+
|
1084
|
+
end
|