Class: Pdfrb::Encryption::StandardSecurityHandler
- Inherits:
-
Object
- Object
- Pdfrb::Encryption::StandardSecurityHandler
- Defined in:
- lib/pdfrb/encryption/standard_security_handler.rb
Constant Summary collapse
- PAD =
String.new("\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", encoding: Encoding::BINARY).freeze
Instance Attribute Summary collapse
-
#encrypt_dict ⇒ Object
readonly
Returns the value of attribute encrypt_dict.
-
#key ⇒ Object
readonly
Returns the value of attribute key.
-
#key_length ⇒ Object
readonly
Returns the value of attribute key_length.
-
#permissions ⇒ Object
readonly
Returns the value of attribute permissions.
-
#revision ⇒ Object
readonly
Returns the value of attribute revision.
-
#version ⇒ Object
readonly
Returns the value of attribute version.
Class Method Summary collapse
-
.for_v5(user_password:, owner_password:, permissions: -1,, id: SecureRandom.hex(16).b, key_length: 32) ⇒ Object
Construct a write-side V5 (AES-256, R6) handler from a user and owner password.
Instance Method Summary collapse
- #compute_encryption_key(password) ⇒ Object
-
#compute_encryption_key_v5(password) ⇒ Object
V5 key derivation: SHA-256 of (password + key_salt) gives an intermediate hash.
- #compute_object_key(oid, gen) ⇒ Object
-
#compute_object_key_v5(oid, gen) ⇒ Object
V5 per-object key: SHA-256 of (file_key || oid_le32 || gen_le32).
- #decrypt_data(data, oid, gen) ⇒ Object
- #encrypt_data(data, oid, gen) ⇒ Object (also: #encrypt)
-
#initialize(trailer_dict, precomputed_key: nil) ⇒ StandardSecurityHandler
constructor
A new instance of StandardSecurityHandler.
- #verify_user_password?(password) ⇒ Boolean
- #verify_user_password_v5?(password) ⇒ Boolean
Constructor Details
#initialize(trailer_dict, precomputed_key: nil) ⇒ StandardSecurityHandler
Returns a new instance of StandardSecurityHandler.
20 21 22 23 24 25 26 27 28 29 |
# File 'lib/pdfrb/encryption/standard_security_handler.rb', line 20 def initialize(trailer_dict, precomputed_key: nil) @encrypt = trailer_dict[:Encrypt] @encrypt_dict = @encrypt @id = trailer_dict[:ID] @version = (@encrypt[:V] || 0).to_i @revision = (@encrypt[:R] || 3).to_i @key_length = (@encrypt[:Length] || 40).to_i / 8 @permissions = (@encrypt[:P] || -1).to_i @key = precomputed_key end |
Instance Attribute Details
#encrypt_dict ⇒ Object (readonly)
Returns the value of attribute encrypt_dict.
13 14 15 |
# File 'lib/pdfrb/encryption/standard_security_handler.rb', line 13 def encrypt_dict @encrypt_dict end |
#key ⇒ Object (readonly)
Returns the value of attribute key.
13 14 15 |
# File 'lib/pdfrb/encryption/standard_security_handler.rb', line 13 def key @key end |
#key_length ⇒ Object (readonly)
Returns the value of attribute key_length.
13 14 15 |
# File 'lib/pdfrb/encryption/standard_security_handler.rb', line 13 def key_length @key_length end |
#permissions ⇒ Object (readonly)
Returns the value of attribute permissions.
13 14 15 |
# File 'lib/pdfrb/encryption/standard_security_handler.rb', line 13 def @permissions end |
#revision ⇒ Object (readonly)
Returns the value of attribute revision.
13 14 15 |
# File 'lib/pdfrb/encryption/standard_security_handler.rb', line 13 def revision @revision end |
#version ⇒ Object (readonly)
Returns the value of attribute version.
13 14 15 |
# File 'lib/pdfrb/encryption/standard_security_handler.rb', line 13 def version @version end |
Class Method Details
.for_v5(user_password:, owner_password:, permissions: -1,, id: SecureRandom.hex(16).b, key_length: 32) ⇒ Object
Construct a write-side V5 (AES-256, R6) handler from a user and owner password. Returns a StandardSecurityHandler whose encrypt_dict is populated with /U, /O, /UE, /OE, /Perms ready for inclusion in the trailer. The handler's key is set to the freshly-generated file encryption key so #encrypt works.
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 |
# File 'lib/pdfrb/encryption/standard_security_handler.rb', line 36 def self.for_v5(user_password:, owner_password:, permissions: -1, id: SecureRandom.hex(16).b, key_length: 32) file_key = SecureRandom.random_bytes(key_length) u_validation_salt = V5Writer.random_salt u_key_salt = V5Writer.random_salt o_validation_salt = V5Writer.random_salt o_key_salt = V5Writer.random_salt u_entry = V5Writer.build_u_entry(password: user_password.to_s, validation_salt: u_validation_salt, key_salt: u_key_salt) o_entry = V5Writer.build_o_entry(owner_password: owner_password.to_s, user_password: user_password.to_s, validation_salt: o_validation_salt, key_salt: o_key_salt) u_intermediate = V5Writer.hash_v5(user_password.to_s, u_key_salt) ue = V5Writer.aes_ecb_encrypt(file_key, u_intermediate) o_intermediate = V5Writer.hash_v5(owner_password.to_s + user_password.to_s, o_key_salt) oe = V5Writer.aes_ecb_encrypt(file_key, o_intermediate) perms_plain = "#{[ & 0xFFFFFFFF, 0xFFFFFFFF].pack('VV')}T#{SecureRandom.random_bytes(7)}" encrypt_dict = { Filter: :Standard, V: 5, R: 6, Length: key_length * 8, P: , U: u_entry, O: o_entry, UE: ue, OE: oe, Perms: V5Writer.aes_ecb_encrypt(perms_plain, file_key), } trailer = { Encrypt: encrypt_dict, ID: [id, id] } new(trailer, precomputed_key: file_key) end |
Instance Method Details
#compute_encryption_key(password) ⇒ Object
101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 |
# File 'lib/pdfrb/encryption/standard_security_handler.rb', line 101 def compute_encryption_key(password) return compute_encryption_key_v5(password) if @version >= 5 padded = pad_password(password) owner_hash = @encrypt[:O] || String.new("", encoding: Encoding::BINARY) perms = [@permissions].pack("V") id_first = id_bytes md5 = Digest::MD5.new md5.update(padded) md5.update(owner_hash) md5.update(perms) md5.update(id_first) if @revision >= 4 md5.update(@encrypt[:UE] || "") md5.update(@encrypt[:Perms] ? [@encrypt[:Perms]].pack("N") : "") end hash = md5.digest if @revision >= 3 50.times { hash = Digest::MD5.digest(hash[0, @key_length]) } end hash[0, @key_length] end |
#compute_encryption_key_v5(password) ⇒ Object
V5 key derivation: SHA-256 of (password + key_salt) gives an intermediate hash. AES-ECB-decrypt /UE with the intermediate to recover the file encryption key.
132 133 134 135 136 137 138 139 140 141 142 |
# File 'lib/pdfrb/encryption/standard_security_handler.rb', line 132 def compute_encryption_key_v5(password) u_entry = @encrypt[:U] || "".b _hash, _vsalt, key_salt = V5Writer.extract_v5_salts(u_entry) return nil unless key_salt intermediate = V5Writer.hash_v5(password.to_s, key_salt) ue = @encrypt[:UE] || "".b return nil if ue.bytesize != 32 V5Writer.aes_ecb_decrypt(ue, intermediate) end |
#compute_object_key(oid, gen) ⇒ Object
144 145 146 147 148 149 150 151 152 153 154 155 156 157 |
# File 'lib/pdfrb/encryption/standard_security_handler.rb', line 144 def compute_object_key(oid, gen) return compute_object_key_v5(oid, gen) if @version >= 5 md5 = Digest::MD5.new md5.update(@key) md5.update([oid].pack("V")) md5.update([gen].pack("v")) if @version >= 4 md5.update("\xAA") end md5.digest[0, [@key_length + 5, 16].min] end |
#compute_object_key_v5(oid, gen) ⇒ Object
V5 per-object key: SHA-256 of (file_key || oid_le32 || gen_le32).
160 161 162 |
# File 'lib/pdfrb/encryption/standard_security_handler.rb', line 160 def compute_object_key_v5(oid, gen) Digest::SHA256.digest(@key + [oid].pack("V") + [gen].pack("V")) end |
#decrypt_data(data, oid, gen) ⇒ Object
181 182 183 184 185 186 187 188 189 190 191 |
# File 'lib/pdfrb/encryption/standard_security_handler.rb', line 181 def decrypt_data(data, oid, gen) return data unless @key obj_key = compute_object_key(oid, gen) if @version >= 4 decrypt_aes_cbc(data, obj_key) else decrypt_rc4(data, obj_key) end end |
#encrypt_data(data, oid, gen) ⇒ Object Also known as: encrypt
164 165 166 167 168 169 170 171 172 173 174 |
# File 'lib/pdfrb/encryption/standard_security_handler.rb', line 164 def encrypt_data(data, oid, gen) return data unless @key obj_key = compute_object_key(oid, gen) if @version >= 4 encrypt_aes_cbc(data, obj_key) else encrypt_rc4(data, obj_key) end end |
#verify_user_password?(password) ⇒ Boolean
78 79 80 81 82 83 84 |
# File 'lib/pdfrb/encryption/standard_security_handler.rb', line 78 def verify_user_password?(password) return verify_user_password_v5?(password) if @version >= 5 computed = compute_encryption_key(password) @key = computed verify_user_password_hash?(computed) end |
#verify_user_password_v5?(password) ⇒ Boolean
86 87 88 89 90 91 92 93 94 95 96 97 98 99 |
# File 'lib/pdfrb/encryption/standard_security_handler.rb', line 86 def verify_user_password_v5?(password) u_entry = @encrypt[:U] || "".b _hash, validation_salt, _key_salt = V5Writer.extract_v5_salts(u_entry) return false unless validation_salt candidate = V5Writer.hash_v5(password.to_s, validation_salt) stored_hash = u_entry.byteslice(0, 32) return false unless candidate[0, 32] == stored_hash # Password verified — derive and stash the file key so the # handler is immediately usable for decrypt/encrypt. @key = compute_encryption_key_v5(password) true end |