lib/origami/encryption.rb in origami-1.2.7 vs lib/origami/encryption.rb in origami-2.0.0
- old
+ new
@@ -1,1404 +1,1435 @@
=begin
-= File
- encryption.rb
+ This file is part of Origami, PDF manipulation framework for Ruby
+ Copyright (C) 2016 Guillaume Delugré.
-= Info
- This file is part of Origami, PDF manipulation framework for Ruby
- Copyright (C) 2010 Guillaume DelugrÈ <guillaume AT security-labs DOT org>
- All right reserved.
-
- Origami is free software: you can redistribute it and/or modify
- it under the terms of the GNU Lesser General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
+ Origami is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
- Origami is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Lesser General Public License for more details.
+ Origami is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Lesser General Public License for more details.
- You should have received a copy of the GNU Lesser General Public License
- along with Origami. If not, see <http://www.gnu.org/licenses/>.
+ 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]
+ require 'openssl' if Origami::OPTIONS[:use_openssl]
rescue LoadError
- Origami::OPTIONS[:use_openssl] = false
+ Origami::OPTIONS[:use_openssl] = false
end
+require 'securerandom'
require 'digest/md5'
require 'digest/sha2'
module Origami
- class EncryptionError < Exception #:nodoc:
- end
+ class EncryptionError < Error #:nodoc:
+ end
- class EncryptionInvalidPasswordError < EncryptionError #:nodoc:
- end
+ class EncryptionInvalidPasswordError < EncryptionError #:nodoc:
+ end
- class EncryptionNotSupportedError < EncryptionError #:nodoc:
- end
-
- class PDF
-
- #
- # Returns whether the PDF file is encrypted.
- #
- def is_encrypted?
- has_attr? :Encrypt
+ class EncryptionNotSupportedError < EncryptionError #:nodoc:
end
-
- #
- # Decrypts the current document (only RC4 40..128 bits).
- # _passwd_:: The password to decrypt the document.
- #
- def decrypt(passwd = "")
-
- unless self.is_encrypted?
- raise EncryptionError, "PDF is not encrypted"
- end
-
- encrypt_dict = get_doc_attr(:Encrypt)
- handler = Encryption::Standard::Dictionary.new(encrypt_dict.dup)
- unless handler.Filter == :Standard
- raise EncryptionNotSupportedError, "Unknown security handler : '#{handler.Filter.to_s}'"
- end
+ class PDF
- case handler.V.to_i
- when 1,2 then str_algo = stm_algo = Encryption::ARC4
- when 4,5
- if handler[:CF].is_a?(Dictionary)
- cfs = handler[:CF]
-
- if handler[:StrF].is_a?(Name) and cfs[handler[:StrF]].is_a?(Dictionary)
- cfdict = cfs[handler[:StrF]]
-
- str_algo =
- if cfdict[:CFM] == :V2 then Encryption::ARC4
- elsif cfdict[:CFM] == :AESV2 then Encryption::AES
- elsif cfdict[:CFM] == :None then Encryption::Identity
- elsif cfdict[:CFM] == :AESV3 and handler.V.to_i == 5 then Encryption::AES
- else
- raise EncryptionNotSupportedError, "Unsupported encryption version : #{handler.V}"
+ #
+ # Returns whether the PDF file is encrypted.
+ #
+ def encrypted?
+ trailer_key? :Encrypt
+ end
+
+ #
+ # Decrypts the current document (only RC4 40..128 bits).
+ # _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)
+
+ unless handler.Filter == :Standard
+ raise EncryptionNotSupportedError, "Unknown security handler : '#{handler.Filter.to_s}'"
+ end
+
+ crypt_filters = {
+ Identity: Encryption::Identity
+ }
+
+ case handler.V.to_i
+ when 1,2
+ crypt_filters = Hash.new(Encryption::RC4)
+ string_filter = stream_filter = nil
+ when 4,5
+ crypt_filters = {
+ Identity: Encryption::Identity
+ }
+
+ if handler[:CF].is_a?(Dictionary)
+ handler[:CF].each_pair do |name, cf|
+ next unless cf.is_a?(Dictionary)
+
+ crypt_filters[name.value] =
+ if cf[:CFM] == :V2 then Encryption::RC4
+ elsif cf[:CFM] == :AESV2 then Encryption::AES
+ elsif cf[:CFM] == :None then Encryption::Identity
+ elsif cf[:CFM] == :AESV3 and handler.V.to_i == 5 then Encryption::AES
+ else
+ raise EncryptionNotSupportedError, "Unsupported encryption version : #{handler.V}"
+ end
+ end
end
+
+ string_filter = handler.StrF.is_a?(Name) ? handler.StrF.value : :Identity
+ stream_filter = handler.StmF.is_a?(Name) ? handler.StmF.value : :Identity
+
+ unless crypt_filters.key?(string_filter)
+ raise EncryptionError, "Invalid StrF value in encryption dictionary"
+ end
+
+ unless crypt_filters.key?(stream_filter)
+ raise EncryptionError, "Invalid StmF value in encryption dictionary"
+ end
else
- str_algo = Encryption::Identity
+ raise EncryptionNotSupportedError, "Unsupported encryption version : #{handler.V}"
end
- if handler[:StmF].is_a?(Name) and cfs[handler[:StmF]].is_a?(Dictionary)
- cfdict = cfs[handler[:StmF]]
+ doc_id = trailer_key(:ID)
+ unless doc_id.is_a?(Array)
+ raise EncryptionError, "Document ID was not found or is invalid" unless handler.V.to_i == 5
+ else
+ doc_id = doc_id.first
+ end
- stm_algo =
- if cfdict[:CFM] == :V2 then Encryption::ARC4
- elsif cfdict[:CFM] == :AESV2 then Encryption::AES
- elsif cfdict[:CFM] == :None then Encryption::Identity
- elsif cfdict[:CFM] == :AESV3 and handler.V.to_i == 5 then Encryption::AES
+ if handler.is_user_password?(passwd, doc_id)
+ encryption_key = handler.compute_user_encryption_key(passwd, doc_id)
+ elsif handler.is_owner_password?(passwd, doc_id)
+ if handler.V.to_i < 5
+ user_passwd = handler.retrieve_user_password(passwd)
+ encryption_key = handler.compute_user_encryption_key(user_passwd, doc_id)
else
- raise EncryptionNotSupportedError, "Unsupported encryption version : #{handler.V}"
+ encryption_key = handler.compute_owner_encryption_key(passwd)
end
else
- stm_algo = Encryption::Identity
+ raise EncryptionInvalidPasswordError
end
- else
- str_algo = stm_algo = Encryption::Identity
- end
+ encrypt_metadata = (handler.EncryptMetadata != false)
- else
- raise EncryptionNotSupportedError, "Unsupported encryption version : #{handler.V}"
- end
-
- doc_id = get_doc_attr(:ID)
- unless doc_id.is_a?(Array)
- raise EncryptionError, "Document ID was not found or is invalid" unless handler.V.to_i == 5
- else
- doc_id = doc_id.first
- end
+ 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
- if handler.is_user_password?(passwd, doc_id)
- encryption_key = handler.compute_user_encryption_key(passwd, doc_id)
- elsif handler.is_owner_password?(passwd, doc_id)
- if handler.V.to_i < 5
- user_passwd = handler.retrieve_user_password(passwd)
- encryption_key = handler.compute_user_encryption_key(user_passwd, doc_id)
- else
- encryption_key = handler.compute_owner_encryption_key(passwd)
- end
- else
- raise EncryptionInvalidPasswordError
- end
+ #
+ # Should be fixed to exclude only the active XRefStream
+ #
+ metadata = self.Catalog.Metadata
+ 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
- #self.extend(Encryption::EncryptedDocument)
- #self.encryption_dict = encrypt_dict
- #self.encryption_key = encryption_key
- #self.stm_algo = self.str_algo = algorithm
+ 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]))
- encrypt_metadata = (handler.EncryptMetadata != false)
+ obj.extend(Encryption::EncryptedString) unless obj.is_a?(Encryption::EncryptedString)
+ obj.decrypt!
- self.extend(Encryption::EncryptedDocument)
- self.encryption_dict = handler
- self.encryption_key = encryption_key
- self.stm_algo,self.str_algo = stm_algo,str_algo
-
- #
- # Should be fixed to exclude only the active XRefStream
- #
- metadata = self.Catalog.Metadata
+ when Stream
+ next if obj.is_a?(XRefStream) or (not encrypt_metadata and obj.equal?(metadata))
- 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
+ obj.extend(Encryption::EncryptedStream) unless obj.is_a?(Encryption::EncryptedStream)
+ end
+ end
+ end
+
+ self
end
- encrypted_objects.each do |obj|
+ #
+ # Encrypts the current document with the provided passwords.
+ # The document will be encrypted at writing-on-disk time.
+ # _userpasswd_:: The user password.
+ # _ownerpasswd_:: The owner password.
+ # _options_:: A set of options to configure encryption.
+ #
+ def encrypt(options = {})
+ raise EncryptionError, "PDF is already encrypted" if self.encrypted?
- 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]))
+ #
+ # Default encryption options.
+ #
+ params =
+ {
+ :user_passwd => '',
+ :owner_passwd => '',
+ :cipher => 'rc4', # :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)
- obj.extend(Encryption::EncryptedString) unless obj.is_a?(Encryption::EncryptedString)
- obj.encryption_handler = handler
- obj.encryption_key = encryption_key
- obj.algorithm = str_algo
- obj.decrypt!
+ userpasswd, ownerpasswd = params[:user_passwd], params[:owner_passwd]
- 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)
- obj.encryption_handler = handler
- obj.encryption_key = encryption_key
- obj.algorithm = stm_algo
- end
- end
- end
+ 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
- self
- end
-
- #
- # Encrypts the current document with the provided passwords.
- # The document will be encrypted at writing-on-disk time.
- # _userpasswd_:: The user password.
- # _ownerpasswd_:: The owner password.
- # _options_:: A set of options to configure encryption.
- #
- def encrypt(options = {})
-
- if self.is_encrypted?
- raise EncryptionError, "PDF is already encrypted"
- end
+ crypt_filters = Hash.new(algorithm)
+ string_filter = stream_filter = nil
- #
- # Default encryption options.
- #
- params =
- {
- :user_passwd => '',
- :owner_passwd => '',
- :cipher => 'rc4', # :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)
+ 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
- userpasswd, ownerpasswd = params[:user_passwd], params[:owner_passwd]
+ crypt_filters = {
+ Identity: Encryption::Identity,
+ StdCF: algorithm
+ }
+ string_filter = stream_filter = :StdCF
- case params[:cipher].upcase
- when 'RC4'
- algorithm = Encryption::ARC4
- 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
+ raise EncryptionNotSupportedError, "Cipher not supported : #{params[:cipher]}"
end
- else
- raise EncryptionError, "Invalid RC4 key length"
- end
- 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
- else
- raise EncryptionNotSupportedError, "Cipher not supported : #{params[:cipher]}"
- end
-
- doc_id = (get_doc_attr(:ID) || gen_id).first
- handler = Encryption::Standard::Dictionary.new
- handler.Filter = :Standard #:nodoc:
- handler.V = version
- handler.R = revision
- handler.Length = params[:key_size]
- handler.P = -1 # params[:Permissions]
-
- if revision >= 4
- handler.EncryptMetadata = params[:encrypt_metadata]
- handler.CF = Dictionary.new
- cryptfilter = Encryption::CryptFilterDictionary.new
- cryptfilter.AuthEvent = :DocOpen
-
- if revision == 4
- cryptfilter.CFM = :AESV2
- else
- cryptfilter.CFM = :AESV3
- end
+ doc_id = (trailer_key(:ID) || generate_id).first
- cryptfilter.Length = params[:key_size] >> 3
+ handler = Encryption::Standard::Dictionary.new
+ handler.Filter = :Standard #:nodoc:
+ handler.V = version
+ handler.R = revision
+ handler.Length = params[:key_size]
+ handler.P = -1 # params[:Permissions]
- handler.CF[:StdCF] = cryptfilter
- handler.StmF = handler.StrF = :StdCF
- end
-
- handler.set_passwords(ownerpasswd, userpasswd, doc_id)
- encryption_key = handler.compute_user_encryption_key(userpasswd, doc_id)
+ if revision >= 4
+ handler.EncryptMetadata = params[:encrypt_metadata]
+ handler.CF = Dictionary.new
+ cryptfilter = Encryption::CryptFilterDictionary.new
+ cryptfilter.AuthEvent = :DocOpen
- fileInfo = get_trailer_info
- fileInfo[:Encrypt] = self << handler
+ if revision == 4
+ cryptfilter.CFM = :AESV2
+ else
+ cryptfilter.CFM = :AESV3
+ end
- self.extend(Encryption::EncryptedDocument)
- self.encryption_dict = handler
- self.encryption_key = encryption_key
- self.stm_algo = self.str_algo = algorithm
+ cryptfilter.Length = params[:key_size] >> 3
- self
- end
-
- end
+ handler.CF[:StdCF] = cryptfilter
+ handler.StmF = handler.StrF = :StdCF
+ end
- #
- # Module to provide support for encrypting and decrypting PDF documents.
- #
- module Encryption
+ handler.set_passwords(ownerpasswd, userpasswd, doc_id)
+ encryption_key = handler.compute_user_encryption_key(userpasswd, doc_id)
- #
- # Generates _n_ random bytes from a fast PRNG.
- #
- def self.rand_bytes(n)
- ::Array.new(n) { rand(256) }.pack("C*")
+ file_info = get_trailer_info
+ file_info[:Encrypt] = self << handler
+
+ 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
+
+ self
+ end
end
#
- # Generates _n_ random bytes from a crypto PRNG.
+ # Module to provide support for encrypting and decrypting PDF documents.
#
- 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 Encryption
- module EncryptedDocument
+ #
+ # Generates _n_ random bytes from a fast PRNG.
+ #
+ def self.rand_bytes(n)
+ Random.new.bytes(n)
+ end
- attr_writer :encryption_key
- attr_writer :encryption_dict
- attr_writer :stm_algo
- attr_writer :str_algo
+ #
+ # 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
+ end
- private
+ module EncryptedDocument
+ attr_accessor :encryption_key
+ attr_accessor :encryption_handler
+ attr_accessor :str_filter, :stm_filter
+ attr_accessor :crypt_filters
- def physicalize(options = {})
+ # Get the encryption cipher from the crypt filter name.
+ def encryption_cipher(name)
+ @crypt_filters[name]
+ end
- def build(obj, revision, options) #:nodoc:
- if obj.is_a?(EncryptedObject) # already built
- if options[:decrypt] == true
- obj.pre_build
- obj.decrypt!
- obj.decrypted = false # makes it believe no encryption pass is required
- obj.post_build
+ # Get the default string encryption cipher.
+ def string_encryption_cipher
+ encryption_cipher @str_filter
end
- return
- end
-
- if obj.is_a?(ObjectStream)
- obj.each do |subobj|
- build(subobj, revision, options)
+ # Get the default stream encryption cipher.
+ def stream_encryption_cipher
+ encryption_cipher @stm_filter
end
- end
- obj.pre_build
+ private
- case obj
- when String
- if not obj.equal?(@encryption_dict[:U]) and
- not obj.equal?(@encryption_dict[:O]) and
- not obj.equal?(@encryption_dict[:UE]) and
- not obj.equal?(@encryption_dict[:OE]) and
- not obj.equal?(@encryption_dict[:Perms]) and
- not (obj.parent.is_a?(Signature::DigitalSignature) and obj.equal?(obj.parent[:Contents])) and
- not obj.indirect_parent.parent.is_a?(ObjectStream)
-
- obj.extend(EncryptedString)
- obj.decrypted = true
- obj.encryption_handler = @encryption_dict
- obj.encryption_key = @encryption_key
- obj.algorithm = @str_algo
- end
+ def physicalize(options = {})
- when Stream
- return if obj.is_a?(XRefStream)
- return if obj.equal?(self.Catalog.Metadata) and not @encryption_dict.EncryptMetadata
- obj.extend(EncryptedStream)
- obj.decrypted = true
- obj.encryption_handler = @encryption_dict
- obj.encryption_key = @encryption_key
- obj.algorithm = @stm_algo
+ 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
- when Dictionary, Array
- obj.map! do |subobj|
- if subobj.is_indirect?
- if get_object(subobj.reference)
- subobj.reference
- else
- ref = add_to_revision(subobj, revision)
- build(subobj, revision, options)
- ref
- end
- else
- subobj
- end
- end
-
- obj.each do |subobj|
- build(subobj, revision, options)
- end
- end
+ return
+ end
+ end
- obj.post_build
- end
-
- # stack up every root objects
- indirect_objects_by_rev.each do |obj, revision|
- build(obj, revision, options)
- end
+ if obj.is_a?(ObjectStream)
+ obj.each do |subobj|
+ build.call(subobj, revision)
+ end
+ end
- # remove encrypt dictionary if requested
- if options[:decrypt]
- delete_object(get_trailer_info[:Encrypt])
- get_trailer_info[:Encrypt] = nil
- end
-
- self
- end
-
- end
+ obj.pre_build
- #
- # Module for encrypted PDF objects.
- #
- module EncryptedObject #:nodoc
+ case obj
+ 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)
- attr_writer :encryption_key
- attr_writer :algorithm
- attr_writer :encryption_handler
- attr_accessor :decrypted
+ unless obj.is_a?(EncryptedString)
+ obj.extend(EncryptedString)
+ obj.decrypted = true
+ end
+ end
- def self.extended(obj)
- obj.decrypted = false
- end
+ when Stream
+ return if obj.is_a?(XRefStream)
+ return if obj.equal?(self.Catalog.Metadata) and not @encryption_handler.EncryptMetadata
- def post_build
- encrypt!
+ unless obj.is_a?(EncryptedStream)
+ obj.extend(EncryptedStream)
+ obj.decrypted = true
+ end
- super
- 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
- private
+ obj.each do |subobj|
+ build.call(subobj, revision)
+ end
+ end
- def compute_object_key
- if @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
- k << "sAlT" if @algorithm == Encryption::AES
-
- Digest::MD5.digest(k)[0, key_len]
- else
- @encryption_key
+ obj.post_build
+ end
+
+ # stack up every root objects
+ indirect_objects_by_rev.each do |obj, revision|
+ build.call(obj, revision)
+ end
+
+ # remove encrypt dictionary if requested
+ if options[:decrypt]
+ delete_object(get_trailer_info[:Encrypt])
+ get_trailer_info[:Encrypt] = nil
+ end
+
+ self
+ end
end
- end
- end
+ #
+ # Module for encrypted PDF objects.
+ #
+ module EncryptedObject #:nodoc
+ attr_accessor :decrypted
- #
- # Module for encrypted String.
- #
- module EncryptedString
- include EncryptedObject
+ def post_build
+ encrypt!
- def encrypt!
- if @decrypted
- key = compute_object_key
-
- encrypted_data =
- if @algorithm == ARC4 or @algorithm == Identity
- @algorithm.encrypt(key, self.value)
- else
- iv = Encryption.rand_bytes(AES::BLOCKSIZE)
- @algorithm.encrypt(key, iv, self.value)
- end
+ super
+ end
- @decrypted = false
+ private
- self.replace(encrypted_data)
- self.freeze
- end
-
- self
- end
+ def compute_object_key(cipher)
+ doc = self.document
+ raise EncryptionError, "Document is not encrypted" unless doc.is_a?(EncryptedDocument)
- def decrypt!
- unless @decrypted
- key = compute_object_key
- self.replace(@algorithm.decrypt(key, self.to_str))
- @decrypted = true
+ encryption_key = doc.encryption_key
+
+ 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
+ k << "sAlT" if cipher == Encryption::AES
+
+ Digest::MD5.digest(k)[0, key_len]
+ else
+ encryption_key
+ end
+ end
end
- self
- end
- end
+ #
+ # Module for encrypted String.
+ #
+ module EncryptedString
+ include EncryptedObject
- #
- # Module for encrypted Stream.
- #
- module EncryptedStream
- include EncryptedObject
+ def self.extended(obj)
+ obj.decrypted = false
+ end
- def encrypt!
- if @decrypted
- encode!
+ def encrypt!
+ return self unless @decrypted
- key = compute_object_key
+ cipher = self.document.string_encryption_cipher
+ raise EncryptionError, "Cannot find string encryption filter" if cipher.nil?
- @rawdata =
- if @algorithm == ARC4 or @algorithm == Identity
- @algorithm.encrypt(key, self.rawdata)
- else
- iv = Encryption.rand_bytes(AES::BLOCKSIZE)
- @algorithm.encrypt(key, iv, @rawdata)
+ key = compute_object_key(cipher)
+
+ encrypted_data =
+ if cipher == RC4 or cipher == Identity
+ cipher.encrypt(key, self.value)
+ else
+ iv = Encryption.rand_bytes(AES::BLOCKSIZE)
+ cipher.encrypt(key, iv, self.value)
+ end
+
+ @decrypted = false
+
+ self.replace(encrypted_data)
+ self.freeze
+
+ self
end
- @decrypted = false
+ def decrypt!
+ return self if @decrypted
- @rawdata.freeze
- self.freeze
- end
+ cipher = self.document.string_encryption_cipher
+ raise EncryptionError, "Cannot find string encryption filter" if cipher.nil?
- self
- end
+ key = compute_object_key(cipher)
- def decrypt!
- unless @decrypted
- key = compute_object_key
+ self.replace(cipher.decrypt(key, self.to_str))
+ @decrypted = true
- self.rawdata = @algorithm.decrypt(key, @rawdata)
- @decrypted = true
+ self
+ end
end
- self
- end
+ #
+ # Module for encrypted Stream.
+ #
+ module EncryptedStream
+ include EncryptedObject
- end
+ def self.extended(obj)
+ obj.decrypted = false
+ end
- #
- # Identity transformation.
- #
- module Identity
- def Identity.encrypt(key, data)
- data
- end
+ def encrypt!
+ return self unless @decrypted
- def Identity.decrypt(key, data)
- data
- end
- end
+ encode!
- #
- # Pure Ruby implementation of the aRC4 symmetric algorithm
- #
- class ARC4
-
- #
- # Encrypts data using the given key
- #
- def ARC4.encrypt(key, data)
- ARC4.new(key).encrypt(data)
- end
-
- #
- # Decrypts data using the given key
- #
- def ARC4.decrypt(key, data)
- ARC4.new(key).decrypt(data)
- end
-
- #
- # Creates and initialises a new aRC4 generator using given key
- #
- def initialize(key)
- if Origami::OPTIONS[:use_openssl]
- @key = key
- else
- @state = init(key)
- end
- end
-
- #
- # Encrypt/decrypt data with the aRC4 encryption algorithm
- #
- def cipher(data)
- return "" if data.empty?
-
- if Origami::OPTIONS[:use_openssl]
- rc4 = OpenSSL::Cipher::RC4.new.encrypt
- rc4.key_len = @key.length
- rc4.key = @key
+ if self.filters.first == :Crypt
+ params = decode_params.first
- 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
- 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
+ if params.is_a?(Dictionary) and params.Name.is_a?(Name)
+ crypt_filter = params.Name.value
+ else
+ crypt_filter = :Identity
+ end
- 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?
- #
- # Pure Ruby implementation of the AES symmetric algorithm.
- # Using mode CBC.
- #
- class AES
-
- NROWS = 4
- NCOLS = 4
- BLOCKSIZE = NROWS * NCOLS
+ key = compute_object_key(cipher)
- ROUNDS =
- {
- 16 => 10,
- 24 => 12,
- 32 => 14
- }
+ @encoded_data =
+ if cipher == RC4 or cipher == Identity
+ cipher.encrypt(key, self.encoded_data)
+ else
+ iv = Encryption.rand_bytes(AES::BLOCKSIZE)
+ cipher.encrypt(key, iv, @encoded_data)
+ end
- #
- # 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
- ]
+ @decrypted = false
- #
- # 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
- ]
+ @encoded_data.freeze
+ self.freeze
- 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
- ]
+ self
+ end
- attr_writer :iv
+ def decrypt!
+ return self if @decrypted
- def AES.encrypt(key, iv, data)
- AES.new(key, iv).encrypt(data)
- end
+ if self.filters.first == :Crypt
+ params = decode_params.first
- 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
- raise EncryptionError, "Key must have a length of 128, 192 or 256 bits."
- end
+ if params.is_a?(Dictionary) and params.Name.is_a?(Name)
+ crypt_filter = params.Name.value
+ else
+ crypt_filter = :Identity
+ end
- if not iv.nil? and iv.size != BLOCKSIZE
- raise EncryptionError, "Initialization vector must have a length of #{BLOCKSIZE} bytes."
- 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?
- @key = key
- @iv = iv
- @use_padding = use_padding
- end
+ key = compute_object_key(cipher)
- def encrypt(data)
+ self.encoded_data = cipher.decrypt(key, @encoded_data)
+ @decrypted = true
- if @iv.nil?
- raise EncryptionError, "No initialization vector has been set."
+ self
+ end
end
-
- if @use_padding
- padlen = BLOCKSIZE - (data.size % BLOCKSIZE)
- data << (padlen.chr * padlen)
+
+ #
+ # Identity transformation.
+ #
+ module Identity
+ def Identity.encrypt(key, data)
+ data
+ end
+
+ def Identity.decrypt(key, data)
+ data
+ end
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
+ #
+ # Pure Ruby implementation of the RC4 symmetric algorithm
+ #
+ class RC4
- @iv + aes.update(data) + aes.final
- else
- cipher = []
- cipherblock = []
- nblocks = data.size / BLOCKSIZE
+ #
+ # Encrypts data using the given key
+ #
+ def RC4.encrypt(key, data)
+ RC4.new(key).encrypt(data)
+ end
- first_round = true
- nblocks.times do |n|
- plainblock = data[n * BLOCKSIZE, BLOCKSIZE].unpack("C*")
+ #
+ # Decrypts data using the given key
+ #
+ def RC4.decrypt(key, data)
+ RC4.new(key).decrypt(data)
+ end
- if first_round
- BLOCKSIZE.times do |i| plainblock[i] ^= @iv[i].ord end
- else
- BLOCKSIZE.times do |i| plainblock[i] ^= cipherblock[i] end
+ #
+ # 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
end
- first_round = false
- cipherblock = aesEncrypt(plainblock)
- cipher.concat(cipherblock)
- end
+ #
+ # Encrypt/decrypt data with the RC4 encryption algorithm
+ #
+ def cipher(data)
+ return "" if data.empty?
- @iv + cipher.pack("C*")
- end
- end
+ if Origami::OPTIONS[:use_openssl]
+ rc4 = OpenSSL::Cipher::RC4.new.encrypt
+ rc4.key_len = @key.length
+ rc4.key = @key
- def decrypt(data)
- unless data.size % BLOCKSIZE == 0
- raise EncryptionError,
- "Data must be 16-bytes padded (data size = #{data.size} bytes)"
+ 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
+ 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
- @iv = data.slice!(0, BLOCKSIZE)
+ #
+ # Pure Ruby implementation of the AES symmetric algorithm.
+ # Using mode CBC.
+ #
+ class AES
+ NROWS = 4
+ NCOLS = 4
+ BLOCKSIZE = NROWS * NCOLS
- 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
+ ROUNDS =
+ {
+ 16 => 10,
+ 24 => 12,
+ 32 => 14
+ }
- plain = (aes.update(data) + aes.final).unpack("C*")
- else
- plain = []
- plainblock = []
- prev_cipherblock = []
- nblocks = data.size / BLOCKSIZE
+ #
+ # 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
+ ]
- first_round = true
- nblocks.times do |n|
- cipherblock = data[n * BLOCKSIZE, BLOCKSIZE].unpack("C*")
+ #
+ # 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
+ ]
- plainblock = aesDecrypt(cipherblock)
+ 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
+ ]
- if first_round
- BLOCKSIZE.times do |i| plainblock[i] ^= @iv[i].ord end
- else
- BLOCKSIZE.times do |i| plainblock[i] ^= prev_cipherblock[i] end
+ attr_writer :iv
+
+ def AES.encrypt(key, iv, data)
+ AES.new(key, iv).encrypt(data)
end
- first_round = false
- prev_cipherblock = cipherblock
- plain.concat(plainblock)
- end
- end
+ def AES.decrypt(key, data)
+ AES.new(key, nil).decrypt(data)
+ end
- if @use_padding
- padlen = plain[-1]
- unless (1..16) === padlen
- raise EncryptionError, "Incorrect padding length : #{padlen}"
- end
+ def initialize(key, iv, use_padding = true)
+ unless key.size == 16 or key.size == 24 or key.size == 32
+ raise EncryptionError, "Key must have a length of 128, 192 or 256 bits."
+ end
- padlen.times do
- pad = plain.pop
- raise EncryptionError,
- "Incorrect padding byte : 0x#{pad.to_s 16}" if pad != padlen
- end
- end
+ if not iv.nil? and iv.size != BLOCKSIZE
+ raise EncryptionError, "Initialization vector must have a length of #{BLOCKSIZE} bytes."
+ end
- plain.pack("C*")
- end
+ @key = key
+ @iv = iv
+ @use_padding = use_padding
+ end
- private
+ def encrypt(data)
+ if @iv.nil?
+ raise EncryptionError, "No initialization vector has been set."
+ end
- def rol(row, n = 1) #:nodoc
- n.times do row.push row.shift end ; row
- end
+ if @use_padding
+ padlen = BLOCKSIZE - (data.size % BLOCKSIZE)
+ data << (padlen.chr * padlen)
+ end
- def ror(row, n = 1) #:nodoc:
- n.times do row.unshift row.pop end ; row
- 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
- def galoisMult(a, b) #:nodoc:
- p = 0
+ @iv + aes.update(data) + aes.final
+ else
+ cipher = []
+ cipherblock = []
+ nblocks = data.size / BLOCKSIZE
- 8.times do
- p ^= a if b[0] == 1
- highBit = a[7]
- a <<= 1
- a ^= 0x1b if highBit == 1
- b >>= 1
- end
+ first_round = true
+ nblocks.times do |n|
+ plainblock = data[n * BLOCKSIZE, BLOCKSIZE].unpack("C*")
- p % 256
- end
+ if first_round
+ BLOCKSIZE.times do |i| plainblock[i] ^= @iv[i].ord end
+ else
+ BLOCKSIZE.times do |i| plainblock[i] ^= cipherblock[i] end
+ end
- def scheduleCore(word, iter) #:nodoc:
- rol(word)
- word.map! do |byte| SBOX[byte] end
- word[0] ^= RCON[iter]
+ first_round = false
+ cipherblock = aes_encrypt(plainblock)
+ cipher.concat(cipherblock)
+ end
- word
- end
+ @iv + cipher.pack("C*")
+ end
+ end
- def transpose(m) #:nodoc:
- [
- m[NROWS * 0, NROWS],
- m[NROWS * 1, NROWS],
- m[NROWS * 2, NROWS],
- m[NROWS * 3, NROWS]
- ].transpose.flatten
- end
+ def decrypt(data)
+ unless data.size % BLOCKSIZE == 0
+ raise EncryptionError, "Data must be 16-bytes padded (data size = #{data.size} bytes)"
+ end
- #
- # AES round methods.
- #
+ @iv = data.slice!(0, BLOCKSIZE)
- def createRoundKey(expandedKey, round = 0) #:nodoc:
- transpose(expandedKey[round * BLOCKSIZE, BLOCKSIZE])
- end
+ 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
- def addRoundKey(roundKey) #:nodoc:
- BLOCKSIZE.times do |i| @state[i] ^= roundKey[i] end
- end
+ plain = (aes.update(data) + aes.final).unpack("C*")
+ else
+ plain = []
+ plainblock = []
+ prev_cipherblock = []
+ nblocks = data.size / BLOCKSIZE
- def subBytes #:nodoc:
- BLOCKSIZE.times do |i| @state[i] = SBOX[ @state[i] ] end
- end
+ first_round = true
+ nblocks.times do |n|
+ cipherblock = data[n * BLOCKSIZE, BLOCKSIZE].unpack("C*")
- def rsubBytes #:nodoc:
- BLOCKSIZE.times do |i| @state[i] = RSBOX[ @state[i] ] end
- end
+ plainblock = aes_decrypt(cipherblock)
- def shiftRows #:nodoc:
- NROWS.times do |i|
- @state[i * NCOLS, NCOLS] = rol(@state[i * NCOLS, NCOLS], i)
- end
- end
+ 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
- def rshiftRows #:nodoc:
- NROWS.times do |i|
- @state[i * NCOLS, NCOLS] = ror(@state[i * NCOLS, NCOLS], i)
- end
- end
+ first_round = false
+ prev_cipherblock = cipherblock
+ plain.concat(plainblock)
+ end
+ end
- def mixColumnWithField(column, field) #:nodoc:
- p = field
-
- column[0], column[1], column[2], column[3] =
- galoisMult(column[0], p[0]) ^ galoisMult(column[3], p[1]) ^ galoisMult(column[2], p[2]) ^ galoisMult(column[1], p[3]),
- galoisMult(column[1], p[0]) ^ galoisMult(column[0], p[1]) ^ galoisMult(column[3], p[2]) ^ galoisMult(column[2], p[3]),
- galoisMult(column[2], p[0]) ^ galoisMult(column[1], p[1]) ^ galoisMult(column[0], p[2]) ^ galoisMult(column[3], p[3]),
- galoisMult(column[3], p[0]) ^ galoisMult(column[2], p[1]) ^ galoisMult(column[1], p[2]) ^ galoisMult(column[0], p[3])
- end
+ if @use_padding
+ padlen = plain[-1]
+ unless (1..16) === padlen
+ raise EncryptionError, "Incorrect padding length : #{padlen}"
+ end
- def mixColumn(column) #:nodoc:
- mixColumnWithField(column, [ 2, 1, 1, 3 ])
- end
+ padlen.times do
+ pad = plain.pop
+ raise EncryptionError, "Incorrect padding byte : 0x#{pad.to_s 16}" if pad != padlen
+ end
+ end
- def rmixColumn(column) #:nodoc:
- mixColumnWithField(column, [ 14, 9, 13, 11 ])
- end
+ plain.pack("C*")
+ end
- def mixColumns #:nodoc:
- NCOLS.times do |c|
- column = []
- NROWS.times do |r| column << @state[c + r * NCOLS] end
- mixColumn(column)
- NROWS.times do |r| @state[c + r * NCOLS] = column[r] end
- end
- end
+ private
- def rmixColumns #:nodoc:
- NCOLS.times do |c|
- column = []
- NROWS.times do |r| column << @state[c + r * NCOLS] end
- rmixColumn(column)
- NROWS.times do |r| @state[c + r * NCOLS] = column[r] end
- end
- end
+ def rol(row, n = 1) #:nodoc
+ n.times do row.push row.shift end ; row
+ end
- def expandKey(key) #:nodoc:
+ def ror(row, n = 1) #:nodoc:
+ n.times do row.unshift row.pop end ; row
+ end
- key = key.unpack("C*")
- size = key.size
- expandedSize = 16 * (ROUNDS[key.size] + 1)
- rconIter = 1
- expandedKey = key[0, size]
+ def galois_mult(a, b) #:nodoc:
+ p = 0
- while expandedKey.size < expandedSize
- temp = expandedKey[-4, 4]
+ 8.times do
+ p ^= a if b[0] == 1
+ highBit = a[7]
+ a <<= 1
+ a ^= 0x1b if highBit == 1
+ b >>= 1
+ end
- if expandedKey.size % size == 0
- scheduleCore(temp, rconIter)
- rconIter = rconIter.succ
- end
+ p % 256
+ end
- temp.map! do |b| SBOX[b] end if size == 32 and expandedKey.size % size == 16
+ def schedule_core(word, iter) #:nodoc:
+ rol(word)
+ word.map! do |byte| SBOX[byte] end
+ word[0] ^= RCON[iter]
- temp.each do |b| expandedKey << (expandedKey[-size] ^ b) end
- end
+ word
+ end
- expandedKey
- end
+ def transpose(m) #:nodoc:
+ [
+ m[NROWS * 0, NROWS],
+ m[NROWS * 1, NROWS],
+ m[NROWS * 2, NROWS],
+ m[NROWS * 3, NROWS]
+ ].transpose.flatten
+ end
- def aesRound(roundKey) #:nodoc:
- subBytes
- #puts "after subBytes: #{@state.inspect}"
- shiftRows
- #puts "after shiftRows: #{@state.inspect}"
- mixColumns
- #puts "after mixColumns: #{@state.inspect}"
- addRoundKey(roundKey)
- #puts "roundKey = #{roundKey.inspect}"
- #puts "after addRoundKey: #{@state.inspect}"
- end
+ #
+ # AES round methods.
+ #
- def raesRound(roundKey) #:nodoc:
- addRoundKey(roundKey)
- rmixColumns
- rshiftRows
- rsubBytes
- end
+ def create_round_key(expanded_key, round = 0) #:nodoc:
+ transpose(expanded_key[round * BLOCKSIZE, BLOCKSIZE])
+ end
- def aesEncrypt(block) #:nodoc:
- @state = transpose(block)
- expandedKey = expandKey(@key)
- rounds = ROUNDS[@key.size]
+ def add_round_key(roundKey) #:nodoc:
+ BLOCKSIZE.times do |i| @state[i] ^= roundKey[i] end
+ end
- aesMain(expandedKey, rounds)
- end
+ def sub_bytes #:nodoc:
+ BLOCKSIZE.times do |i| @state[i] = SBOX[ @state[i] ] end
+ end
- def aesDecrypt(block) #:nodoc:
- @state = transpose(block)
- expandedKey = expandKey(@key)
- rounds = ROUNDS[@key.size]
+ def r_sub_bytes #:nodoc:
+ BLOCKSIZE.times do |i| @state[i] = RSBOX[ @state[i] ] end
+ end
- raesMain(expandedKey, rounds)
- end
+ def shift_rows #:nodoc:
+ NROWS.times do |i|
+ @state[i * NCOLS, NCOLS] = rol(@state[i * NCOLS, NCOLS], i)
+ end
+ end
- def aesMain(expandedKey, rounds) #:nodoc:
- #puts "expandedKey: #{expandedKey.inspect}"
- roundKey = createRoundKey(expandedKey)
- addRoundKey(roundKey)
+ def r_shift_rows #:nodoc:
+ NROWS.times do |i|
+ @state[i * NCOLS, NCOLS] = ror(@state[i * NCOLS, NCOLS], i)
+ end
+ end
- for i in 1..rounds-1
- roundKey = createRoundKey(expandedKey, i)
- aesRound(roundKey)
- end
+ def mix_column_with_field(column, field) #:nodoc:
+ p = field
- roundKey = createRoundKey(expandedKey, rounds)
- subBytes
- shiftRows
- addRoundKey(roundKey)
+ 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]),
- transpose(@state)
- end
+ galois_mult(column[1], p[0]) ^
+ galois_mult(column[0], p[1]) ^
+ galois_mult(column[3], p[2]) ^
+ galois_mult(column[2], p[3]),
- def raesMain(expandedKey, rounds) #:nodoc:
-
- roundKey = createRoundKey(expandedKey, rounds)
- addRoundKey(roundKey)
- rshiftRows
- rsubBytes
-
- (rounds - 1).downto(1) do |i|
- roundKey = createRoundKey(expandedKey, i)
- raesRound(roundKey)
- end
+ galois_mult(column[2], p[0]) ^
+ galois_mult(column[1], p[1]) ^
+ galois_mult(column[0], p[2]) ^
+ galois_mult(column[3], p[3]),
- roundKey = createRoundKey(expandedKey)
- addRoundKey(roundKey)
+ 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
- transpose(@state)
- end
+ def mix_column(column) #:nodoc:
+ mix_column_with_field(column, [ 2, 1, 1, 3 ])
+ end
- end
-
- #
- # Class representing a crypt filter Dictionary
- #
- class CryptFilterDictionary < Dictionary
- include StandardObject
+ def r_mix_column_(column) #:nodoc:
+ mix_column_with_field(column, [ 14, 9, 13, 11 ])
+ end
- field :Type, :Type => Name, :Default => :CryptFilter
- field :CFM, :Type => Name, :Default => :None
- field :AuthEvent, :Type => Name, :Default => :DocOpen
- field :Length, :Type => Integer
- 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
- #
- # Common class for encryption dictionaries.
- #
- class EncryptionDictionary < Dictionary
- include StandardObject
+ 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
- field :Filter, :Type => Name, :Default => :Standard, :Required => true
- field :SubFilter, :Type => Name, :Version => "1.3"
- field :V, :Type => Integer, :Default => 0
- field :Length, :Type => Integer, :Default => 40, :Version => "1.4"
- field :CF, :Type => Dictionary, :Version => "1.5"
- field :StmF, :Type => Name, :Default => :Identity, :Version => "1.5"
- field :StrF, :Type => Name, :Default => :Identity, :Version => "1.5"
- field :EFF, :Type => Name, :Version => "1.6"
- end
-
- #
- # 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.9'
-
- #
- # Permission constants for encrypted documents.
- #
- module Permissions
- RESERVED = 1 << 6 | 1 << 7 | 0xFFFFF000
- PRINT = 1 << 2 | RESERVED
- MODIFY_CONTENTS = 1 << 3 | RESERVED
- COPY_CONTENTS = 1 << 4 | RESERVED
- MODIFY_ANNOTATIONS = 1 << 5 | RESERVED
- FILLIN_FORMS = 1 << 8 | RESERVED
- EXTRACT_CONTENTS = 1 << 9 | RESERVED
- ASSEMBLE_DOC = 1 << 10 | RESERVED
- HIGH_QUALITY_PRINT = 1 << 11 | RESERVED
-
- ALL = PRINT | MODIFY_CONTENTS | COPY_CONTENTS | MODIFY_ANNOTATIONS | FILLIN_FORMS | EXTRACT_CONTENTS | ASSEMBLE_DOC | HIGH_QUALITY_PRINT
- end
-
- #
- # Class defining a standard encryption dictionary.
- #
- class Dictionary < EncryptionDictionary
-
- field :R, :Type => Number, :Required => true
- field :O, :Type => String, :Required => true
- field :U, :Type => String, :Required => true
- field :OE, :Type => String, :Version => '1.7', :ExtensionLevel => 3
- field :UE, :Type => String, :Version => '1.7', :ExtensionLevel => 3
- field :Perms, :Type => String, :Version => '1.7', :ExtensionLevel => 3
- field :P, :Type => Integer, :Default => 0, :Required => true
- field :EncryptMetadata, :Type => Boolean, :Default => true, :Version => "1.5"
+ 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]
- def pdf_version_required #:nodoc:
- if self.R > 5
- [ 1.7, 8 ]
- else
- super
- end
- end
-
- #
- # Computes the key that will be used to encrypt/decrypt the document contents with user password.
- #
- def compute_user_encryption_key(userpassword, fileid)
-
- if self.R < 5
- padded = pad_password(userpassword)
- padded.force_encoding('binary') if RUBY_VERSION >= '1.9'
+ while expanded_key.size < expanded_size
+ temp = expanded_key[-4, 4]
- padded << self.O
- padded << [ self.P ].pack("i")
-
- padded << fileid
-
- encrypt_metadata = self.EncryptMetadata != false
- padded << [ -1 ].pack("i") if self.R >= 4 and not encrypt_metadata
+ if expanded_key.size % size == 0
+ schedule_core(temp, rcon_iter)
+ rcon_iter = rcon_iter.succ
+ end
- key = Digest::MD5.digest(padded)
+ temp.map! do |b| SBOX[b] end if size == 32 and expanded_key.size % size == 16
- 50.times { key = Digest::MD5.digest(key[0, self.Length / 8]) } if self.R >= 3
+ temp.each do |b| expanded_key << (expanded_key[-size] ^ b) end
+ end
- if self.R == 2
- key[0, 5]
- elsif self.R >= 3
- key[0, self.Length / 8]
+ expanded_key
end
- else
- passwd = password_to_utf8(userpassword)
-
- uks = self.U[40, 8]
-
- if self.R == 5
- ukey = Digest::SHA256.digest(passwd + uks)
- else
- ukey = compute_hardened_hash(passwd, uks)
+
+ 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
-
- iv = ::Array.new(AES::BLOCKSIZE, 0).pack("C*")
- AES.new(ukey, nil, false).decrypt(iv + self.UE.value)
- end
- 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 r_aes_round(round_key) #:nodoc:
+ add_round_key(round_key)
+ r_mix_columns
+ r_shift_rows
+ r_sub_bytes
+ end
- oks = self.O[40, 8]
+ def aes_encrypt(block) #:nodoc:
+ @state = transpose(block)
+ expanded_key = expand_key(@key)
+ rounds = ROUNDS[@key.size]
- if self.R == 5
- okey = Digest::SHA256.digest(passwd + oks + self.U)
- else
- okey = compute_hardened_hash(passwd, oks, self.U)
+ aes_main(expanded_key, rounds)
end
- iv = ::Array.new(AES::BLOCKSIZE, 0).pack("C*")
- AES.new(okey, nil, false).decrypt(iv + self.OE.value)
- end
- end
+ def aes_decrypt(block) #:nodoc:
+ @state = transpose(block)
+ expanded_key = expand_key(@key)
+ rounds = ROUNDS[@key.size]
- #
- # Set up document passwords.
- #
- def set_passwords(ownerpassword, userpassword, salt = nil)
- if self.R < 5
- key = compute_owner_key(ownerpassword)
- upadded = pad_password(userpassword)
-
- owner_key = ARC4.encrypt(key, upadded)
- 19.times { |i| owner_key = ARC4.encrypt(xor(key,i+1), owner_key) } if self.R >= 3
-
- self.O = owner_key
- self.U = compute_user_password(userpassword, salt)
-
- else
- upass = password_to_utf8(userpassword)
- opass = password_to_utf8(ownerpassword)
+ r_aes_main(expanded_key, rounds)
+ 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*")
-
- 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)
+ 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
- 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]
+ 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
- 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
+ (rounds - 1).downto(1) do |i|
+ round_key = create_round_key(expanded_key, i)
+ r_aes_round(round_key)
+ end
- self.Perms = AES.new(file_key, iv, false).encrypt(perms)[iv.size, 16]
+ round_key = create_round_key(expanded_key)
+ add_round_key(round_key)
- file_key
- end
+ transpose(@state)
+ end
end
-
- #
- # Checks user password.
- # 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
- elsif self.R == 3 or self.R == 4
- compute_user_password(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]
- compute_hardened_hash(password_to_utf8(pass), uvs) == self.U[0, 32]
- end
- end
-
- #
- # Checks owner password.
- # For version 2,3 and 4, _salt_ is the document ID.
- # For version 5, _salt_ is (Owner Key Salt + U)
- #
- def is_owner_password?(pass, salt)
-
- if self.R < 5
- user_password = retrieve_user_password(pass)
- is_user_password?(user_password, salt)
- elsif self.R == 5
- ovs = self.O[32, 8]
- Digest::SHA256.digest(password_to_utf8(pass) + ovs + self.U) == self.O[0, 32]
- elsif self.R == 6
- ovs = self.O[32, 8]
- compute_hardened_hash(password_to_utf8(pass), ovs, self.U[0,48]) == self.O[0, 32]
- end
- end
#
- # Retrieve user password from owner password.
- # Cannot be used with revision 5.
+ # Class representing a crypt filter Dictionary
#
- def retrieve_user_password(ownerpassword)
- key = compute_owner_key(ownerpassword)
+ class CryptFilterDictionary < Dictionary
+ include StandardObject
- if self.R == 2
- ARC4.decrypt(key, self.O)
- elsif self.R == 3 or self.R == 4
- user_password = ARC4.decrypt(xor(key, 19), self.O)
- 19.times { |i| user_password = ARC4.decrypt(xor(key, 18-i), user_password) }
-
- user_password
- end
+ field :Type, :Type => Name, :Default => :CryptFilter
+ field :CFM, :Type => Name, :Default => :None
+ field :AuthEvent, :Type => Name, :Default => :DocOpen
+ field :Length, :Type => Integer
end
-
- private
#
- # Used to encrypt/decrypt the O field.
- # Rev 2,3,4: O = crypt(user_pass, owner_key).
- # Rev 5: unused.
+ # Common class for encryption dictionaries.
#
- def compute_owner_key(ownerpassword) #:nodoc:
+ class EncryptionDictionary < Dictionary
+ include StandardObject
- opadded = pad_password(ownerpassword)
-
- hash = Digest::MD5.digest(opadded)
- 50.times { hash = Digest::MD5.digest(hash) } if self.R >= 3
-
- if self.R == 2
- hash[0, 5]
- elsif self.R >= 3
- hash[0, self.Length / 8]
- end
+ field :Filter, :Type => Name, :Default => :Standard, :Required => true
+ field :SubFilter, :Type => Name, :Version => "1.3"
+ field :V, :Type => Integer, :Default => 0
+ field :Length, :Type => Integer, :Default => 40, :Version => "1.4"
+ field :CF, :Type => Dictionary, :Version => "1.5"
+ field :StmF, :Type => Name, :Default => :Identity, :Version => "1.5"
+ field :StrF, :Type => Name, :Default => :Identity, :Version => "1.5"
+ field :EFF, :Type => Name, :Version => "1.6"
end
-
- #
- # Compute the value of the U field.
- # Cannot be used with revision 5.
- #
- def compute_user_password(userpassword, salt) #:nodoc:
-
- if self.R == 2
- key = compute_user_encryption_key(userpassword, salt)
- user_key = ARC4.encrypt(key, PADDING)
- elsif self.R == 3 or self.R == 4
- key = compute_user_encryption_key(userpassword, salt)
-
- upadded = PADDING + salt
- hash = Digest::MD5.digest(upadded)
-
- user_key = ARC4.encrypt(key, hash)
-
- 19.times { |i| user_key = ARC4.encrypt(xor(key,i+1), user_key) }
-
- user_key.ljust(32, 0xFF.chr)
- end
- end
#
- # Computes hardened hash used in revision 6 (extension level 8).
+ # The standard security handler for PDF encryption.
#
- def compute_hardened_hash(password, salt, vector = '')
- block_size = 32
- input = Digest::SHA256.digest(password + salt + vector) + "\x00" * 32
- key = input[0, 16]
- iv = input[16, 16]
- digest, aes, h, x = nil, nil, nil, nil
+ 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".b #:nodoc:
- i = 0
- while i < 64 or i < x[-1].ord + 32
- j = 0
- block = input[0, block_size]
+ #
+ # Permission constants for encrypted documents.
+ #
+ module Permissions
+ RESERVED = 1 << 6 | 1 << 7 | 0xFFFFF000
+ PRINT = 1 << 2 | RESERVED
+ MODIFY_CONTENTS = 1 << 3 | RESERVED
+ COPY_CONTENTS = 1 << 4 | RESERVED
+ MODIFY_ANNOTATIONS = 1 << 5 | RESERVED
+ FILLIN_FORMS = 1 << 8 | RESERVED
+ EXTRACT_CONTENTS = 1 << 9 | RESERVED
+ ASSEMBLE_DOC = 1 << 10 | RESERVED
+ HIGH_QUALITY_PRINT = 1 << 11 | RESERVED
- 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"
+ ALL = PRINT | MODIFY_CONTENTS | COPY_CONTENTS |
+ MODIFY_ANNOTATIONS | FILLIN_FORMS | EXTRACT_CONTENTS |
+ ASSEMBLE_DOC | HIGH_QUALITY_PRINT
end
- 64.times do |j|
- x = ''
- x += aes.update(password) unless password.empty?
- x += aes.update(block)
- x += aes.update(vector) unless vector.empty?
+ #
+ # Class defining a standard encryption dictionary.
+ #
+ class Dictionary < EncryptionDictionary
- if j == 0
- block_size = 32 + (x.unpack("C16").inject(0) {|a,b| a+b} % 3) * 16
- digest = Digest::SHA2.new(block_size << 3)
- end
+ field :R, :Type => Number, :Required => true
+ field :O, :Type => String, :Required => true
+ field :U, :Type => String, :Required => true
+ field :OE, :Type => String, :Version => '1.7', :ExtensionLevel => 3
+ field :UE, :Type => String, :Version => '1.7', :ExtensionLevel => 3
+ field :Perms, :Type => String, :Version => '1.7', :ExtensionLevel => 3
+ field :P, :Type => Integer, :Default => 0, :Required => true
+ field :EncryptMetadata, :Type => Boolean, :Default => true, :Version => "1.5"
- digest.update(x)
- end
+ def version_required #:nodoc:
+ if self.R > 5
+ [ 1.7, 8 ]
+ else
+ super
+ end
+ end
- h = digest.digest
- key = h[0, 16]
- input[0, block_size] = h[0, block_size]
- iv = h[16, 16]
+ #
+ # Computes the key that will be used to encrypt/decrypt the document contents with user password.
+ #
+ def compute_user_encryption_key(userpassword, fileid)
+ if self.R < 5
+ padded = pad_password(userpassword)
+ padded.force_encoding('binary')
- i = i + 1
- end
+ padded << self.O
+ padded << [ self.P ].pack("i")
- h[0, 32]
- end
-
- def xor(str, byte) #:nodoc:
- str.split(//).map!{|c| (c[0].ord ^ byte).chr }.join
- end
-
- def pad_password(password) #:nodoc:
- return PADDING.dup if password.empty? # Fix for Ruby 1.9 bug
- password[0,32].ljust(32, PADDING)
- end
+ padded << fileid
- def password_to_utf8(passwd) #:nodoc:
- Origami::ByteString.new(passwd).to_utf8[0, 127]
+ encrypt_metadata = self.EncryptMetadata != false
+ 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
+
+ if self.R == 2
+ key[0, 5]
+ elsif self.R >= 3
+ key[0, self.Length / 8]
+ end
+ else
+ passwd = password_to_utf8(userpassword)
+
+ uks = self.U[40, 8]
+
+ if self.R == 5
+ ukey = Digest::SHA256.digest(passwd + uks)
+ else
+ ukey = compute_hardened_hash(passwd, uks)
+ end
+
+ iv = ::Array.new(AES::BLOCKSIZE, 0).pack("C*")
+ AES.new(ukey, nil, false).decrypt(iv + self.UE.value)
+ end
+ 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)
+
+ 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)
+ end
+ 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)
+
+ owner_key = RC4.encrypt(key, upadded)
+ 19.times { |i| owner_key = RC4.encrypt(xor(key,i+1), owner_key) } if self.R >= 3
+
+ self.O = owner_key
+ self.U = compute_user_password(userpassword, salt)
+
+ else
+ upass = password_to_utf8(userpassword)
+ opass = password_to_utf8(ownerpassword)
+
+ 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
+ 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
+
+ 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]
+
+ 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.Perms = AES.new(file_key, iv, false).encrypt(perms)[iv.size, 16]
+
+ file_key
+ end
+ end
+
+ #
+ # Checks user password.
+ # 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
+ elsif self.R == 3 or self.R == 4
+ compute_user_password(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]
+ compute_hardened_hash(password_to_utf8(pass), uvs) == self.U[0, 32]
+ end
+ end
+
+ #
+ # Checks owner password.
+ # For version 2,3 and 4, _salt_ is the document ID.
+ # For version 5, _salt_ is (Owner Key Salt + U)
+ #
+ def is_owner_password?(pass, salt)
+
+ if self.R < 5
+ user_password = retrieve_user_password(pass)
+ is_user_password?(user_password, salt)
+ elsif self.R == 5
+ ovs = self.O[32, 8]
+ Digest::SHA256.digest(password_to_utf8(pass) + ovs + self.U) == self.O[0, 32]
+ elsif self.R == 6
+ ovs = self.O[32, 8]
+ compute_hardened_hash(password_to_utf8(pass), ovs, self.U[0,48]) == self.O[0, 32]
+ end
+ end
+
+ #
+ # Retrieve user password from owner password.
+ # Cannot be used with revision 5.
+ #
+ def retrieve_user_password(ownerpassword)
+
+ key = compute_owner_key(ownerpassword)
+
+ 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)
+ 19.times { |i| user_password = RC4.decrypt(xor(key, 18-i), user_password) }
+
+ user_password
+ end
+ end
+
+ private
+
+ #
+ # 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:
+
+ opadded = pad_password(ownerpassword)
+
+ hash = Digest::MD5.digest(opadded)
+ 50.times { hash = Digest::MD5.digest(hash) } if self.R >= 3
+
+ if self.R == 2
+ hash[0, 5]
+ elsif self.R >= 3
+ hash[0, self.Length / 8]
+ end
+ end
+
+ #
+ # Compute the value of the U field.
+ # Cannot be used with revision 5.
+ #
+ def compute_user_password(userpassword, salt) #:nodoc:
+
+ if self.R == 2
+ key = compute_user_encryption_key(userpassword, salt)
+ user_key = RC4.encrypt(key, PADDING)
+ elsif self.R == 3 or self.R == 4
+ key = compute_user_encryption_key(userpassword, salt)
+
+ upadded = PADDING + salt
+ hash = Digest::MD5.digest(upadded)
+
+ user_key = RC4.encrypt(key, hash)
+
+ 19.times { |i| user_key = RC4.encrypt(xor(key,i+1), user_key) }
+
+ user_key.ljust(32, 0xFF.chr)
+ end
+ end
+
+ #
+ # Computes hardened hash used in revision 6 (extension level 8).
+ #
+ def compute_hardened_hash(password, salt, vector = '')
+ block_size = 32
+ input = Digest::SHA256.digest(password + salt + vector) + "\x00" * 32
+ key = input[0, 16]
+ iv = input[16, 16]
+ digest, aes, h, x = nil, nil, nil, nil
+
+ 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
+
+ 64.times do |j|
+ x = ''
+ x += aes.update(password) unless password.empty?
+ x += aes.update(block)
+ x += aes.update(vector) unless vector.empty?
+
+ if j == 0
+ block_size = 32 + (x.unpack("C16").inject(0) {|a,b| a+b} % 3) * 16
+ digest = Digest::SHA2.new(block_size << 3)
+ end
+
+ digest.update(x)
+ end
+
+ h = digest.digest
+ key = h[0, 16]
+ input[0, block_size] = h[0, block_size]
+ iv = h[16, 16]
+
+ i = i + 1
+ end
+
+ h[0, 32]
+ end
+
+ def xor(str, byte) #:nodoc:
+ str.split(//).map!{|c| (c[0].ord ^ byte).chr }.join
+ end
+
+ def pad_password(password) #:nodoc:
+ return PADDING.dup if password.empty? # Fix for Ruby 1.9 bug
+ password[0,32].ljust(32, PADDING)
+ end
+
+ def password_to_utf8(passwd) #:nodoc:
+ LiteralString.new(passwd).to_utf8[0, 127]
+ end
+ end
end
-
- end
-
end
-
- end
end
-
-__END__
-def hexprint(str)
- hex = ""
- str.each_byte do |b|
- digit = b.to_s(16)
- digit = "0" + digit if digit.size == 1
- hex << digit
- end
-
- puts hex.upcase
-end
-