lib/origami/encryption.rb in origami-2.0.0 vs lib/origami/encryption.rb in origami-2.0.1
- old
+ new
@@ -16,16 +16,11 @@
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 'openssl'
require 'securerandom'
require 'digest/md5'
require 'digest/sha2'
module Origami
@@ -53,15 +48,16 @@
# _passwd_:: The password to decrypt the document.
#
def decrypt(passwd = "")
raise EncryptionError, "PDF is not encrypted" unless self.encrypted?
- encrypt_dict = trailer_key(:Encrypt)
- handler = Encryption::Standard::Dictionary.new(encrypt_dict.dup)
+ # Turn the encryption dictionary into a standard encryption dictionary.
+ handler = trailer_key(:Encrypt)
+ handler = self.cast_object(handler.reference, Encryption::Standard::Dictionary)
unless handler.Filter == :Standard
- raise EncryptionNotSupportedError, "Unknown security handler : '#{handler.Filter.to_s}'"
+ raise EncryptionNotSupportedError, "Unknown security handler : '#{handler.Filter}'"
end
crypt_filters = {
Identity: Encryption::Identity
}
@@ -122,52 +118,18 @@
end
else
raise EncryptionInvalidPasswordError
end
- encrypt_metadata = (handler.EncryptMetadata != false)
-
self.extend(Encryption::EncryptedDocument)
self.encryption_handler = handler
self.crypt_filters = crypt_filters
self.encryption_key = encryption_key
self.stm_filter, self.str_filter = stream_filter, string_filter
- #
- # Should be fixed to exclude only the active XRefStream
- #
- metadata = self.Catalog.Metadata
+ decrypt_objects
- self.indirect_objects.each do |indobj|
- encrypted_objects = []
- case indobj
- when String,Stream then encrypted_objects << indobj
- when Dictionary,Array then encrypted_objects |= indobj.strings_cache
- end
-
- encrypted_objects.each do |obj|
- case obj
- when String
- next if obj.equal?(encrypt_dict[:U]) or
- obj.equal?(encrypt_dict[:O]) or
- 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) unless obj.is_a?(Encryption::EncryptedString)
- obj.decrypt!
-
- when Stream
- next if obj.is_a?(XRefStream) or (not encrypt_metadata and obj.equal?(metadata))
-
- obj.extend(Encryption::EncryptedStream) unless obj.is_a?(Encryption::EncryptedStream)
- end
- end
- end
-
self
end
#
# Encrypts the current document with the provided passwords.
@@ -184,103 +146,150 @@
#
params =
{
:user_passwd => '',
:owner_passwd => '',
- :cipher => 'rc4', # :RC4 or :AES
+ :cipher => 'aes', # :RC4 or :AES
:key_size => 128, # Key size in bits
:hardened => false, # Use newer password validation (since Reader X)
:encrypt_metadata => true, # Metadata shall be encrypted?
:permissions => Encryption::Standard::Permissions::ALL # Document permissions
}.update(options)
- userpasswd, ownerpasswd = params[:user_passwd], params[:owner_passwd]
+ # Get the cryptographic parameters.
+ version, revision, crypt_filters = crypto_revision_from_options(params)
- case params[:cipher].upcase
- when 'RC4'
- algorithm = Encryption::RC4
- if (40..128) === params[:key_size] and params[:key_size] % 8 == 0
- if params[:key_size] > 40
- version = 2
- revision = 3
- else
- version = 1
- revision = 2
- end
- else
- raise EncryptionError, "Invalid RC4 key length"
- end
+ # Create the security handler.
+ handler, encryption_key = create_security_handler(version, revision, params)
- crypt_filters = Hash.new(algorithm)
- string_filter = stream_filter = nil
+ # Turn this document into an EncryptedDocument instance.
+ self.extend(Encryption::EncryptedDocument)
+ self.encryption_handler = handler
+ self.encryption_key = encryption_key
+ self.crypt_filters = crypt_filters
+ self.stm_filter = self.str_filter = :StdCF
- when 'AES'
- algorithm = Encryption::AES
- if params[:key_size] == 128
- version = revision = 4
- elsif params[:key_size] == 256
- version = 5
- if params[:hardened]
- revision = 6
- else
- revision = 5
- end
- else
- raise EncryptionError, "Invalid AES key length (Only 128 and 256 bits keys are supported)"
- end
+ self
+ end
- crypt_filters = {
- Identity: Encryption::Identity,
- StdCF: algorithm
- }
- string_filter = stream_filter = :StdCF
+ private
- else
- raise EncryptionNotSupportedError, "Cipher not supported : #{params[:cipher]}"
- end
+ #
+ # Installs the standard security dictionary, marking the document as being encrypted.
+ # Returns the handler and the encryption key used for protecting contents.
+ #
+ def create_security_handler(version, revision, params)
+ # Ensure the document has an ID.
doc_id = (trailer_key(:ID) || generate_id).first
+ # Create the standard encryption dictionary.
handler = Encryption::Standard::Dictionary.new
- handler.Filter = :Standard #:nodoc:
+ handler.Filter = :Standard
handler.V = version
handler.R = revision
handler.Length = params[:key_size]
handler.P = -1 # params[:Permissions]
+ # Build the crypt filter dictionary.
if revision >= 4
handler.EncryptMetadata = params[:encrypt_metadata]
handler.CF = Dictionary.new
- cryptfilter = Encryption::CryptFilterDictionary.new
- cryptfilter.AuthEvent = :DocOpen
+ crypt_filter = Encryption::CryptFilterDictionary.new
+ crypt_filter.AuthEvent = :DocOpen
if revision == 4
- cryptfilter.CFM = :AESV2
+ crypt_filter.CFM = :AESV2
else
- cryptfilter.CFM = :AESV3
+ crypt_filter.CFM = :AESV3
end
- cryptfilter.Length = params[:key_size] >> 3
+ crypt_filter.Length = params[:key_size] >> 3
- handler.CF[:StdCF] = cryptfilter
+ handler.CF[:StdCF] = crypt_filter
handler.StmF = handler.StrF = :StdCF
end
- handler.set_passwords(ownerpasswd, userpasswd, doc_id)
- encryption_key = handler.compute_user_encryption_key(userpasswd, doc_id)
+ user_passwd, owner_passwd = params[:user_passwd], params[:owner_passwd]
- file_info = get_trailer_info
- file_info[:Encrypt] = self << handler
+ # Setup keys.
+ handler.set_passwords(owner_passwd, user_passwd, doc_id)
+ encryption_key = handler.compute_user_encryption_key(user_passwd, doc_id)
- self.extend(Encryption::EncryptedDocument)
- self.encryption_handler = handler
- self.encryption_key = encryption_key
- self.crypt_filters = crypt_filters
- self.stm_filter = self.str_filter = :StdCF
+ # Install the encryption dictionary to the document.
+ self.trailer.Encrypt = self << handler
- self
+ [ handler, encryption_key ]
end
+
+ #
+ # Converts the parameters passed to PDF#encrypt.
+ # Returns [ version, revision, crypt_filters ]
+ #
+ def crypto_revision_from_options(params)
+ case params[:cipher].upcase
+ when 'RC4'
+ algorithm = Encryption::RC4
+ version, revision = crypto_revision_from_rc4_key(params[:key_size])
+ crypt_filters = Hash.new(algorithm)
+
+ when 'AES'
+ algorithm = Encryption::AES
+ version, revision = crypto_revision_from_aes_key(params[:key_size], params[:hardened])
+
+ crypt_filters = {
+ Identity: Encryption::Identity,
+ StdCF: algorithm
+ }
+ else
+ raise EncryptionNotSupportedError, "Cipher not supported : #{params[:cipher]}"
+ end
+
+ [ version, revision, crypt_filters ]
+ end
+
+ #
+ # Compute the required standard security handler version based on the RC4 key size.
+ # _key_size_:: Key size in bits.
+ # Returns [ version, revision ].
+ #
+ def crypto_revision_from_rc4_key(key_size)
+ raise EncryptionError, "Invalid RC4 key length" unless (40..128) === key_size and key_size % 8 == 0
+
+ if key_size > 40
+ version = 2
+ revision = 3
+ else
+ version = 1
+ revision = 2
+ end
+
+ [ version, revision ]
+ end
+
+ #
+ # Compute the required standard security handler version based on the AES key size.
+ # _key_size_:: Key size in bits.
+ # _hardened_:: Use the extension level 8 hardened derivation algorithm.
+ # Returns [ version, revision ].
+ #
+ def crypto_revision_from_aes_key(key_size, hardened)
+ if key_size == 128
+ version = revision = 4
+ elsif key_size == 256
+ version = 5
+ if hardened
+ revision = 6
+ else
+ revision = 5
+ end
+ else
+ raise EncryptionError, "Invalid AES key length (Only 128 and 256 bits keys are supported)"
+ end
+
+ [ version, revision ]
+ end
end
#
# Module to provide support for encrypting and decrypting PDF documents.
#
@@ -295,15 +304,11 @@
#
# Generates _n_ random bytes from a crypto PRNG.
#
def self.strong_rand_bytes(n)
- if Origami::OPTIONS[:use_openssl]
- OpenSSL::Random.random_bytes(n)
- else
- SecureRandom.random_bytes(n)
- end
+ SecureRandom.random_bytes(n)
end
module EncryptedDocument
attr_accessor :encryption_key
attr_accessor :encryption_handler
@@ -325,94 +330,95 @@
encryption_cipher @stm_filter
end
private
- def physicalize(options = {})
+ #
+ # For each object subject to encryption, convert it to an EncryptedObject and decrypt it if necessary.
+ #
+ def decrypt_objects
+ each_encryptable_object do |object|
+ case object
+ when String
+ object.extend(EncryptedString) unless object.is_a?(EncryptedString)
+ object.decrypt!
- build = -> (obj, revision) do
- if obj.is_a?(EncryptedObject)
- if options[:decrypt] == true
- obj.pre_build
- obj.decrypt!
- obj.decrypted = false # makes it believe no encryption pass is required
- obj.post_build
-
- return
- end
+ when Stream
+ object.extend(EncryptedStream) unless object.is_a?(EncryptedStream)
end
+ end
+ end
- if obj.is_a?(ObjectStream)
- obj.each do |subobj|
- build.call(subobj, revision)
- end
- end
-
- obj.pre_build
-
- case obj
+ #
+ # For each object subject to encryption, convert it to an EncryptedObject and mark it as not encrypted yet.
+ #
+ def encrypt_objects
+ each_encryptable_object do |object|
+ case object
when String
- if not obj.equal?(@encryption_handler[:U]) and
- not obj.equal?(@encryption_handler[:O]) and
- not obj.equal?(@encryption_handler[:UE]) and
- not obj.equal?(@encryption_handler[:OE]) and
- not obj.equal?(@encryption_handler[:Perms]) and
- not (obj.parent.is_a?(Signature::DigitalSignature) and
- obj.equal?(obj.parent[:Contents])) and
- not obj.indirect_parent.parent.is_a?(ObjectStream)
-
- unless obj.is_a?(EncryptedString)
- obj.extend(EncryptedString)
- obj.decrypted = true
- end
+ unless object.is_a?(EncryptedString)
+ object.extend(EncryptedString)
+ object.decrypted = true
end
when Stream
- return if obj.is_a?(XRefStream)
- return if obj.equal?(self.Catalog.Metadata) and not @encryption_handler.EncryptMetadata
-
- unless obj.is_a?(EncryptedStream)
- obj.extend(EncryptedStream)
- obj.decrypted = true
+ unless object.is_a?(EncryptedStream)
+ object.extend(EncryptedStream)
+ object.decrypted = true
end
+ end
+ end
+ end
- when Dictionary, Array
- obj.map! do |subobj|
- if subobj.indirect?
- if get_object(subobj.reference)
- subobj.reference
- else
- ref = add_to_revision(subobj, revision)
- build.call(subobj, revision)
- ref
- end
- else
- subobj
- end
- end
+ #
+ # Iterates over each encryptable objects in the document.
+ #
+ def each_encryptable_object(&b)
- obj.each do |subobj|
- build.call(subobj, revision)
+ # Metadata may not be encrypted depending on the security handler configuration.
+ encrypt_metadata = (@encryption_handler.EncryptMetadata != false)
+ metadata = self.Catalog.Metadata
+
+ self.each_object(recursive: true)
+ .lazy
+ .select { |object|
+ case object
+ when Stream
+ not object.is_a?(XRefStream) or (encrypt_metadata and object.equal?(metadata))
+ when String
+ not object.parent.equal?(@encryption_handler)
end
- end
+ }
+ .each(&b)
+ end
- obj.post_build
- end
+ def physicalize(options = {})
+ encrypt_objects
- # stack up every root objects
- indirect_objects_by_rev.each do |obj, revision|
- build.call(obj, revision)
- end
+ super
# remove encrypt dictionary if requested
if options[:decrypt]
- delete_object(get_trailer_info[:Encrypt])
- get_trailer_info[:Encrypt] = nil
+ delete_object(self.trailer[:Encrypt])
+ self.trailer[:Encrypt] = nil
end
self
end
+
+ def build_object(object, revision, options)
+ if object.is_a?(EncryptedObject) and options[:decrypt]
+ object.pre_build
+ object.decrypt!
+ object.decrypted = false # makes it believe no encryption pass is required
+ object.post_build
+
+ return
+ end
+
+ super
+ end
end
#
# Module for encrypted PDF objects.
#
@@ -436,11 +442,11 @@
if doc.encryption_handler.V < 5
parent = self.indirect_parent
no, gen = parent.no, parent.generation
k = encryption_key + [no].pack("I")[0..2] + [gen].pack("I")[0..1]
- key_len = (k.length > 16) ? 16 : k.length
+ key_len = [k.length, 16].min
k << "sAlT" if cipher == Encryption::AES
Digest::MD5.digest(k)[0, key_len]
else
encryption_key
@@ -510,25 +516,11 @@
def encrypt!
return self unless @decrypted
encode!
- if self.filters.first == :Crypt
- params = decode_params.first
-
- if params.is_a?(Dictionary) and params.Name.is_a?(Name)
- crypt_filter = params.Name.value
- else
- crypt_filter = :Identity
- end
-
- cipher = self.document.encryption_cipher(crypt_filter)
- else
- cipher = self.document.stream_encryption_cipher
- end
- raise EncryptionError, "Cannot find stream encryption filter" if cipher.nil?
-
+ cipher = get_encryption_cipher
key = compute_object_key(cipher)
@encoded_data =
if cipher == RC4 or cipher == Identity
cipher.encrypt(key, self.encoded_data)
@@ -546,10 +538,26 @@
end
def decrypt!
return self if @decrypted
+ cipher = get_encryption_cipher
+ key = compute_object_key(cipher)
+
+ self.encoded_data = cipher.decrypt(key, @encoded_data)
+ @decrypted = true
+
+ self
+ end
+
+ private
+
+ #
+ # Get the stream encryption cipher.
+ # The cipher used may depend on the presence of a Crypt filter.
+ #
+ def get_encryption_cipher
if self.filters.first == :Crypt
params = decode_params.first
if params.is_a?(Dictionary) and params.Name.is_a?(Name)
crypt_filter = params.Name.value
@@ -559,36 +567,32 @@
cipher = self.document.encryption_cipher(crypt_filter)
else
cipher = self.document.stream_encryption_cipher
end
+
raise EncryptionError, "Cannot find stream encryption filter" if cipher.nil?
- key = compute_object_key(cipher)
-
- self.encoded_data = cipher.decrypt(key, @encoded_data)
- @decrypted = true
-
- self
+ cipher
end
end
#
# Identity transformation.
#
module Identity
- def Identity.encrypt(key, data)
+ def Identity.encrypt(_key, data)
data
end
- def Identity.decrypt(key, data)
+ def Identity.decrypt(_key, data)
data
end
end
#
- # Pure Ruby implementation of the RC4 symmetric algorithm
+ # Class wrapper for the RC4 algorithm.
#
class RC4
#
# Encrypts data using the given key
@@ -606,145 +610,36 @@
#
# Creates and initialises a new RC4 generator using given key
#
def initialize(key)
- if Origami::OPTIONS[:use_openssl]
- @key = key
- else
- @state = init(key)
- end
+ @key = key
end
#
# Encrypt/decrypt data with the RC4 encryption algorithm
#
def cipher(data)
- return "" if data.empty?
+ return '' if data.empty?
- if Origami::OPTIONS[:use_openssl]
- rc4 = OpenSSL::Cipher::RC4.new.encrypt
- rc4.key_len = @key.length
- rc4.key = @key
+ rc4 = OpenSSL::Cipher::RC4.new.encrypt
+ rc4.key_len = @key.length
+ rc4.key = @key
- output = rc4.update(data) << rc4.final
- else
- output = ""
- i, j = 0, 0
- data.each_byte do |byte|
- i = i.succ & 0xFF
- j = (j + @state[i]) & 0xFF
-
- @state[i], @state[j] = @state[j], @state[i]
-
- output << (@state[@state[i] + @state[j] & 0xFF] ^ byte).chr
- end
- end
-
- output
+ rc4.update(data) + rc4.final
end
alias encrypt cipher
alias decrypt cipher
-
- private
-
- def init(key) #:nodoc:
- state = (0..255).to_a
-
- j = 0
- 256.times do |i|
- j = ( j + state[i] + key[i % key.size].ord ) & 0xFF
- state[i], state[j] = state[j], state[i]
- end
-
- state
- end
end
#
- # Pure Ruby implementation of the AES symmetric algorithm.
- # Using mode CBC.
+ # Class wrapper for AES mode CBC.
#
class AES
- NROWS = 4
- NCOLS = 4
- BLOCKSIZE = NROWS * NCOLS
+ BLOCKSIZE = 16
- ROUNDS =
- {
- 16 => 10,
- 24 => 12,
- 32 => 14
- }
-
- #
- # Rijndael S-box
- #
- SBOX =
- [
- 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76,
- 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0,
- 0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15,
- 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75,
- 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84,
- 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf,
- 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8,
- 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2,
- 0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73,
- 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb,
- 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79,
- 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08,
- 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a,
- 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e,
- 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf,
- 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16
- ]
-
- #
- # Inverse of the Rijndael S-box
- #
- RSBOX =
- [
- 0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb,
- 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb,
- 0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e,
- 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25,
- 0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92,
- 0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84,
- 0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06,
- 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b,
- 0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73,
- 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e,
- 0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b,
- 0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4,
- 0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f,
- 0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef,
- 0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61,
- 0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d
- ]
-
- RCON =
- [
- 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a,
- 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39,
- 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a,
- 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8,
- 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef,
- 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc,
- 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b,
- 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3,
- 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94,
- 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20,
- 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35,
- 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f,
- 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04,
- 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63,
- 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd,
- 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb
- ]
-
attr_writer :iv
def AES.encrypt(key, iv, data)
AES.new(key, iv).encrypt(data)
end
@@ -752,11 +647,11 @@
def AES.decrypt(key, data)
AES.new(key, nil).decrypt(data)
end
def initialize(key, iv, use_padding = true)
- unless key.size == 16 or key.size == 24 or key.size == 32
+ unless [16, 24, 32].include?(key.size)
raise EncryptionError, "Key must have a length of 128, 192 or 256 bits."
end
if not iv.nil? and iv.size != BLOCKSIZE
raise EncryptionError, "Initialization vector must have a length of #{BLOCKSIZE} bytes."
@@ -775,79 +670,32 @@
if @use_padding
padlen = BLOCKSIZE - (data.size % BLOCKSIZE)
data << (padlen.chr * padlen)
end
- if Origami::OPTIONS[:use_openssl]
- aes = OpenSSL::Cipher::Cipher.new("aes-#{@key.length << 3}-cbc").encrypt
- aes.iv = @iv
- aes.key = @key
- aes.padding = 0
+ aes = OpenSSL::Cipher.new("aes-#{@key.length << 3}-cbc").encrypt
+ aes.iv = @iv
+ aes.key = @key
+ aes.padding = 0
- @iv + aes.update(data) + aes.final
- else
- cipher = []
- cipherblock = []
- nblocks = data.size / BLOCKSIZE
-
- first_round = true
- nblocks.times do |n|
- plainblock = data[n * BLOCKSIZE, BLOCKSIZE].unpack("C*")
-
- if first_round
- BLOCKSIZE.times do |i| plainblock[i] ^= @iv[i].ord end
- else
- BLOCKSIZE.times do |i| plainblock[i] ^= cipherblock[i] end
- end
-
- first_round = false
- cipherblock = aes_encrypt(plainblock)
- cipher.concat(cipherblock)
- end
-
- @iv + cipher.pack("C*")
- end
+ @iv + aes.update(data) + aes.final
end
def decrypt(data)
unless data.size % BLOCKSIZE == 0
raise EncryptionError, "Data must be 16-bytes padded (data size = #{data.size} bytes)"
end
@iv = data.slice!(0, BLOCKSIZE)
- if Origami::OPTIONS[:use_openssl]
- aes = OpenSSL::Cipher::Cipher.new("aes-#{@key.length << 3}-cbc").decrypt
- aes.iv = @iv
- aes.key = @key
- aes.padding = 0
+ aes = OpenSSL::Cipher.new("aes-#{@key.length << 3}-cbc").decrypt
+ aes.iv = @iv
+ aes.key = @key
+ aes.padding = 0
- plain = (aes.update(data) + aes.final).unpack("C*")
- else
- plain = []
- plainblock = []
- prev_cipherblock = []
- nblocks = data.size / BLOCKSIZE
+ plain = (aes.update(data) + aes.final).unpack("C*")
- first_round = true
- nblocks.times do |n|
- cipherblock = data[n * BLOCKSIZE, BLOCKSIZE].unpack("C*")
-
- plainblock = aes_decrypt(cipherblock)
-
- if first_round
- BLOCKSIZE.times do |i| plainblock[i] ^= @iv[i].ord end
- else
- BLOCKSIZE.times do |i| plainblock[i] ^= prev_cipherblock[i] end
- end
-
- first_round = false
- prev_cipherblock = cipherblock
- plain.concat(plainblock)
- end
- end
-
if @use_padding
padlen = plain[-1]
unless (1..16) === padlen
raise EncryptionError, "Incorrect padding length : #{padlen}"
end
@@ -858,227 +706,10 @@
end
end
plain.pack("C*")
end
-
- private
-
- def rol(row, n = 1) #:nodoc
- n.times do row.push row.shift end ; row
- end
-
- def ror(row, n = 1) #:nodoc:
- n.times do row.unshift row.pop end ; row
- end
-
- def galois_mult(a, b) #:nodoc:
- p = 0
-
- 8.times do
- p ^= a if b[0] == 1
- highBit = a[7]
- a <<= 1
- a ^= 0x1b if highBit == 1
- b >>= 1
- end
-
- p % 256
- end
-
- def schedule_core(word, iter) #:nodoc:
- rol(word)
- word.map! do |byte| SBOX[byte] end
- word[0] ^= RCON[iter]
-
- word
- end
-
- def transpose(m) #:nodoc:
- [
- m[NROWS * 0, NROWS],
- m[NROWS * 1, NROWS],
- m[NROWS * 2, NROWS],
- m[NROWS * 3, NROWS]
- ].transpose.flatten
- end
-
- #
- # AES round methods.
- #
-
- def create_round_key(expanded_key, round = 0) #:nodoc:
- transpose(expanded_key[round * BLOCKSIZE, BLOCKSIZE])
- end
-
- def add_round_key(roundKey) #:nodoc:
- BLOCKSIZE.times do |i| @state[i] ^= roundKey[i] end
- end
-
- def sub_bytes #:nodoc:
- BLOCKSIZE.times do |i| @state[i] = SBOX[ @state[i] ] end
- end
-
- def r_sub_bytes #:nodoc:
- BLOCKSIZE.times do |i| @state[i] = RSBOX[ @state[i] ] end
- end
-
- def shift_rows #:nodoc:
- NROWS.times do |i|
- @state[i * NCOLS, NCOLS] = rol(@state[i * NCOLS, NCOLS], i)
- end
- end
-
- def r_shift_rows #:nodoc:
- NROWS.times do |i|
- @state[i * NCOLS, NCOLS] = ror(@state[i * NCOLS, NCOLS], i)
- end
- end
-
- def mix_column_with_field(column, field) #:nodoc:
- p = field
-
- column[0], column[1], column[2], column[3] =
- galois_mult(column[0], p[0]) ^
- galois_mult(column[3], p[1]) ^
- galois_mult(column[2], p[2]) ^
- galois_mult(column[1], p[3]),
-
- galois_mult(column[1], p[0]) ^
- galois_mult(column[0], p[1]) ^
- galois_mult(column[3], p[2]) ^
- galois_mult(column[2], p[3]),
-
- galois_mult(column[2], p[0]) ^
- galois_mult(column[1], p[1]) ^
- galois_mult(column[0], p[2]) ^
- galois_mult(column[3], p[3]),
-
- galois_mult(column[3], p[0]) ^
- galois_mult(column[2], p[1]) ^
- galois_mult(column[1], p[2]) ^
- galois_mult(column[0], p[3])
- end
-
- def mix_column(column) #:nodoc:
- mix_column_with_field(column, [ 2, 1, 1, 3 ])
- end
-
- def r_mix_column_(column) #:nodoc:
- mix_column_with_field(column, [ 14, 9, 13, 11 ])
- end
-
- def mix_columns #:nodoc:
- NCOLS.times do |c|
- column = []
- NROWS.times do |r| column << @state[c + r * NCOLS] end
- mix_column(column)
- NROWS.times do |r| @state[c + r * NCOLS] = column[r] end
- end
- end
-
- def r_mix_columns #:nodoc:
- NCOLS.times do |c|
- column = []
- NROWS.times do |r| column << @state[c + r * NCOLS] end
- r_mix_column_(column)
- NROWS.times do |r| @state[c + r * NCOLS] = column[r] end
- end
- end
-
- def expand_key(key) #:nodoc:
- key = key.unpack("C*")
- size = key.size
- expanded_size = 16 * (ROUNDS[key.size] + 1)
- rcon_iter = 1
- expanded_key = key[0, size]
-
- while expanded_key.size < expanded_size
- temp = expanded_key[-4, 4]
-
- if expanded_key.size % size == 0
- schedule_core(temp, rcon_iter)
- rcon_iter = rcon_iter.succ
- end
-
- temp.map! do |b| SBOX[b] end if size == 32 and expanded_key.size % size == 16
-
- temp.each do |b| expanded_key << (expanded_key[-size] ^ b) end
- end
-
- expanded_key
- end
-
- def aes_round(round_key) #:nodoc:
- sub_bytes
- #puts "after sub_bytes: #{@state.inspect}"
- shift_rows
- #puts "after shift_rows: #{@state.inspect}"
- mix_columns
- #puts "after mix_columns: #{@state.inspect}"
- add_round_key(round_key)
- #puts "roundKey = #{roundKey.inspect}"
- #puts "after add_round_key: #{@state.inspect}"
- end
-
- def r_aes_round(round_key) #:nodoc:
- add_round_key(round_key)
- r_mix_columns
- r_shift_rows
- r_sub_bytes
- end
-
- def aes_encrypt(block) #:nodoc:
- @state = transpose(block)
- expanded_key = expand_key(@key)
- rounds = ROUNDS[@key.size]
-
- aes_main(expanded_key, rounds)
- end
-
- def aes_decrypt(block) #:nodoc:
- @state = transpose(block)
- expanded_key = expand_key(@key)
- rounds = ROUNDS[@key.size]
-
- r_aes_main(expanded_key, rounds)
- end
-
- def aes_main(expanded_key, rounds) #:nodoc:
- #puts "expandedKey: #{expandedKey.inspect}"
- round_key = create_round_key(expanded_key)
- add_round_key(round_key)
-
- for i in 1..rounds-1
- round_key = create_round_key(expanded_key, i)
- aes_round(round_key)
- end
-
- round_key = create_round_key(expanded_key, rounds)
- sub_bytes
- shift_rows
- add_round_key(round_key)
-
- transpose(@state)
- end
-
- def r_aes_main(expanded_key, rounds) #:nodoc:
- round_key = create_round_key(expanded_key, rounds)
- add_round_key(round_key)
- r_shift_rows
- r_sub_bytes
-
- (rounds - 1).downto(1) do |i|
- round_key = create_round_key(expanded_key, i)
- r_aes_round(round_key)
- end
-
- round_key = create_round_key(expanded_key)
- add_round_key(round_key)
-
- transpose(@state)
- end
end
#
# Class representing a crypt filter Dictionary
#
@@ -1154,131 +785,138 @@
end
end
#
# Computes the key that will be used to encrypt/decrypt the document contents with user password.
+ # Called at all revisions.
#
- def compute_user_encryption_key(userpassword, fileid)
- if self.R < 5
- padded = pad_password(userpassword)
- padded.force_encoding('binary')
+ def compute_user_encryption_key(user_password, file_id)
+ return compute_legacy_user_encryption_key(user_password, file_id) if self.R < 5
- padded << self.O
- padded << [ self.P ].pack("i")
+ passwd = password_to_utf8(user_password)
- padded << fileid
+ uks = self.U[40, 8]
- encrypt_metadata = self.EncryptMetadata != false
- padded << [ -1 ].pack("i") if self.R >= 4 and not encrypt_metadata
+ if self.R == 5
+ ukey = Digest::SHA256.digest(passwd + uks)
+ else
+ ukey = compute_hardened_hash(passwd, uks)
+ end
- key = Digest::MD5.digest(padded)
+ iv = ::Array.new(AES::BLOCKSIZE, 0).pack("C*")
+ AES.new(ukey, nil, false).decrypt(iv + self.UE.value)
+ end
- 50.times { key = Digest::MD5.digest(key[0, self.Length / 8]) } if self.R >= 3
+ #
+ # Computes the key that will be used to encrypt/decrypt the document contents.
+ # Only for Revision 4 and less.
+ #
+ def compute_legacy_user_encryption_key(user_password, file_id)
+ padded = pad_password(user_password)
+ padded.force_encoding('binary')
- if self.R == 2
- key[0, 5]
- elsif self.R >= 3
- key[0, self.Length / 8]
- end
- else
- passwd = password_to_utf8(userpassword)
+ padded << self.O
+ padded << [ self.P ].pack("i")
- uks = self.U[40, 8]
+ padded << file_id
- if self.R == 5
- ukey = Digest::SHA256.digest(passwd + uks)
- else
- ukey = compute_hardened_hash(passwd, uks)
- end
+ encrypt_metadata = self.EncryptMetadata != false
+ padded << [ -1 ].pack("i") if self.R >= 4 and not encrypt_metadata
- iv = ::Array.new(AES::BLOCKSIZE, 0).pack("C*")
- AES.new(ukey, nil, false).decrypt(iv + self.UE.value)
- end
+ key = Digest::MD5.digest(padded)
+
+ 50.times { key = Digest::MD5.digest(key[0, self.Length / 8]) } if self.R >= 3
+
+ truncate_key(key)
end
#
# Computes the key that will be used to encrypt/decrypt the document contents with owner password.
# Revision 5 and above.
#
- def compute_owner_encryption_key(ownerpassword)
- if self.R >= 5
- passwd = password_to_utf8(ownerpassword)
+ def compute_owner_encryption_key(owner_password)
+ return if self.R < 5
- oks = self.O[40, 8]
+ passwd = password_to_utf8(owner_password)
+ oks = self.O[40, 8]
- if self.R == 5
- okey = Digest::SHA256.digest(passwd + oks + self.U)
- else
- 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)
+ if self.R == 5
+ okey = Digest::SHA256.digest(passwd + oks + self.U)
+ else
+ 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
#
# Set up document passwords.
#
- def set_passwords(ownerpassword, userpassword, salt = nil)
- if self.R < 5
- key = compute_owner_key(ownerpassword)
- upadded = pad_password(userpassword)
+ def set_passwords(owner_password, user_password, salt = nil)
+ return set_legacy_passwords(owner_password, user_password, salt) if self.R < 5
- owner_key = RC4.encrypt(key, upadded)
- 19.times { |i| owner_key = RC4.encrypt(xor(key,i+1), owner_key) } if self.R >= 3
+ upass = password_to_utf8(user_password)
+ opass = password_to_utf8(owner_password)
- self.O = owner_key
- self.U = compute_user_password(userpassword, salt)
+ 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 + self.U)
else
- upass = password_to_utf8(userpassword)
- opass = password_to_utf8(ownerpassword)
+ 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(opass, oks, self.U)
+ end
- 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*")
+ 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
- ukey = Digest::SHA256.digest(upass + uks)
- 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(opass, oks, self.U)
- end
+ perms =
+ [ self.P ].pack("V") + # 0-3
+ [ -1 ].pack("V") + # 4-7
+ (self.EncryptMetadata == true ? "T" : "F") + # 8
+ "adb" + # 9-11
+ [ 0 ].pack("V") # 12-15
- 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]
+ self.Perms = AES.new(file_key, iv, false).encrypt(perms)[iv.size, 16]
- perms =
- [ self.P ].pack("V") + # 0-3
- [ -1 ].pack("V") + # 4-7
- (self.EncryptMetadata == true ? "T" : "F") + # 8
- "adb" + # 9-11
- [ 0 ].pack("V") # 12-15
+ file_key
+ end
- self.Perms = AES.new(file_key, iv, false).encrypt(perms)[iv.size, 16]
+ #
+ # Set up document passwords.
+ # Only for Revision 4 and less.
+ #
+ def set_legacy_passwords(owner_password, user_password, salt)
+ owner_key = compute_owner_key(owner_password)
+ upadded = pad_password(user_password)
- file_key
- end
+ owner_key_hash = RC4.encrypt(owner_key, upadded)
+ 19.times { |i| owner_key_hash = RC4.encrypt(xor(owner_key, i + 1), owner_key_hash) } if self.R >= 3
+
+ self.O = owner_key_hash
+ self.U = compute_user_password_hash(user_password, salt)
end
#
# Checks user password.
- # For version 2,3 and 4, _salt_ is the document ID.
+ # For version 2, 3 and 4, _salt_ is the document ID.
# For version 5 and 6, _salt_ is the User Key Salt.
#
def is_user_password?(pass, salt)
if self.R == 2
- compute_user_password(pass, salt) == self.U
+ compute_user_password_hash(pass, salt) == self.U
elsif self.R == 3 or self.R == 4
- compute_user_password(pass, salt)[0, 16] == self.U[0, 16]
+ compute_user_password_hash(pass, salt)[0, 16] == self.U[0, 16]
elsif self.R == 5
uvs = self.U[32, 8]
Digest::SHA256.digest(password_to_utf8(pass) + uvs) == self.U[0, 32]
elsif self.R == 6
uvs = self.U[32, 8]
@@ -1307,13 +945,13 @@
#
# Retrieve user password from owner password.
# Cannot be used with revision 5.
#
- def retrieve_user_password(ownerpassword)
+ def retrieve_user_password(owner_password)
- key = compute_owner_key(ownerpassword)
+ key = compute_owner_key(owner_password)
if self.R == 2
RC4.decrypt(key, self.O)
elsif self.R == 3 or self.R == 4
user_password = RC4.decrypt(xor(key, 19), self.O)
@@ -1328,35 +966,31 @@
#
# Used to encrypt/decrypt the O field.
# Rev 2,3,4: O = crypt(user_pass, owner_key).
# Rev 5: unused.
#
- def compute_owner_key(ownerpassword) #:nodoc:
+ def compute_owner_key(owner_password) #:nodoc:
- opadded = pad_password(ownerpassword)
+ opadded = pad_password(owner_password)
- hash = Digest::MD5.digest(opadded)
- 50.times { hash = Digest::MD5.digest(hash) } if self.R >= 3
+ owner_key = Digest::MD5.digest(opadded)
+ 50.times { owner_key = Digest::MD5.digest(owner_key) } if self.R >= 3
- if self.R == 2
- hash[0, 5]
- elsif self.R >= 3
- hash[0, self.Length / 8]
- end
+ truncate_key(owner_key)
end
#
# Compute the value of the U field.
# Cannot be used with revision 5.
#
- def compute_user_password(userpassword, salt) #:nodoc:
+ def compute_user_password_hash(user_password, salt) #:nodoc:
if self.R == 2
- key = compute_user_encryption_key(userpassword, salt)
+ key = compute_user_encryption_key(user_password, salt)
user_key = RC4.encrypt(key, PADDING)
elsif self.R == 3 or self.R == 4
- key = compute_user_encryption_key(userpassword, salt)
+ key = compute_user_encryption_key(user_password, salt)
upadded = PADDING + salt
hash = Digest::MD5.digest(upadded)
user_key = RC4.encrypt(key, hash)
@@ -1380,18 +1014,14 @@
i = 0
while i < 64 or i < x[-1].ord + 32
block = input[0, block_size]
- if Origami::OPTIONS[:use_openssl]
- aes = OpenSSL::Cipher::Cipher.new("aes-128-cbc").encrypt
- aes.iv = iv
- aes.key = key
- aes.padding = 0
- else
- fail "You need OpenSSL support to encrypt/decrypt documents with this method"
- end
+ aes = OpenSSL::Cipher.new("aes-128-cbc").encrypt
+ aes.iv = iv
+ aes.key = key
+ aes.padding = 0
64.times do |j|
x = ''
x += aes.update(password) unless password.empty?
x += aes.update(block)
@@ -1414,16 +1044,28 @@
end
h[0, 32]
end
+ #
+ # Some revision handlers require different key sizes.
+ # Revision 2 uses 40-bit keys.
+ # Revisions 3 and higher rely on the Length field for the key size.
+ #
+ def truncate_key(key)
+ if self.R == 2
+ key[0, 5]
+ elsif self.R >= 3
+ key[0, self.Length / 8]
+ end
+ end
+
def xor(str, byte) #:nodoc:
- str.split(//).map!{|c| (c[0].ord ^ byte).chr }.join
+ str.bytes.map!{|b| b ^ byte }.pack("C*")
end
def pad_password(password) #:nodoc:
- return PADDING.dup if password.empty? # Fix for Ruby 1.9 bug
- password[0,32].ljust(32, PADDING)
+ password[0, 32].ljust(32, PADDING)
end
def password_to_utf8(passwd) #:nodoc:
LiteralString.new(passwd).to_utf8[0, 127]
end