lib/combine_pdf/decrypt.rb in combine_pdf-0.2.34 vs lib/combine_pdf/decrypt.rb in combine_pdf-0.2.35
- old
+ new
@@ -29,12 +29,11 @@
@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
- @encryption_iv = nil
+
change_references_to_actual_values @encryption_dictionary
end
# call this to start the decryption.
def decrypt
@@ -43,24 +42,28 @@
case actual_object(@encryption_dictionary[:V])
when 1, 2
# raise_encrypted_error
_perform_decrypt_proc_ @objects, 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 actual_object(@encryption_dictionary[:CF]).is_a?(Hash)
- # do nothing if there is no data to decrypt except embeded files...?
- return true unless (actual_object(@encryption_dictionary[:CF]).values.select { |v| !v[:AuthEvent] || v[:AuthEvent] == :DocOpen }).empty?
+ # support trivial case for now
+ # - same filter for streams (Stmf) and strings(Strf)
+ # - AND :CFM == :V2 (use algorithm 1)
+ raise_encrypted_error unless (@encryption_dictionary[:StmF] == @encryption_dictionary[:StrF])
- # attempt to decrypt all strings?
- # attempt to decrypy all streams
- # attempt to decrypt all embeded files?
-
- else
- raise_encrypted_error
+ cfilter = actual_object(@encryption_dictionary[:CF])[@encryption_dictionary[:StrF]]
+ raise_encrypted_error unless cfilter
+ raise_encrypted_error unless (cfilter[:AuthEvent] == :DocOpen)
+ if (cfilter[:CFM] == :V2)
+ _perform_decrypt_proc_ @objects, method(:decrypt_RC4)
+ elsif (cfilter[:CFM] == :AESV2)
+ _perform_decrypt_proc_ @objects, method(:decrypt_AES)
+ else
+ raise_encrypted_error
+ end
end
# rebuild stream lengths?
@objects
rescue => e
puts e
@@ -83,15 +86,13 @@
# (the value of the ID entry in the document’s trailer dictionary
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 actual_object(@encryption_dictionary[:R]) >= 4
- key << if actual_object(@encryption_dictionary)[:EncryptMetadata] == false
- "\xFF\xFF\xFF\xFF"
- else # default is true and nil != false
- "\x00\x00\x00\x00"
- end
+ if actual_object(@encryption_dictionary)[:EncryptMetadata] == false
+ key << "\xFF\xFF\xFF\xFF".force_encoding(Encoding::ASCII_8BIT)
+ end
end
# 5) pass everything as a MD5 hash
key = Digest::MD5.digest(key)
# 5(a) h) (Security handlers of revision 3 or greater) Do the following 50 times:
# Take the output from the previous MD5 hash and
@@ -130,26 +131,28 @@
rc4 = ::RC4.new(Digest::MD5.digest(object_key)[(0...key_length)])
rc4.decrypt(encrypted)
end
def decrypt_AES(encrypted, encrypted_id, encrypted_generation, _encrypted_filter)
- ## extract encryption_iv if it wasn't extracted yet
- unless @encryption_iv
- @encryption_iv = encrypted[0..15].to_i
- # raise "Tryed decrypting using AES and couldn't extract iv" if @encryption_iv == 0
- @encryption_iv = 0.chr * 16
- # encrypted = encrypted[16..-1]
- end
## start decryption using padding strings
object_key = @key.dup
- (0..2).each { |e| object_key << (encrypted_id >> e * 8 & 0xFF) }
- (0..1).each { |e| object_key << (encrypted_generation >> e * 8 & 0xFF) }
- object_key << 'sAlT'
+ object_key << [encrypted_id].pack('i')[0..2]
+ object_key << [encrypted_generation].pack('i')[0..1]
+ object_key << 'sAlT'.force_encoding(Encoding::ASCII_8BIT)
key_length = object_key.length < 16 ? object_key.length : 16
- cipher = OpenSSL::Cipher::Cipher.new("aes-#{object_key.length << 3}-cbc").decrypt
- cipher.padding = 0
- (cipher.update(encrypted) + cipher.final).unpack('C*')
+
+ begin
+ cipher = OpenSSL::Cipher.new("aes-#{key_length << 3}-cbc")
+ cipher.decrypt
+ cipher.key = Digest::MD5.digest(object_key)[(0...key_length)]
+ cipher.iv = encrypted[0..15]
+ cipher.padding = 0
+ cipher.update(encrypted[16..-1]) + cipher.final
+ rescue StandardError => e
+ # puts e.class.name
+ encrypted
+ end
end
protected
def _perform_decrypt_proc_(object, decrypt_proc, encrypted_id = nil, encrypted_generation = nil, encrypted_filter = nil)
@@ -157,17 +160,17 @@
object.map! { |item| _perform_decrypt_proc_(item, decrypt_proc, encrypted_id, encrypted_generation, encrypted_filter) }
elsif object.is_a?(Hash)
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]
+ if object[:raw_stream_content] && !object[:raw_stream_content].empty?
stream_length = actual_object(object[:Length])
actual_length = object[:raw_stream_content].bytesize
# p stream_length
# p actual_length
# p object[:Length]
# p object
- warn "Stream registeded length was #{object[:Length]} and the actual length was #{actual_length}." if actual_length < stream_length
+ warn "Stream registered length was #{object[:Length]} 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
object.each { |k, v| object[k] = _perform_decrypt_proc_(v, decrypt_proc, encrypted_id, encrypted_generation, encrypted_filter) if k != :raw_stream_content && (v.is_a?(Hash) || v.is_a?(Array) || v.is_a?(String)) } # assumes no decrypting is never performed on keys
elsif object.is_a?(String)