1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89
|
# coding: utf-8
require 'digest'
require 'openssl'
class PDF::Reader
# class creates interface to encrypt dictionary for use in Decrypt
class StandardSecurityHandlerV5
attr_reader :key_length, :encrypt_key
def initialize(opts = {})
@key_length = 256
@O = opts[:O] # hash(32B) + validation salt(8B) + key salt(8B)
@U = opts[:U] # hash(32B) + validation salt(8B) + key salt(8B)
@OE = opts[:OE] # decryption key, encrypted w/ owner password
@UE = opts[:UE] # decryption key, encrypted w/ user password
@encrypt_key = build_standard_key(opts[:password] || '')
end
# This handler supports AES-256 encryption defined in PDF 1.7 Extension Level 3
def self.supports?(encrypt)
return false if encrypt.nil?
filter = encrypt.fetch(:Filter, :Standard)
version = encrypt.fetch(:V, 0)
revision = encrypt.fetch(:R, 0)
algorithm = encrypt.fetch(:CF, {}).fetch(encrypt[:StmF], {}).fetch(:CFM, nil)
(filter == :Standard) && (encrypt[:StmF] == encrypt[:StrF]) &&
((version == 5) && (revision == 5) && (algorithm == :AESV3))
end
##7.6.2 General Encryption Algorithm
#
# Algorithm 1: Encryption of data using the RC4 or AES algorithms
#
# used to decrypt RC4/AES encrypted PDF streams (buf)
#
# buf - a string to decrypt
# ref - a PDF::Reader::Reference for the object to decrypt
#
def decrypt( buf, ref )
cipher = OpenSSL::Cipher.new("AES-#{@key_length}-CBC")
cipher.decrypt
cipher.key = @encrypt_key.dup
cipher.iv = buf[0..15]
cipher.update(buf[16..-1]) + cipher.final
end
private
# Algorithm 3.2a - Computing an encryption key
#
# Defined in PDF 1.7 Extension Level 3
#
# if the string is a valid user/owner password, this will return the decryption key
#
def auth_owner_pass(password)
if Digest::SHA256.digest(password + @O[32..39] + @U) == @O[0..31]
cipher = OpenSSL::Cipher.new('AES-256-CBC')
cipher.decrypt
cipher.key = Digest::SHA256.digest(password + @O[40..-1] + @U)
cipher.iv = "\x00" * 16
cipher.padding = 0
cipher.update(@OE) + cipher.final
end
end
def auth_user_pass(password)
if Digest::SHA256.digest(password + @U[32..39]) == @U[0..31]
cipher = OpenSSL::Cipher.new('AES-256-CBC')
cipher.decrypt
cipher.key = Digest::SHA256.digest(password + @U[40..-1])
cipher.iv = "\x00" * 16
cipher.padding = 0
cipher.update(@UE) + cipher.final
end
end
def build_standard_key(pass)
pass = pass.byteslice(0...127) # UTF-8 encoded password. first 127 bytes
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
end
end
|