defmodule Pdf.Reader.Encryption.PasswordPad do @moduledoc """ Provides the canonical 32-byte PDF password-padding constant and a helper to pad (or truncate) an arbitrary password binary to exactly 32 bytes. The padding constant is defined verbatim in the PDF specification and is used in Algorithm 2 (file encryption key derivation) as well as Algorithms 3–5 (owner/user password authentication) for Standard Security Handler revisions R=2 through R=4. ## Usage ```elixir padded = PasswordPad.pad(user_supplied_password) # padded is always exactly 32 bytes ``` ## Spec references - PDF 1.7 (ISO 32000-1) § 7.6.3.3 Algorithm 2 (step a — padding): https://opensource.adobe.com/dc-acrobat-sdk-docs/standards/pdfstandards/pdf/PDF32000_2008.pdf - RFC 1321 (MD5 — used alongside this constant in Algorithm 2): 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 (CipherTransformFactory._defaultPasswordBytes — cross-check verified) """ # --------------------------------------------------------------------------- # The 32-byte padding constant from PDF 1.7 § 7.6.3.3. # # Verified against Mozilla pdf.js CipherTransformFactory._defaultPasswordBytes: # [0x28,0xbf,0x4e,0x5e,0x4e,0x75,0x8a,0x41,0x64,0x00,0x4e,0x56, # 0xff,0xfa,0x01,0x08,0x2e,0x2e,0x00,0xb6,0xd0,0x68,0x3e,0x80, # 0x2f,0x0c,0xa9,0xfe,0x64,0x53,0x69,0x7a] # --------------------------------------------------------------------------- @pad_constant << 0x28, 0xBF, 0x4E, 0x5E, 0x4E, 0x75, 0x8A, 0x41, 0x64, 0x00, 0x4E, 0x56, 0xFF, 0xFA, 0x01, 0x08, 0x2E, 0x2E, 0x00, 0xB6, 0xD0, 0x68, 0x3E, 0x80, 0x2F, 0x0C, 0xA9, 0xFE, 0x64, 0x53, 0x69, 0x7A >> # --------------------------------------------------------------------------- # Public API # --------------------------------------------------------------------------- @doc """ Returns the canonical 32-byte PDF password-padding constant. Defined in PDF 1.7 § 7.6.3.3 as a fixed magic value used to pad short passwords before they are fed into MD5 hashing. """ @spec constant() :: <<_::256>> def constant, do: @pad_constant @doc """ Pads or truncates `password` to exactly 32 bytes per PDF 1.7 § 7.6.3.3. - If `password` is shorter than 32 bytes: appends bytes from `@pad_constant` until the result is 32 bytes long. - If `password` is exactly 32 bytes: returns it unchanged. - If `password` is longer than 32 bytes: truncates to the first 32 bytes. """ @spec pad(binary()) :: <<_::256>> def pad(password) when is_binary(password) do n = byte_size(password) cond do n >= 32 -> binary_part(password, 0, 32) n == 0 -> @pad_constant true -> # Append the first (32 - n) bytes of the padding constant take = 32 - n password <> binary_part(@pad_constant, 0, take) end end end