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)