Encryption
Sign password-protected PDFs and password-protect the signed output — AES-128 or AES-256, with owner-enforced permissions
Encryption touches signing on two independent sides, and Mudrit keeps them separate:
- Encrypted input — the PDF you're signing is already password-protected. The open password is
a property of the document, so it rides on the input via
PdfReader, not on the config. - Encrypted output — you want the signed result to stay (or become) password-protected. That
is a signing decision, so it lives on
SignConfigvia.encrypt(…).
By default the two are unrelated: signing an encrypted input produces an unencrypted output unless you also ask for output encryption.
Encrypted input
A password belongs to the input document, not to how you sign it — so it travels with the input
through PdfReader, and SignConfig stays purely about the signature. Mudrit decrypts the input
in memory before signing.
use mudrit_pdfsign::prelude::*;
// from a file…
let signed = sign_pdf(PdfReader::open("locked.pdf")?.password("asd"), &signer, &cfg)?;
// …or from bytes already in memory
let signed = sign_pdf(PdfReader::from(bytes).password("asd"), &signer, &cfg)?;PdfReader also offers .maybe_password(Option<…>) when the password is already wrapped in an
Option.
Wrong or missing password fails loud
Signing an encrypted PDF without a password, or with the wrong one, returns a clear
Error::Encryption instead of producing a broken signature. Mudrit never silently signs a document
it couldn't fully decrypt.
The bundled samples/test_protected.pdf has open password asd — use it to exercise the encrypted
paths (see the sign_protected example).
Encrypted output
Where the input open password is a PdfReader concern, output encryption is a signing concern, so
it lives on the builder. The document is re-encrypted before signing, so the output is both
signed and password-protected — the signature is computed over the encrypted bytes (its /Contents
stays exempt, per the PDF spec).
// a new user (open) password; AES-128 by default
let cfg = SignConfig::builder()
.place("F", [350, 60, 560, 160])?
.encrypt("newpw")
.build();// reuse the input's own open password on the output ("keep the same password")
let cfg = SignConfig::builder()
.place("F", [350, 60, 560, 160])?
.keep_password() // errors if the input had no password
.build();// distinct owner (permissions) password, AES-256, restricted permissions
let cfg = SignConfig::builder()
.place("F", [350, 60, 560, 160])?
.encrypt(
OutputEncryption::password("user")
.owner_password("owner")
.cipher(Cipher::Aes256)
.permissions(Permissions::all().copy(false).modify(false)),
)
.build();.encrypt(_) accepts either a password string (a shorthand for OutputEncryption::password(…),
AES-128) or a full OutputEncryption. The OutputEncryption fields:
Prop
Type
Choosing the cipher
Prop
Type
Output defaults to AES-128 (AESV2). Choose AES-256 (AESV3 / V5, revision R6 per ISO
32000-2 §7.6.4.3.4) with Cipher::Aes256:
.encrypt(OutputEncryption::password("pw").cipher(Cipher::Aes256)) // AES-256 (R6)Both open correctly in Adobe Acrobat / Reader — the R6 /Encrypt dictionary is emitted with the
/Length and crypt-filter hints Acrobat requires (see the aes256_demo example).
Permissions
Permissions controls what a viewer that opened with the user password may do. The restrictions
are enforced by the owner password — the owner password lifts them, so set a distinct owner
password when you rely on the flags. Start from a preset and toggle:
let p = Permissions::all().print(true).copy(false).modify(false); // view + print only| Preset / toggle | Effect |
|---|---|
Permissions::all() | All permissions granted (the default — unrestricted) |
Permissions::none() | View only with the user password |
.print(bool) | Print (low resolution unless print_high_quality) |
.modify(bool) | Modify contents |
.copy(bool) | Copy / extract text and graphics |
.annotate(bool) | Add / modify annotations and fill forms |
.fill_forms(bool) | Fill form fields (even if annotate is off) |
.accessibility(bool) | Extract text/graphics for accessibility |
.assemble(bool) | Insert / rotate / delete pages |
.print_high_quality(bool) | Print at high resolution |
Permissions need an owner password
Restrictions set under the user password are trivially lifted by the owner password — and when no
owner password is given it defaults to the user password. Set
OutputEncryption::password(u).owner_password(o).permissions(…) so the restrictions can't be
bypassed by the same password that opens the file.
Works with every method — and with certification
Encryption works with every Method, including MultiChained: each
chained revision is encrypted with the same file key, so the whole multi-signature document
stays protected. Combining certification with encryption keeps the signature DocMDP-valid — a
certified, encrypted output is both.
Protected → protected round-trip
The common real-world case is a document that arrives protected and must leave protected. Pass the
open password on the input and .keep_password() on the config:
let cfg = SignConfig::builder()
.place("F", [350, 60, 560, 160])?
.keep_password() // same password in, same password out
.build();
let signed = sign_pdf(PdfReader::open("locked.pdf")?.password("asd"), &signer, &cfg)?;Re-signing an already-signed and encrypted document works the same way — the appended revision is encrypted with the document's own key, so the output stays protected with the same password and cipher (any cipher, including AES-256):
let resigned = sign_pdf(PdfReader::from(signed_protected).password("asd"), &signer, &cfg)?;Output encryption cannot be combined with PDF/A — the standard forbids
encryption, so .pdfa(…) and .encrypt(…) together are rejected.
Next
Document Timestamps
Append a standalone /DocTimeStamp archive revision — the PAdES-B-LTA archive stamp — and re-apply it to renew long-term validity before a timestamp certificate expires
Metadata
Signature properties, signing time and time zone, and document /Info + XMP — all grouped into one Metadata struct