View Source Pdf.Reader.Encryption.V1V2 (ExPDF v1.0.1)
Implements PDF Standard Security Handler algorithms for V1 (RC4-40) and V2 (RC4-128) — revisions R=2 and R=3/4.
Algorithms implemented
| Algorithm | Description | Function |
|---|---|---|
| Alg 2 | File encryption key derivation (MD5 + optional 50×) | derive_file_key/2 |
| Alg 4 | User password auth for R=2 (RC4 of pad constant) | authenticate_user/2 |
| Alg 5 | User password auth for R≥3 (RC4 with 19-step iterations) | authenticate_user/2 |
| Alg 7 | Owner → user password derivation | derive_user_from_owner/2 |
All public functions perform an RC4 availability check first. On systems
where :rc4 is not in :crypto.supports(:ciphers) (e.g. OpenSSL 3.x with
FIPS mode), every function returns {:error, :encrypted_unsupported_handler}
rather than crashing.
Algorithm 2 — File Encryption Key (PDF 1.7 § 7.6.3.3, step a–h)
- Pad the user password to 32 bytes via
PasswordPad.pad/1. - MD5 streaming: hash(padded_pw ++ /O ++ <<P::little-32>> ++ /ID[0]).
- If R ≥ 4 and
encrypt_metadata == false, append<<0xFF,0xFF,0xFF,0xFF>>. - If R ≥ 3, iterate MD5 × 50 on the first
key_lenbytes of the digest. - Truncate to first
key_len = Length / 8bytes (min 5 for V1).
Algorithm 4 — User Auth for R=2
- Derive file key via Algorithm 2.
- RC4-encrypt the 32-byte padding constant with the file key.
- Compare result byte-for-byte with /U (32 bytes).
Algorithm 5 — User Auth for R≥3
- Derive file key via Algorithm 2.
- MD5(padding_constant ++ /ID[0]) → 16 bytes.
- RC4-encrypt those 16 bytes with the file key.
- For i in 1..19: XOR each byte of file_key with i → RC4-encrypt previous result.
- Compare final 16 bytes to the first 16 bytes of /U.
Algorithm 7 — Owner → User Password
- Pad the owner password to 32 bytes.
- MD5 it; if R ≥ 3, iterate MD5 × 50 on the full 16 bytes.
- Truncate to
key_lenbytes → this becomes the RC4 key. - For R=2: RC4-decrypt /O once.
- For R≥3: 20 iterative RC4 passes in reverse order (i=19 down to 0), XORing each key byte with i.
- The result is the padded user password. Feed it to
authenticate_user/2.
Spec references
- PDF 1.7 (ISO 32000-1) § 7.6.3.3 — Algorithms 2, 4, 5, 7: https://opensource.adobe.com/dc-acrobat-sdk-docs/standards/pdfstandards/pdf/PDF32000_2008.pdf
- RFC 1321 (MD5): https://www.rfc-editor.org/rfc/rfc1321.html
- Mozilla pdf.js src/core/crypto.js (Apache-2.0 reference implementation): https://github.com/mozilla/pdf.js/blob/master/src/core/crypto.js
- Erlang OTP
:cryptoalgorithm details: https://www.erlang.org/docs/27/apps/crypto/algorithm_details
Summary
Functions
Derives the padded user password from the owner password using Algorithm 7,
then authenticates via authenticate_user/2.
Authenticates a user password against the /U value in the handler.
Decrypts a stream ciphertext blob using the V1/V2 RC4 algorithm.
Decrypts a string ciphertext using the V1/V2 RC4 algorithm.
Derives the file encryption key for V1/V2/V4 using Algorithm 2.
Derives the padded user password from the owner password using Algorithm 7.
Functions
@spec authenticate_owner(binary(), Pdf.Reader.Encryption.StandardHandler.t()) :: {:ok, binary()} | :error | {:error, :encrypted_unsupported_handler}
Derives the padded user password from the owner password using Algorithm 7,
then authenticates via authenticate_user/2.
Returns
{:ok, file_key}— owner password authenticated.:error— owner password authentication failed.{:error, :encrypted_unsupported_handler}— RC4 not available.
@spec authenticate_user(binary(), Pdf.Reader.Encryption.StandardHandler.t()) :: {:ok, binary()} | :error | {:error, :encrypted_unsupported_handler}
Authenticates a user password against the /U value in the handler.
Uses Algorithm 4 for R=2 and Algorithm 5 for R≥3.
Returns
{:ok, file_key}— password authenticated;file_keyis the derived file encryption key (5 bytes for V1, up to 16 bytes for V2/V4).:error— authentication failed (wrong password).{:error, :encrypted_unsupported_handler}— RC4 not available on this runtime (per R-ENC29 / S-ENC14).
@spec decrypt_stream( binary(), map(), non_neg_integer(), non_neg_integer(), Pdf.Reader.Encryption.StandardHandler.t() ) :: {:ok, binary()} | {:error, :encrypted_unsupported_handler}
Decrypts a stream ciphertext blob using the V1/V2 RC4 algorithm.
Derives a per-object key from handler.file_key, obj_num, and gen_num
using ObjectKey.derive/4, then applies RC4 decryption.
Parameters
bytes— the raw stream bytes (RC4 ciphertext)._stream_dict— the stream dictionary (unused for V1/V2; kept for API symmetry with V4/V5).obj_num— the PDF object number.gen_num— the PDF generation number.handler— a%StandardHandler{}with:file_keypopulated.
Returns
{:ok, plaintext}— successfully decrypted.{:error, :encrypted_unsupported_handler}— RC4 not available at runtime.
Spec references
- PDF 1.7 (ISO 32000-1) § 7.6.2 — General Encryption Algorithm: https://opensource.adobe.com/dc-acrobat-sdk-docs/standards/pdfstandards/pdf/PDF32000_2008.pdf
@spec decrypt_string( binary(), non_neg_integer(), non_neg_integer(), Pdf.Reader.Encryption.StandardHandler.t() ) :: {:ok, binary()} | {:error, :encrypted_unsupported_handler}
Decrypts a string ciphertext using the V1/V2 RC4 algorithm.
Derives a per-object key from handler.file_key, obj_num, and gen_num
using ObjectKey.derive/4, then applies RC4 decryption.
Parameters
bytes— the raw string bytes (RC4 ciphertext).obj_num— the PDF object number.gen_num— the PDF generation number.handler— a%StandardHandler{}with:file_keypopulated.
Returns
{:ok, plaintext}— successfully decrypted.{:error, :encrypted_unsupported_handler}— RC4 not available at runtime.
Spec references
- PDF 1.7 (ISO 32000-1) § 7.6.2 — General Encryption Algorithm: https://opensource.adobe.com/dc-acrobat-sdk-docs/standards/pdfstandards/pdf/PDF32000_2008.pdf
@spec derive_file_key(binary(), Pdf.Reader.Encryption.StandardHandler.t()) :: binary()
Derives the file encryption key for V1/V2/V4 using Algorithm 2.
Parameters
password— the plaintext user password (any length; padded/truncated internally).handler— a%StandardHandler{}with:revision,:length,:o,:p,:id, and:encrypt_metadatapopulated.
Returns
A binary of handler.length / 8 bytes (e.g. 5 bytes for V1/R=2, 16 bytes for
V2/R=3 with Length=128).
Does NOT check RC4 availability — the caller (authenticate_user/2) guards
that. Safe to call from tests or other modules that already checked.
@spec derive_user_from_owner(binary(), Pdf.Reader.Encryption.StandardHandler.t()) :: {:ok, binary()} | {:error, :encrypted_unsupported_handler}
Derives the padded user password from the owner password using Algorithm 7.
Steps
- Pad owner password to 32 bytes.
- MD5; for R≥3, iterate 50 times.
- Truncate to
key_lenbytes → RC4 key. - For R=2: RC4-decrypt /O once.
- For R≥3: 20 iterative passes in reverse order (i=19 down to 0), XOR each byte of RC4 key with i before each pass.
Returns
{:ok, padded_user_password}— a 32-byte binary that is the padded user password (as ifPasswordPad.pad(user_password)had been called).{:error, :encrypted_unsupported_handler}— RC4 not available.