lib/combine_pdf/decrypt.rb in combine_pdf-0.2.4 vs lib/combine_pdf/decrypt.rb in combine_pdf-0.2.5
- old
+ new
@@ -15,21 +15,22 @@
# @private
# @!visibility private
# This is an internal class. you don't need it.
class PDFDecrypt
+ include CombinePDF::Renderer
# @!visibility private
# make a new Decrypt object. requires:
# objects:: an array containing the encrypted objects.
# root_dictionary:: the root PDF dictionary, containing the Encrypt dictionary.
def initialize objects=[], root_dictionary = {}
@objects = objects
- @encryption_dictionary = root_dictionary[:Encrypt]
+ @encryption_dictionary = actual_object(root_dictionary[:Encrypt])
raise "Cannot decrypt an encrypted file without an encryption dictionary!" unless @encryption_dictionary
- @root_dictionary = root_dictionary
+ @root_dictionary = actual_object(root_dictionary)
@padding_key = [ 0x28, 0xBF, 0x4E, 0x5E, 0x4E, 0x75, 0x8A, 0x41,
0x64, 0x00, 0x4E, 0x56, 0xFF, 0xFA, 0x01, 0x08,
0x2E, 0x2E, 0x00, 0xB6, 0xD0, 0x68, 0x3E, 0x80,
0x2F, 0x0C, 0xA9, 0xFE, 0x64, 0x53, 0x69, 0x7A ]
@key_crypt_first_iv_store = nil
@@ -39,51 +40,53 @@
# call this to start the decryption.
def decrypt
raise_encrypted_error @encryption_dictionary unless @encryption_dictionary[:Filter] == :Standard
@key = set_general_key
- case @encryption_dictionary[:V]
+ case actual_object(@encryption_dictionary[:V])
when 1,2
# raise_encrypted_error
_perform_decrypt_proc_ @objects, self.method(:decrypt_RC4)
when 4
# raise unsupported error for now
raise_encrypted_error
# make sure CF is a Hash (as required by the PDF standard for this type of encryption).
- raise_encrypted_error unless @encryption_dictionary[:CF].is_a?(Hash)
+ raise_encrypted_error unless actual_object(@encryption_dictionary[:CF]).is_a?(Hash)
# do nothing if there is no data to decrypt except embeded files...?
- return true unless (@encryption_dictionary[:CF].values.select { |v| !v[:AuthEvent] || v[:AuthEvent] == :DocOpen } ).empty?
+ return true unless (actual_object(@encryption_dictionary[:CF]).values.select { |v| !v[:AuthEvent] || v[:AuthEvent] == :DocOpen } ).empty?
# attempt to decrypt all strings?
# attempt to decrypy all streams
# attempt to decrypt all embeded files?
else
raise_encrypted_error
end
#rebuild stream lengths?
@objects
+ rescue => e
+ raise_encrypted_error
end
protected
def set_general_key(password = "")
# 1) make sure the initial key is 32 byte long (if no password, uses padding).
key = (password.bytes[0..32].to_a + @padding_key)[0..31].to_a.pack('C*').force_encoding(Encoding::ASCII_8BIT)
# 2) add the value of the encryption dictionary’s O entry
- key << @encryption_dictionary[:O].to_s
+ key << actual_object(@encryption_dictionary[:O]).to_s
# 3) Convert the integer value of the P entry to a 32-bit unsigned binary number
# and pass these bytes low-order byte first
- key << [@encryption_dictionary[:P]].pack('i')
+ key << [actual_object(@encryption_dictionary[:P])].pack('i')
# 4) Pass the first element of the file’s file identifier array
# (the value of the ID entry in the document’s trailer dictionary
- key << @root_dictionary[:ID][0]
+ key << actual_object(@root_dictionary[:ID])[0]
# # 4(a) (Security handlers of revision 4 or greater)
# # if document metadata is not being encrypted, add 4 bytes with the value 0xFFFFFFFF.
- if @encryption_dictionary[:R] >= 4
- unless @encryption_dictionary[:EncryptMetadata] == false #default is true and nil != false
+ if actual_object(@encryption_dictionary[:R]) >= 4
+ unless actual_object(@encryption_dictionary)[:EncryptMetadata] == false #default is true and nil != false
key << "\x00\x00\x00\x00"
else
key << "\xFF\xFF\xFF\xFF"
end
end
@@ -92,21 +95,21 @@
# 5(a) h) (Security handlers of revision 3 or greater) Do the following 50 times:
# Take the output from the previous MD5 hash and
# pass the first n bytes of the output as input into a new MD5 hash,
# where n is the number of bytes of the encryption key as defined by the value of
# the encryption dictionary’s Length entry.
- if @encryption_dictionary[:R] >= 3
+ if actual_object(@encryption_dictionary[:R]) >= 3
50.times do|i|
- key = Digest::MD5.digest(key[0...@encryption_dictionary[:Length]])
+ key = Digest::MD5.digest(key[0...actual_object(@encryption_dictionary[:Length])])
end
end
# 6) Set the encryption key to the first n bytes of the output from the final MD5 hash,
# where n shall always be 5 for security handlers of revision 2 but,
# for security handlers of revision 3 or greater,
# shall depend on the value of the encryption dictionary’s Length entry.
- if @encryption_dictionary[:R] >= 3
- @key = key[0..(@encryption_dictionary[:Length]/8)]
+ if actual_object(@encryption_dictionary[:R]) >= 3
+ @key = key[0..(actual_object(@encryption_dictionary[:Length])/8)]
else
@key = key[0..4]
end
@key
end
@@ -148,17 +151,14 @@
def _perform_decrypt_proc_ (object, decrypt_proc, encrypted_id = nil, encrypted_generation = nil, encrypted_filter = nil)
case
when object.is_a?(Array)
object.map! { |item| _perform_decrypt_proc_(item, decrypt_proc, encrypted_id, encrypted_generation, encrypted_filter) }
when object.is_a?(Hash)
- encrypted_id ||= object[:indirect_reference_id]
- encrypted_generation ||= object[:indirect_generation_number]
- encrypted_filter ||= object[:Filter]
+ encrypted_id ||= actual_object(object[:indirect_reference_id])
+ encrypted_generation ||= actual_object(object[:indirect_generation_number])
+ encrypted_filter ||= actual_object(object[:Filter])
if object[:raw_stream_content]
- stream_length = object[:Length]
- if stream_length.is_a?(Hash) && stream_length[:is_reference_only]
- stream_length = get_refernced_object(stream_length)[:indirect_without_dictionary]
- end
+ stream_length = actual_object(object[:Length])
actual_length = object[:raw_stream_content].length
warn "Stream registeded length was #{object[:Length].to_s} and the actual length was #{actual_length}." if actual_length < stream_length
length = [ stream_length, actual_length].min
object[:raw_stream_content] = decrypt_proc.call( (object[:raw_stream_content][0...length]), encrypted_id, encrypted_generation, encrypted_filter)
end