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

AlgorithmDescriptionFunction
Alg 2File encryption key derivation (MD5 + optional 50×)derive_file_key/2
Alg 4User password auth for R=2 (RC4 of pad constant)authenticate_user/2
Alg 5User password auth for R≥3 (RC4 with 19-step iterations)authenticate_user/2
Alg 7Owner → user password derivationderive_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)

  1. Pad the user password to 32 bytes via PasswordPad.pad/1.
  2. MD5 streaming: hash(padded_pw ++ /O ++ <<P::little-32>> ++ /ID[0]).
  3. If R ≥ 4 and encrypt_metadata == false, append <<0xFF,0xFF,0xFF,0xFF>>.
  4. If R ≥ 3, iterate MD5 × 50 on the first key_len bytes of the digest.
  5. Truncate to first key_len = Length / 8 bytes (min 5 for V1).

Algorithm 4 — User Auth for R=2

  1. Derive file key via Algorithm 2.
  2. RC4-encrypt the 32-byte padding constant with the file key.
  3. Compare result byte-for-byte with /U (32 bytes).

Algorithm 5 — User Auth for R≥3

  1. Derive file key via Algorithm 2.
  2. MD5(padding_constant ++ /ID[0]) → 16 bytes.
  3. RC4-encrypt those 16 bytes with the file key.
  4. For i in 1..19: XOR each byte of file_key with i → RC4-encrypt previous result.
  5. Compare final 16 bytes to the first 16 bytes of /U.

Algorithm 7 — Owner → User Password

  1. Pad the owner password to 32 bytes.
  2. MD5 it; if R ≥ 3, iterate MD5 × 50 on the full 16 bytes.
  3. Truncate to key_len bytes → this becomes the RC4 key.
  4. For R=2: RC4-decrypt /O once.
  5. For R≥3: 20 iterative RC4 passes in reverse order (i=19 down to 0), XORing each key byte with i.
  6. The result is the padded user password. Feed it to authenticate_user/2.

Spec references

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

Link to this function

authenticate_owner(password, handler)

View Source
@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.
Link to this function

authenticate_user(password, handler)

View Source
@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_key is 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).
Link to this function

decrypt_stream(bytes, stream_dict, obj_num, gen_num, handler)

View Source
@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_key populated.

Returns

  • {:ok, plaintext} — successfully decrypted.
  • {:error, :encrypted_unsupported_handler} — RC4 not available at runtime.

Spec references

Link to this function

decrypt_string(bytes, obj_num, gen_num, handler)

View Source
@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_key populated.

Returns

  • {:ok, plaintext} — successfully decrypted.
  • {:error, :encrypted_unsupported_handler} — RC4 not available at runtime.

Spec references

Link to this function

derive_file_key(password, handler)

View Source
@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_metadata populated.

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.

Link to this function

derive_user_from_owner(owner_password, handler)

View Source
@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

  1. Pad owner password to 32 bytes.
  2. MD5; for R≥3, iterate 50 times.
  3. Truncate to key_len bytes → RC4 key.
  4. For R=2: RC4-decrypt /O once.
  5. 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 if PasswordPad.pad(user_password) had been called).
  • {:error, :encrypted_unsupported_handler} — RC4 not available.