class PDF::Reader::StandardSecurityHandler

class creates interface to encrypt dictionary for use in Decrypt

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 itteration decrypt owner_key
    out = @owner_key
    #RC4 keyed with (keyBegins XOR with itteration #) 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 > 2
    #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)).decrypt(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 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 )
  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( enc, file_id, password )

def initialize( enc, file_id, password )
  @filter        = enc[:Filter]
  @subFilter     = enc[:SubFilter]
  @version       = enc[:V].to_i
  @key_length    = enc[:Length].to_i/8
  @crypt_filter  = enc[:CF]
  @stream_filter = enc[:StmF]
  @string_filter = enc[:StrF]
  @revision      = enc[:R].to_i
  @owner_key     = enc[:O]
  @user_key      = enc[:U]
  @permissions   = enc[:P].to_i
  @embedded_file_filter = enc[:EFF]
  @encryptMeta   = enc.has_key?(:EncryptMetadata)? enc[:EncryptMetadata].to_s == "true" : true;
  @file_id       = file_id.first
  @encrypt_key   = build_standard_key(password)
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 then if encryptMetadata add 4 bytes of 0x00 else add 4 bytes of 0xFF
  if @revision > 4
    @buf << [ @encryptMetadata ? 0x00 : 0xFF ].pack('C')*4
  end
  # b) init MD5 digest + g) finish the hash
  md5 = Digest::MD5.digest(@buf)
  # h) spin hash 50 times
  if @revision > 2
    50.times {
      md5 = Digest::MD5.digest(md5[(0...@key_length)])
    }
  end
  # i) n = key_length revision > 3, n = 5 revision == 2
  md5[(0...((@revision < 3) ? 5 : @key_length))]
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