Module: Pdfrb::Encryption::V5Writer
- Defined in:
- lib/pdfrb/encryption/v5_writer.rb
Overview
AES-256 V5/V6 (R6) password key derivation and /U + /O entry construction per ISO 23528-2 ยง7.6.4.4 (algorithms 2.B, 8, 9). Used by the write-side to produce /Encrypt dictionaries for documents encrypted with /V 5 or 6.
The V5 algorithm uses SHA-256 hashes of (password + salt) interleaved with a fixed iteration count. Keys are 32 bytes.
Constant Summary collapse
- U_VALIDATION_SALT_LEN =
8- U_KEY_SALT_LEN =
8- O_VALIDATION_SALT_LEN =
8- O_KEY_SALT_LEN =
8
Class Method Summary collapse
- .aes_ecb_decrypt(data, key) ⇒ Object
- .aes_ecb_encrypt(data, key) ⇒ Object
-
.build_o_entry(owner_password:, user_password:, validation_salt:, key_salt:) ⇒ String
Build a 48-byte /O entry for V5 from an owner password + U.
-
.build_u_entry(password:, validation_salt:, key_salt:) ⇒ String
Build a 48-byte /U entry for V5 from a user password.
-
.derive_file_key_v5(password:, u_entry:, ue:, _owner: false) ⇒ Object
Derive the 32-byte file encryption key for V5 from the user password, /U entry's key salt, and the /UE field (encrypted key).
-
.encrypt_file_key(file_key, intermediate) ⇒ Object
Encrypt the 32-byte file encryption key for /UE using the password-derived intermediate key.
-
.extract_v5_salts(entry) ⇒ Object
Extract validation_salt and key_salt from a 48-byte V5 entry.
-
.hash_v5(input, salt, iterations: 100) ⇒ Object
Compute the 32-byte intermediate hash per Algorithm 2.B.
- .random_salt ⇒ Object
Class Method Details
.aes_ecb_decrypt(data, key) ⇒ Object
94 95 96 97 98 99 100 101 |
# File 'lib/pdfrb/encryption/v5_writer.rb', line 94 def aes_ecb_decrypt(data, key) cipher = OpenSSL::Cipher.new("AES-256-ECB") cipher.decrypt cipher.key = key cipher.padding = 0 out = cipher.update(data) out + cipher.final end |
.aes_ecb_encrypt(data, key) ⇒ Object
85 86 87 88 89 90 91 92 |
# File 'lib/pdfrb/encryption/v5_writer.rb', line 85 def aes_ecb_encrypt(data, key) cipher = OpenSSL::Cipher.new("AES-256-ECB") cipher.encrypt cipher.key = key cipher.padding = 0 out = cipher.update(data) out + cipher.final end |
.build_o_entry(owner_password:, user_password:, validation_salt:, key_salt:) ⇒ String
Build a 48-byte /O entry for V5 from an owner password + U. Format: [32-byte hash][8-byte validation salt][8-byte key salt].
43 44 45 46 |
# File 'lib/pdfrb/encryption/v5_writer.rb', line 43 def build_o_entry(owner_password:, user_password:, validation_salt:, key_salt:) hash = hash_v5(owner_password + user_password, validation_salt) hash + validation_salt + key_salt end |
.build_u_entry(password:, validation_salt:, key_salt:) ⇒ String
Build a 48-byte /U entry for V5 from a user password. Format: [32-byte hash][8-byte validation salt][8-byte key salt].
30 31 32 33 |
# File 'lib/pdfrb/encryption/v5_writer.rb', line 30 def build_u_entry(password:, validation_salt:, key_salt:) hash = hash_v5(password, validation_salt) hash + validation_salt + key_salt end |
.derive_file_key_v5(password:, u_entry:, ue:, _owner: false) ⇒ Object
Derive the 32-byte file encryption key for V5 from the user password, /U entry's key salt, and the /UE field (encrypted key). Returns the decrypted key bytes.
63 64 65 66 67 |
# File 'lib/pdfrb/encryption/v5_writer.rb', line 63 def derive_file_key_v5(password:, u_entry:, ue:, _owner: false) _hash, _vsalt, key_salt = extract_v5_salts(u_entry) intermediate = hash_v5(password, key_salt) aes_ecb_decrypt(ue, intermediate) end |
.encrypt_file_key(file_key, intermediate) ⇒ Object
Encrypt the 32-byte file encryption key for /UE using the password-derived intermediate key. AES-256-ECB, no padding.
81 82 83 |
# File 'lib/pdfrb/encryption/v5_writer.rb', line 81 def encrypt_file_key(file_key, intermediate) aes_ecb_encrypt(file_key, intermediate) end |
.extract_v5_salts(entry) ⇒ Object
Extract validation_salt and key_salt from a 48-byte V5 entry. Returns [hash, validation_salt, key_salt].
71 72 73 74 75 76 77 |
# File 'lib/pdfrb/encryption/v5_writer.rb', line 71 def extract_v5_salts(entry) return [nil, nil, nil] unless entry && entry.bytesize >= 48 [entry.byteslice(0, 32), entry.byteslice(32, U_VALIDATION_SALT_LEN), entry.byteslice(40, U_KEY_SALT_LEN)] end |
.hash_v5(input, salt, iterations: 100) ⇒ Object
Compute the 32-byte intermediate hash per Algorithm 2.B. Initial input = SHA256(password + validation_salt). Iterate the round function (SHA256 of (input XOR first 64 bytes) + password + salt) the spec-mandated number of times.
52 53 54 55 56 57 58 |
# File 'lib/pdfrb/encryption/v5_writer.rb', line 52 def hash_v5(input, salt, iterations: 100) k = OpenSSL::Digest::SHA256.digest(input + salt) iterations.times do k = OpenSSL::Digest::SHA256.digest(k) end k end |
.random_salt ⇒ Object
103 104 105 |
# File 'lib/pdfrb/encryption/v5_writer.rb', line 103 def random_salt SecureRandom.random_bytes(8) end |