class PDF::Reader::StandardSecurityHandler

class creates interface to encrypt dictionary for use in Decrypt

def self.supports?(encrypt)

This handler supports all encryption that follows upto PDF 1.5 spec (revision 4)
def self.supports?(encrypt)
  return false if encrypt.nil?
  filter = encrypt.fetch(:Filter, :Standard)
  version = encrypt.fetch(:V, 0)
  algorithm = encrypt.fetch(:CF, {}).fetch(encrypt[:StmF], {}).fetch(:CFM, nil)
  (filter == :Standard) && (encrypt[:StmF] == encrypt[:StrF]) &&
    (version <= 3 || (version == 4 && ((algorithm == :V2) || (algorithm == :AESV2))))
end

def auth_owner_pass(pass)


then it returns nil
if the supplied password is not a valid owner password for this document

password that should be used to decrypt the document.
if the string is a valid owner password this will return the user

Used to test Owner passwords

Algorithm 7 - Authenticating the Owner Password

# 7.6.3.4 Password Algorithms
def auth_owner_pass(pass)
  md5 = Digest::MD5.digest(pad_pass(pass))
  if @revision > 2 then
    50.times { md5 = Digest::MD5.digest(md5) }
    keyBegins = md5[0, key_length]
    #first iteration decrypt owner_key
    out = @owner_key
    #RC4 keyed with (keyBegins XOR with iteration #) to decrypt previous out
    19.downto(0).each { |i| out=RC4.new(xor_each_byte(keyBegins,i)).decrypt(out) }
  else
    out = RC4.new( md5[0, 5] ).decrypt( @owner_key )
  end
  # c) check output as user password
  auth_user_pass( out )
end

def auth_user_pass(pass)


then it returns nil
if the supplied password is not a valid user password for this document

password that should be used to decrypt the document.
if the string is a valid user password this will return the user

Used to test User passwords

Algorithm 6 - Authenticating the User Password
def auth_user_pass(pass)
  keyBegins = make_file_key(pass)
  if @revision >= 3
    #initialize out for first iteration
    out = Digest::MD5.digest(PassPadBytes.pack("C*") + @file_id)
    #zero doesn't matter -> so from 0-19
    20.times{ |i| out=RC4.new(xor_each_byte(keyBegins, i)).encrypt(out) }
    pass = @user_key[0, 16] == out
  else
    pass = RC4.new(keyBegins).encrypt(PassPadBytes.pack("C*")) == @user_key
  end
  pass ? keyBegins : nil
end

def build_standard_key(pass)

def build_standard_key(pass)
  encrypt_key   = auth_owner_pass(pass)
  encrypt_key ||= auth_user_pass(pass)
  raise PDF::Reader::EncryptedPDFError, "Invalid password (#{pass})" if encrypt_key.nil?
  encrypt_key
end

def decrypt( buf, ref )


ref - a PDF::Reader::Reference for the object to decrypt
buf - a string to decrypt

used to decrypt RC4/AES encrypted PDF streams (buf)

Algorithm 1: Encryption of data using the RC4 or AES algorithms

#7.6.2 General Encryption Algorithm
def decrypt( buf, ref )
  case @cfm
    when :AESV2
      decrypt_aes128(buf, ref)
    else
      decrypt_rc4(buf, ref)
  end
end

def decrypt_aes128( buf, ref )

when (version == 4 and CFM == AESV2)
decrypt with AES-128-CBC algorithm
def decrypt_aes128( buf, ref )
  objKey = @encrypt_key.dup
  (0..2).each { |e| objKey << (ref.id >> e*8 & 0xFF ) }
  (0..1).each { |e| objKey << (ref.gen >> e*8 & 0xFF ) }
  objKey << 'sAlT'  # Algorithm 1, b)
  length = objKey.length < 16 ? objKey.length : 16
  cipher = OpenSSL::Cipher.new("AES-#{length << 3}-CBC")
  cipher.decrypt
  cipher.key = Digest::MD5.digest(objKey)[0,length]
  cipher.iv = buf[0..15]
  cipher.update(buf[16..-1]) + cipher.final
end

def decrypt_rc4( buf, ref )

version <=3 or (version == 4 and CFM == V2)
decrypt with RC4 algorithm
def decrypt_rc4( buf, ref )
  objKey = @encrypt_key.dup
  (0..2).each { |e| objKey << (ref.id >> e*8 & 0xFF ) }
  (0..1).each { |e| objKey << (ref.gen >> e*8 & 0xFF ) }
  length = objKey.length < 16 ? objKey.length : 16
  rc4 = RC4.new( Digest::MD5.digest(objKey)[0,length] )
  rc4.decrypt(buf)
end

def initialize(opts = {})

def initialize(opts = {})
  @key_length    = opts[:key_length].to_i/8
  @revision      = opts[:revision].to_i
  @owner_key     = opts[:owner_key]
  @user_key      = opts[:user_key]
  @permissions   = opts[:permissions].to_i
  @encryptMeta   = opts.fetch(:encrypted_metadata, true)
  @file_id       = opts[:file_id] || ""
  @encrypt_key   = build_standard_key(opts[:password] || "")
  @cfm           = opts[:cfm]
  if @key_length != 5 && @key_length != 16
    msg = "StandardSecurityHandler only supports 40 and 128 bit\
           encryption (#{@key_length * 8}bit)"
    raise ArgumentError, msg
  end
end

def make_file_key( user_pass )

def make_file_key( user_pass )
  # a) if there's a password, pad it to 32 bytes, else, just use the padding.
  @buf  = pad_pass(user_pass)
  # c) add owner key
  @buf << @owner_key
  # d) add permissions 1 byte at a time, in little-endian order
  (0..24).step(8){|e| @buf << (@permissions >> e & 0xFF)}
  # e) add the file ID
  @buf << @file_id
  # f) if revision >= 4 and metadata not encrypted then add 4 bytes of 0xFF
  if @revision >= 4 && !@encryptMeta
    @buf << [0xFF,0xFF,0xFF,0xFF].pack('C*')
  end
  # b) init MD5 digest + g) finish the hash
  md5 = Digest::MD5.digest(@buf)
  # h) spin hash 50 times
  if @revision >= 3
    50.times {
      md5 = Digest::MD5.digest(md5[0, @key_length])
    }
  end
  # i) n = key_length revision >= 3, n = 5 revision == 2
  if @revision < 3
    md5[0, 5]
  else
    md5[0, @key_length]
  end
end

def pad_pass(p="")

pp61 of spec
Pads supplied password to 32bytes using PassPadBytes as specified on
def pad_pass(p="")
  if p.nil? || p.empty?
    PassPadBytes.pack('C*')
  else
    p[0, 32] + PassPadBytes[0, 32-p.length].pack('C*')
  end
end

def xor_each_byte(buf, int)

def xor_each_byte(buf, int)
  buf.each_byte.map{ |b| b^int}.pack("C*")
end