=begin

= File
	encryption.rb

= 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 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/>.

=end

begin
  require 'openssl' if Origami::OPTIONS[:use_openssl]
rescue LoadError
  Origami::OPTIONS[:use_openssl] = false
end

require 'digest/md5'
require 'digest/sha2'

module Origami

  class EncryptionError < Exception #: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
    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

      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}"
                end
            else
              str_algo = Encryption::Identity
            end

            if handler[:StmF].is_a?(Name) and cfs[handler[:StmF]].is_a?(Dictionary)
              cfdict = cfs[handler[:StmF]]

              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
                else
                  raise EncryptionNotSupportedError, "Unsupported encryption version : #{handler.V}"
                end
            else
              stm_algo = Encryption::Identity
            end

          else
            str_algo = stm_algo = Encryption::Identity
          end

        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

      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


      #self.extend(Encryption::EncryptedDocument)
      #self.encryption_dict = encrypt_dict
      #self.encryption_key = encryption_key
      #self.stm_algo = self.str_algo = algorithm

      encrypt_metadata = (handler.EncryptMetadata != false)

      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

      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.encryption_handler = handler
              obj.encryption_key = encryption_key
              obj.algorithm = str_algo
              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)
              obj.encryption_handler = handler
              obj.encryption_key = encryption_key
              obj.algorithm = stm_algo
          end
        end
      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

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

      userpasswd, ownerpasswd = params[:user_passwd], params[:owner_passwd]

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

        cryptfilter.Length = params[:key_size] >> 3

        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)

      fileInfo = get_trailer_info
      fileInfo[:Encrypt] = self << handler

      self.extend(Encryption::EncryptedDocument)
      self.encryption_dict = handler
      self.encryption_key = encryption_key
      self.stm_algo = self.str_algo = algorithm

      self
    end
  
  end

  #
  # Module to provide support for encrypting and decrypting PDF documents.
  #
  module Encryption

    #
    # Generates _n_ random bytes from a fast PRNG.
    #
    def self.rand_bytes(n)
      ::Array.new(n) { rand(256) }.pack("C*")
    end

    #
    # Generates _n_ random bytes from a crypto PRNG.
    #
    def self.strong_rand_bytes(n)
      if Origami::OPTIONS[:use_openssl]
        OpenSSL::Random.random_bytes(n)
      elsif RUBY_VERSION >= '1.9'
        Random.new.bytes(n)
      else
        self.rand_bytes(n)
      end
    end

    module EncryptedDocument

      attr_writer :encryption_key
      attr_writer :encryption_dict
      attr_writer :stm_algo
      attr_writer :str_algo

      private

      def physicalize(options = {})

        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
            end

            return
          end
     
          if obj.is_a?(ObjectStream)
            obj.each do |subobj|
              build(subobj, revision, options)
            end
          end

          obj.pre_build

          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

          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

          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

          obj.post_build
        end
      
        # stack up every root objects
        indirect_objects_by_rev.each do |obj, revision|
          build(obj, revision, options)
        end

        # remove encrypt dictionary if requested
        if options[:decrypt]
          delete_object(get_trailer_info[:Encrypt])
          get_trailer_info[:Encrypt] = nil
        end
        
        self
      end
      
    end

    #
    # Module for encrypted PDF objects.
    #
    module EncryptedObject #:nodoc

      attr_writer :encryption_key
      attr_writer :algorithm
      attr_writer :encryption_handler
      attr_accessor :decrypted

      def self.extended(obj)
        obj.decrypted = false
      end

      def post_build
        encrypt!

        super
      end

      private 

      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
        end
      end

    end

    #
    # Module for encrypted String.
    #
    module EncryptedString
      include EncryptedObject

      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

          @decrypted = false

          self.replace(encrypted_data)
          self.freeze
        end
        
        self
      end

      def decrypt!
        unless @decrypted
          key = compute_object_key
          self.replace(@algorithm.decrypt(key, self.to_str))
          @decrypted = true
        end

        self
      end
    end

    #
    # Module for encrypted Stream.
    #
    module EncryptedStream
      include EncryptedObject

      def encrypt!
        if @decrypted
          encode!

          key = compute_object_key

          @rawdata = 
            if @algorithm == ARC4 or @algorithm == Identity
              @algorithm.encrypt(key, self.rawdata)
            else
              iv = Encryption.rand_bytes(AES::BLOCKSIZE)
              @algorithm.encrypt(key, iv, @rawdata)
            end

          @decrypted = false

          @rawdata.freeze
          self.freeze
        end

        self
      end

      def decrypt!
        unless @decrypted
          key = compute_object_key

          self.rawdata = @algorithm.decrypt(key, @rawdata)
          @decrypted = true
        end

        self
      end

    end

    #
    # Identity transformation.
    #
    module Identity
      def Identity.encrypt(key, data)
        data
      end

      def Identity.decrypt(key, data)
        data
      end
    end

    #
    # 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

          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

    #
    # Pure Ruby implementation of the AES symmetric algorithm.
    # Using mode CBC.
    #
    class AES 
      
      NROWS = 4
      NCOLS = 4
      BLOCKSIZE = NROWS * NCOLS

      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

      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 not iv.nil? and iv.size != BLOCKSIZE
          raise EncryptionError, "Initialization vector must have a length of #{BLOCKSIZE} bytes."
        end

        @key = key
        @iv = iv
        @use_padding = use_padding
      end

      def encrypt(data)

        if @iv.nil?
          raise EncryptionError, "No initialization vector has been set."
        end
        
        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

          @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 = aesEncrypt(plainblock)
            cipher.concat(cipherblock)
          end

          @iv + cipher.pack("C*")
        end
      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

          plain = (aes.update(data) + aes.final).unpack("C*")
        else
          plain = []
          plainblock = []
          prev_cipherblock = []
          nblocks = data.size / BLOCKSIZE

          first_round = true
          nblocks.times do |n|
            cipherblock = data[n * BLOCKSIZE, BLOCKSIZE].unpack("C*")

            plainblock = aesDecrypt(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

          padlen.times do 
            pad = plain.pop
            raise EncryptionError, 
              "Incorrect padding byte : 0x#{pad.to_s 16}" if pad != padlen
          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 galoisMult(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 scheduleCore(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 createRoundKey(expandedKey, round = 0) #:nodoc:
        transpose(expandedKey[round * BLOCKSIZE, BLOCKSIZE])
      end

      def addRoundKey(roundKey) #:nodoc:
        BLOCKSIZE.times do |i| @state[i] ^= roundKey[i] end 
      end

      def subBytes #:nodoc:
        BLOCKSIZE.times do |i| @state[i] = SBOX[ @state[i] ] end
      end

      def rsubBytes #:nodoc:
        BLOCKSIZE.times do |i| @state[i] = RSBOX[ @state[i] ] end
      end

      def shiftRows #:nodoc:
        NROWS.times do |i|
          @state[i * NCOLS, NCOLS] = rol(@state[i * NCOLS, NCOLS], i)
        end
      end

      def rshiftRows #:nodoc:
        NROWS.times do |i|
          @state[i * NCOLS, NCOLS] = ror(@state[i * NCOLS, NCOLS], i)
        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

      def mixColumn(column) #:nodoc:
        mixColumnWithField(column, [ 2, 1, 1, 3 ])
      end

      def rmixColumn(column) #:nodoc:
        mixColumnWithField(column, [ 14, 9, 13, 11 ])
      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

      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 expandKey(key) #:nodoc:

        key = key.unpack("C*")
        size = key.size
        expandedSize = 16 * (ROUNDS[key.size] + 1)
        rconIter = 1
        expandedKey = key[0, size]

        while expandedKey.size < expandedSize
          temp = expandedKey[-4, 4]

          if expandedKey.size % size == 0
            scheduleCore(temp, rconIter)
            rconIter = rconIter.succ 
          end

          temp.map! do |b| SBOX[b] end if size == 32 and expandedKey.size % size == 16

          temp.each do |b| expandedKey << (expandedKey[-size] ^ b) end
        end

        expandedKey
      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

      def raesRound(roundKey) #:nodoc:
        addRoundKey(roundKey)
        rmixColumns
        rshiftRows
        rsubBytes
      end

      def aesEncrypt(block) #:nodoc:
        @state = transpose(block)
        expandedKey = expandKey(@key)
        rounds = ROUNDS[@key.size]

        aesMain(expandedKey, rounds)
      end

      def aesDecrypt(block) #:nodoc:
        @state = transpose(block)
        expandedKey = expandKey(@key)
        rounds = ROUNDS[@key.size]

        raesMain(expandedKey, rounds)
      end

      def aesMain(expandedKey, rounds) #:nodoc:
        #puts "expandedKey: #{expandedKey.inspect}" 
        roundKey = createRoundKey(expandedKey)
        addRoundKey(roundKey)

        for i in 1..rounds-1
          roundKey = createRoundKey(expandedKey, i)
          aesRound(roundKey)
        end

        roundKey = createRoundKey(expandedKey, rounds)
        subBytes
        shiftRows
        addRoundKey(roundKey)

        transpose(@state)
      end

      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

        roundKey = createRoundKey(expandedKey)
        addRoundKey(roundKey)

        transpose(@state)
      end

    end
  
    #
    # Class representing a crypt filter Dictionary
    #
    class CryptFilterDictionary < Dictionary
      include StandardObject

      field   :Type,          :Type => Name, :Default => :CryptFilter
      field   :CFM,           :Type => Name, :Default => :None
      field   :AuthEvent,     :Type => Name, :Default => :DocOpen
      field   :Length,        :Type => Integer
    end

    #
    # Common class for encryption dictionaries.
    #
    class EncryptionDictionary < Dictionary
      include StandardObject

      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.8'
    
      #
      # 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 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.8'

            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

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

            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
            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
        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 = 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).
        #
        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
            j = 0
            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:
          Origami::ByteString.new(passwd).to_utf8[0, 127]
        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

