lib/origami/encryption.rb in origami-1.2.5 vs lib/origami/encryption.rb in origami-1.2.6

- old
+ new

@@ -21,10 +21,16 @@ You should have received a copy of the GNU Lesser General Public License along with Origami. If not, see <http://www.gnu.org/licenses/>. =end +begin + require 'openssl' if Origami::OPTIONS[:use_openssl] +rescue LoadError + Origami::OPTIONS[:use_openssl] = false +end + require 'digest/md5' require 'digest/sha2' module Origami @@ -161,24 +167,22 @@ obj.equal?(encrypt_dict[:UE]) or obj.equal?(encrypt_dict[:OE]) or obj.equal?(encrypt_dict[:Perms]) or (obj.parent.is_a?(Signature::DigitalSignature) and obj.equal?(obj.parent[:Contents])) - obj.extend(Encryption::EncryptedString) + obj.extend(Encryption::EncryptedString) unless obj.is_a?(Encryption::EncryptedString) obj.encryption_handler = handler obj.encryption_key = encryption_key obj.algorithm = str_algo - obj.decrypted = false obj.decrypt! when Stream next if obj.is_a?(XRefStream) or (not encrypt_metadata and obj.equal?(metadata)) - obj.extend(Encryption::EncryptedStream) + obj.extend(Encryption::EncryptedStream) unless obj.is_a?(Encryption::EncryptedStream) obj.encryption_handler = handler obj.encryption_key = encryption_key obj.algorithm = stm_algo - obj.decrypted = false end end end self @@ -291,10 +295,30 @@ # # Module to provide support for encrypting and decrypting PDF documents. # module Encryption + # + # Generates _n_ random bytes from a fast PRNG. + # + def self.rand_bytes(n) + ::Array.new(n) { rand(256) }.pack("C*") + end + + # + # Generates _n_ random bytes from a crypto PRNG. + # + def self.strong_rand_bytes(n) + if Origami::OPTIONS[:use_openssl] + OpenSSL::Random.random_bytes(n) + elsif RUBY_VERSION >= '1.9' + Random.new.bytes(n) + else + self.rand_bytes(n) + end + end + module EncryptedDocument attr_writer :encryption_key attr_writer :encryption_dict attr_writer :stm_algo @@ -440,11 +464,11 @@ encrypted_data = if @algorithm == ARC4 or @algorithm == Identity @algorithm.encrypt(key, self.value) else - iv = ::Array.new(AES::BLOCKSIZE) { rand(256) }.pack('C*') + iv = Encryption.rand_bytes(AES::BLOCKSIZE) @algorithm.encrypt(key, iv, self.value) end @decrypted = false @@ -480,11 +504,11 @@ @rawdata = if @algorithm == ARC4 or @algorithm == Identity @algorithm.encrypt(key, self.rawdata) else - iv = ::Array.new(AES::BLOCKSIZE) { rand(256) }.pack('C*') + iv = Encryption.rand_bytes(AES::BLOCKSIZE) @algorithm.encrypt(key, iv, @rawdata) end @decrypted = false @@ -1041,10 +1065,11 @@ # The standard security handler for PDF encryption. # module Standard 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: + PADDING.force_encoding('binary') if RUBY_VERSION > '1.8' # # Permission constants for encrypted documents. # module Permissions @@ -1088,18 +1113,19 @@ # def compute_user_encryption_key(userpassword, fileid) if self.R < 5 padded = pad_password(userpassword) + padded.force_encoding('binary') if RUBY_VERSION > '1.8' padded << self.O padded << [ self.P ].pack("i") padded << fileid encrypt_metadata = self.EncryptMetadata != false - padded << "\xFF\xFF\xFF\xFF" if self.R >= 4 and not encrypt_metadata + padded << [ -1 ].pack("i") if self.R >= 4 and not encrypt_metadata key = Digest::MD5.digest(padded) 50.times { key = Digest::MD5.digest(key[0, self.Length / 8]) } if self.R >= 3 @@ -1133,13 +1159,13 @@ passwd = password_to_utf8(ownerpassword) oks = self.O[40, 8] if self.R == 5 - okey = Digest::SHA256.digest(passwd + oks) + okey = Digest::SHA256.digest(passwd + oks + self.U) else - okey = compute_hardened_hash(passwd, oks) + okey = compute_hardened_hash(passwd, oks, self.U) end iv = ::Array.new(AES::BLOCKSIZE, 0).pack("C*") AES.new(okey, nil, false).decrypt(iv + self.OE.value) end @@ -1161,39 +1187,35 @@ else upass = password_to_utf8(userpassword) opass = password_to_utf8(ownerpassword) - uvs, uks, ovs, oks = ::Array.new(4) { ::Array.new(8) { rand(255) }.pack("C*") } - file_key = ::Array.new(32) { rand(256) }.pack("C*") + uvs, uks, ovs, oks = ::Array.new(4) { Encryption.rand_bytes(8) } + file_key = Encryption.strong_rand_bytes(32) iv = ::Array.new(AES::BLOCKSIZE, 0).pack("C*") if self.R == 5 + self.U = Digest::SHA256.digest(upass + uvs) + uvs + uks + self.O = Digest::SHA256.digest(opass + ovs + self.U) + ovs + oks ukey = Digest::SHA256.digest(upass + uks) - okey = Digest::SHA256.digest(opass + oks) + okey = Digest::SHA256.digest(opass + oks + self.U) else + self.U = compute_hardened_hash(upass, uvs) + uvs + uks + self.O = compute_hardened_hash(opass, ovs, self.U) + ovs + oks ukey = compute_hardened_hash(upass, uks) - okey = compute_hardened_hash(upass, oks) + okey = compute_hardened_hash(opass, oks, self.U) end self.UE = AES.new(ukey, iv, false).encrypt(file_key)[iv.size, 32] self.OE = AES.new(okey, iv, false).encrypt(file_key)[iv.size, 32] - - if self.R == 5 - self.U = Digest::SHA256.digest(upass + uvs) + uvs + uks - self.O = Digest::SHA256.digest(opass + ovs + self.U) + ovs + oks - else - self.U = compute_hardened_hash(upass, uvs) + uvs + uks - self.O = compute_hardened_hash(opass, ovs, self.U) + ovs + oks - end perms = [ self.P ].pack("V") + # 0-3 - "\xff" * 4 + # 4-7 + [ -1 ].pack("V") + # 4-7 (self.EncryptMetadata == true ? "T" : "F") + # 8 "adb" + # 9-11 - "\x00" * 4 # 12-15 + [ 0 ].pack("V") # 12-15 self.Perms = AES.new(file_key, iv, false).encrypt(perms)[iv.size, 16] file_key end @@ -1292,10 +1314,10 @@ user_key = ARC4.encrypt(key, hash) 19.times { |i| user_key = ARC4.encrypt(xor(key,i+1), user_key) } - user_key.ljust(32, "\xFF") + user_key.ljust(32, 0xFF.chr) end end # # Computes hardened hash used in revision 6 (extension level 8).