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).