Encryption and Signatures
Generated PDFs can be password-protected with permissions, and the parser/editor accept passwords for protected input. Detached PKCS#7/CMS digital signatures are supported with caller-supplied signing.
Password protection
import { createDocument, parseDocument } from "@barrierbreak/a11ydocs-pdf";
const doc = createDocument({
encryption: {
algorithm: "aes-256",
userPassword: "reader",
ownerPassword: "owner-secret",
permissions: {
printing: false,
modifying: false,
copying: false,
annotating: false
}
}
});
const bytes = doc.toUint8Array();
const parsed = parseDocument(bytes, { password: "reader" });
Algorithms: "rc4-40" (legacy compatibility), "rc4-128" (default), "aes-128", "aes-256", "aes-256-r6" (ISO 32000-2 / PDF 2.0 hardened key derivation, recommended for new documents).
Browser support
All algorithms work in the browser. When Node's crypto module is unavailable, the library transparently uses a built-in, zero-dependency AES (CBC) and SHA-2 implementation, so AES encryption no longer requires a Node runtime. Output is byte-for-byte compatible with the Node path, so a document encrypted in the browser opens with the same password anywhere.
// Runs identically in Node and the browser.
const doc = createDocument({
encryption: { algorithm: "aes-256-r6", userPassword: "reader", ownerPassword: "owner-secret" }
});
doc.addPage({ size: "A4" }).text("Confidential", { x: 48, y: 780 });
const bytes = doc.toUint8Array();
// Browser download:
const blob = new Blob([bytes], { type: "application/pdf" });
const url = URL.createObjectURL(blob);
The only browser requirement is a secure-random source for the AES IV and document key. Modern browsers provide crypto.getRandomValues; the library uses it automatically. The pure-JS AES/SHA-2 fallback is not constant-time — AES PDF encryption is a deterrent, not a strong confidentiality boundary, regardless of runtime.
Metadata-Only Encryption
Encrypt document content but leave metadata (title, author, XMP) in plaintext so search engines and indexers can read it:
const doc = createDocument({
encryption: {
algorithm: "aes-256",
userPassword: "reader",
ownerPassword: "owner-secret",
encryptMetadata: false
}
});
Permissions
| Flag | Meaning |
|---|---|
printing | Allow printing |
modifying | Allow document modification |
copying | Allow text/image copy |
annotating | Allow annotation creation |
fillingForms | Allow form fill |
accessibility | Allow accessibility extraction |
assembling | Allow page assembly |
highQualityPrinting | Allow full-resolution print |
Omit permissions to grant all rights.
Complete Permission Bits
All available permission flags used in full:
permissions: {
printing: true,
highQualityPrinting: false,
modifying: true,
copying: true,
annotating: true,
fillingForms: true,
accessibility: true,
assembling: false
}
Cross-Version Encryption Upgrade
Call setEncryption() on an editable document to upgrade legacy RC4 encryption to AES:
const editable = editDocument(rc4Bytes, { password: "reader" });
editable.setEncryption({ algorithm: "aes-256", userPassword: "reader" });
const upgraded = editable.toUint8Array();
Empty Password
An empty string "" is a valid user password—useful when you only want an owner password to restrict permissions:
encryption: {
algorithm: "aes-256",
userPassword: "",
ownerPassword: "admin-secret"
}
Readers can open the PDF without entering a password, but permissions are enforced.
Certificate-Based Encryption
Encrypt for specific recipients using X.509 certificates:
import { readFileSync } from "node:fs";
const cert = readFileSync("recipient.pem");
const doc = createDocument({
encryption: doc.encryptForRecipients([cert], {
permissions: { printing: true, modifying: false }
})
});
Default Permissions for Missing /P
PDFs missing the /P permissions entry are handled gracefully—the library defaults to granting all rights when no permissions dictionary is present.
Editing protected PDFs
editDocument accepts { password } and re-encrypts with the original parameters when serializing:
const editable = editDocument(protectedBytes, { password: "reader" });
editable.updateInfo({ title: "Revised" });
const updated = editable.toUint8Array();
Detached signatures
Generate a /ByteRange and let your signing code produce the CMS blob:
const signed = createDocument({
signature: {
fieldName: "approval",
reason: "Approved for release",
location: "Berlin",
contactInfo: "ops@example.com",
sign: (byteRange) => signDetachedCms(byteRange)
}
});
signed.addPage().text("Print proof", { x: 56, y: 760 });
const bytes = signed.toUint8Array();
sign receives the bytes covered by /ByteRange and must return a DER-encoded CMS SignedData value. Use Node crypto, node-forge, or any PKCS#7 toolkit. The serializer pads /Contents to fit the returned blob.
For the full workflow — incremental signing of existing PDFs, a working node-forge signer, placeholderBytes sizing, visible signature fields, and verification — see Digital Signatures.