PDF Encryption and Permissions: Pdf.Protection
Encrypt a PDF with passwords (RC4 to AES-256-GCM) or for certificate recipients, restrict what users may do, change passwords or permissions, remove encryption, and report a document's security — before it is loaded, as loaded, and as it will be saved.
bool Protection(string op, JsonObject jsonIn, JsonObject jsonOut)bool PreLoadCheck(string path, JsonObject jsonOut) bool PreLoadCheckBd(BinData bd, JsonObject jsonOut)
bool AddEncryptCert(Cert cert) void ClearEncryptCerts() bool SetDecryptCert(Cert cert)
- How it works: loaded and pending security
- The operations
- Quick start
- op = encrypt
- algorithmChoice
- scope
- permissions
- Passwords
- Certificate encryption
- removeEncryption, removeRestrictions, changePasswords, changePermissions
- op = getInfo
- PreLoadCheck: before loading
- Saving and signed PDFs
- Option reference
How it works: loaded and pending security
Set OwnerPassword and/or UserPassword (or call SetDecryptCert) before LoadFile; Chilkat tries the owner password first, then the user password, then the empty password, and records which one opened the file. Every Protection operation except getInfo then changes the pending security — the settings the PDF will be saved with. Nothing is encrypted or decrypted until WriteFile or WriteBd is called; if no operation changed anything, the PDF is saved with exactly the security it was loaded with.
getInfo requires owner rights: the PDF was not encrypted, or it was loaded with its owner password, or (certificate security) the recipient whose key opened it has the changeSecurity permission. Chilkat does not offer a way to ignore owner restrictions. An operation refused for lack of owner rights returns false and says why in LastErrorText. An operation that changes nothing (removeEncryption on an unencrypted PDF) is still success.
The operations
| op | Effect on the pending security | Needs owner rights |
|---|---|---|
encrypt | Replaces it entirely: method, algorithm, scope, permissions, and the passwords (read from the properties at the time of the call) or the recipients (from AddEncryptCert). | Yes, if the PDF was loaded encrypted |
removeEncryption | None: the PDF will be saved unencrypted, with no passwords or restrictions. | Yes |
removeRestrictions | Grants every permission; keeps the user password and the algorithm. With no user password the result is unencrypted, the same as removeEncryption. | Yes |
changePasswords | New user and owner passwords from the properties; algorithm, scope and permissions kept. Password security only. | Yes |
changePermissions | New permissions from jsonIn (the same permissions object as encrypt); passwords, algorithm and scope kept. | Yes |
getInfo | None; reports the loaded and pending security in jsonOut. | No |
Quick start: protect a PDF
An open password, no copying, low-quality printing — AES-256 (revision 6) by default.
Chilkat.Pdf pdf = new Chilkat.Pdf();
pdf.LoadFile("report.pdf");
pdf.UserPassword = "open-me"; // needed to open the PDF
pdf.OwnerPassword = "admin-secret"; // needed to change its security
Chilkat.JsonObject jsonIn = new Chilkat.JsonObject();
Chilkat.JsonObject jsonOut = new Chilkat.JsonObject();
jsonIn.UpdateString("permissions.print", "lowQuality");
jsonIn.UpdateBool("permissions.copy", false);
if (pdf.Protection("encrypt", jsonIn, jsonOut)) {
pdf.WriteFile("protected.pdf"); // saved as PDF 1.7, Adobe extension level 8
}
jsonIn
{
"permissions": { "print": "lowQuality", "copy": false }
}jsonOut
{
"changed": true,
"pending": {
"encrypted": true,
"method": "password",
"algorithm": "aes-256",
"pdfVersion": "1.7",
"adobeExtensionLevel": 8,
"scope": "all",
"userPasswordRequired": true,
"ownerPasswordSet": true,
"permissions": {
"print": "lowQuality", "modify": true, "copy": false,
"accessibility": true, "annotate": true, "fillForms": true, "assemble": true
}
}
}Open a protected PDF and remove its encryption
pdf.OwnerPassword = "admin-secret"; // set before loading, to get owner rights
pdf.LoadFile("protected.pdf");
if (pdf.Protection("removeEncryption", jsonIn, jsonOut)) {
pdf.WriteFile("plain.pdf");
}
op = encrypt
An empty jsonIn encrypts everything with AES-256 revision 6, using the two password properties and granting every permission.
| Member | Values (default in bold) | Description |
|---|---|---|
method | password, certificate | certificate encrypts for the recipients added with AddEncryptCert. |
algorithmChoice | see below (aes-256) | The encryption algorithm. |
scope | see below (all) | What is encrypted. |
permissions | object, see below (all allowed) | What a user without the owner password may do. |
recipientPermissions | array (none) | Certificate method only: entry n overrides permissions for the nth certificate added with AddEncryptCert. |
algorithmChoice
| Value | Algorithm | PDF version saved | Notes |
|---|---|---|---|
aes-256 | AES-256, revision 6 | 1.7 + Adobe extension level 8, or 2.0 if the PDF is already 2.0 | Default. Same encryption either way; the 1.7 header is accepted by more older tools. |
aes-256-gcm | AES-256-GCM (ISO/TS 32003) | 2.0 | Authenticated encryption; few viewers can open it yet. |
aes-128 | AES-128, revision 4 | 1.6 | |
aes-256-r5 | AES-256, Adobe revision 5 | 1.7 + Adobe extension level 3 | Deprecated; only for readers that predate revision 6. |
rc4-128 | RC4, 128-bit (revision 3) | 1.4 | Broken; legacy compatibility only. |
rc4-40 | RC4, 40-bit (revision 2) | 1.3 | Broken; legacy compatibility only. |
The version in the file header is raised to the one shown when the PDF is saved, never lowered.
jsonIn: AES-128 for an older consumer
{ "algorithmChoice": "aes-128" }jsonOut
{
"changed": true,
"pending": {
"encrypted": true, "method": "password", "algorithm": "aes-128",
"pdfVersion": "1.6", "scope": "all",
"userPasswordRequired": true, "ownerPasswordSet": true,
"permissions": { "print": "full", "modify": true, "copy": true, "accessibility": true,
"annotate": true, "fillForms": true, "assemble": true }
}
}jsonIn: authenticated encryption
{ "algorithmChoice": "aes-256-gcm" }jsonOut
{
"changed": true,
"pending": {
"encrypted": true, "method": "password", "algorithm": "aes-256-gcm",
"pdfVersion": "2.0", "isoExtensionLevel": 32003, "scope": "all",
"userPasswordRequired": true, "ownerPasswordSet": true,
"permissions": { "print": "full", "modify": true, "copy": true, "accessibility": true,
"annotate": true, "fillForms": true, "assemble": true }
}
}scope
| Value | Encrypted | Left readable | Requires |
|---|---|---|---|
all | Everything | Nothing | Any algorithm (default) |
allExceptMetadata | Everything except the XMP metadata stream | XMP metadata, so that indexers can read title, author and keywords | aes-128 or later |
attachmentsOnly | Embedded files only | Pages, metadata, everything else. Viewers ask for the password when an attachment is opened | aes-128 or later, and a non-empty UserPassword |
jsonIn: searchable metadata, protected pages
{ "scope": "allExceptMetadata", "permissions": { "copy": false } }jsonOut
{
"changed": true,
"pending": {
"encrypted": true, "method": "password", "algorithm": "aes-256",
"pdfVersion": "1.7", "adobeExtensionLevel": 8, "scope": "allExceptMetadata",
"userPasswordRequired": true, "ownerPasswordSet": true,
"permissions": { "print": "full", "modify": true, "copy": false, "accessibility": true,
"annotate": true, "fillForms": true, "assemble": true }
}
}jsonIn: a readable cover document with protected attachments
{ "scope": "attachmentsOnly" }jsonOut
{
"changed": true,
"pending": {
"encrypted": true, "method": "password", "algorithm": "aes-256",
"pdfVersion": "1.7", "adobeExtensionLevel": 8, "scope": "attachmentsOnly",
"userPasswordRequired": true, "ownerPasswordSet": true,
"permissions": { "print": "full", "modify": true, "copy": true, "accessibility": true,
"annotate": true, "fillForms": true, "assemble": true }
}
}permissions
Every permission is granted by default; name the ones to restrict.
| Member | Values (default in bold) | Description |
|---|---|---|
print | none, lowQuality, full | lowQuality is honored from rc4-128 on. |
modify | true, false | Changes other than those covered below. |
copy | true, false | Copying and extracting text and graphics (this is what ExtractText checks). |
accessibility | true, false | Extraction for screen readers. PDF 2.0 readers always allow it, so it has no effect with aes-256 or aes-256-gcm. |
annotate | true, false | Add or change annotations; also allows filling forms. |
fillForms | true, false | Fill existing form fields even when annotate is false. |
assemble | true, false | Insert, delete and rotate pages, create bookmarks. |
changeSecurity | true, false | Certificate method only: the recipient has owner rights (may change or remove the security) and every other permission is implied. The encrypting program normally gives its own certificate this permission so that the file can be managed later. |
With rc4-40 only print (as none or full), modify, copy and annotate exist; the rest are ignored.
jsonIn: read-only distribution copy
{
"permissions": {
"print": "full",
"modify": false,
"copy": false,
"annotate": false,
"fillForms": false,
"assemble": false
}
}jsonOut
{
"changed": true,
"pending": {
"encrypted": true, "method": "password", "algorithm": "aes-256",
"pdfVersion": "1.7", "adobeExtensionLevel": 8, "scope": "all",
"userPasswordRequired": true, "ownerPasswordSet": true,
"permissions": { "print": "full", "modify": false, "copy": false, "accessibility": true,
"annotate": false, "fillForms": false, "assemble": false }
}
}jsonIn: a form people may fill but not alter
{
"permissions": {
"modify": false,
"annotate": false,
"fillForms": true,
"assemble": false
}
}jsonOut
{
"changed": true,
"pending": {
"encrypted": true, "method": "password", "algorithm": "aes-256",
"pdfVersion": "1.7", "adobeExtensionLevel": 8, "scope": "all",
"userPasswordRequired": true, "ownerPasswordSet": true,
"permissions": { "print": "full", "modify": false, "copy": true, "accessibility": true,
"annotate": false, "fillForms": true, "assemble": false }
}
}Passwords
encryptandchangePasswordsread theUserPasswordandOwnerPasswordproperties at the time of the call.- An empty
UserPasswordgives a PDF that opens without a prompt but keeps its restrictions. encryptrefuses an emptyOwnerPasswordwhenever any permission is restricted, since anyone would then have owner rights and the restrictions would mean nothing. An empty owner password is allowed when every permission is granted (a plain open-password file).aes-256andaes-256-gcm: any Unicode text, up to 127 bytes as UTF-8. All older algorithms: up to 32 Latin-1 characters;encryptfails if a password cannot be represented.- AES-256 PDFs opened with the owner password alone: the user password cannot be recovered from the file, so
changePermissionsandremoveRestrictionsare refused unlessUserPasswordwas also set when the PDF was loaded.changePasswordsandencryptwork, since they set both passwords afresh.
// An open password only: anyone who can open it has every permission.
pdf.UserPassword = "open-me";
pdf.OwnerPassword = "";
pdf.Protection("encrypt", jsonIn, jsonOut); // empty jsonIn: all permissions granted
Certificate encryption
With "method": "certificate", the PDF is encrypted for one or more recipients, each identified by a certificate. Only the public certificate is needed to encrypt; a recipient opens the PDF with the matching certificate and private key.
| Method | Purpose |
|---|---|
AddEncryptCert(cert) | Adds a recipient. Call once per recipient before encrypt. The nth certificate added corresponds to recipientPermissions[n-1]. The certificates stay in effect for later encrypt calls until cleared. |
ClearEncryptCerts() | Removes every recipient certificate. |
SetDecryptCert(cert) | Sets the certificate, with its private key, used to open a certificate-encrypted PDF. Call it before LoadFile. The certificate must be one of the PDF's recipients (see PreLoadCheck) and have its private key available — loaded from a PFX/.p12, or installed in the Windows certificate store. |
Example: encrypt for two recipients, then open as one of them
Chilkat.Cert alice = new Chilkat.Cert();
alice.LoadFromFile("alice.cer"); // the public certificate is enough to encrypt
Chilkat.Cert bob = new Chilkat.Cert();
bob.LoadFromFile("bob.cer");
pdf.AddEncryptCert(alice);
pdf.AddEncryptCert(bob);
jsonIn.UpdateString("method", "certificate");
jsonIn.UpdateBool("recipientPermissions[0].changeSecurity", true); // Alice manages security
jsonIn.UpdateBool("recipientPermissions[1].modify", false); // Bob cannot modify
pdf.Protection("encrypt", jsonIn, jsonOut);
pdf.WriteFile("for-alice-and-bob.pdf");
// Later, Alice opens it with her certificate and private key:
Chilkat.Cert aliceKey = new Chilkat.Cert();
aliceKey.LoadPfxFile("alice.pfx", "pfx-password");
Chilkat.Pdf pdf2 = new Chilkat.Pdf();
pdf2.SetDecryptCert(aliceKey);
pdf2.LoadFile("for-alice-and-bob.pdf");
jsonIn
{
"method": "certificate",
"permissions": { "copy": false },
"recipientPermissions": [
{ "changeSecurity": true },
{ "modify": false }
]
}jsonOut
{
"changed": true,
"pending": {
"encrypted": true,
"method": "certificate",
"algorithm": "aes-256",
"pdfVersion": "1.7",
"adobeExtensionLevel": 8,
"scope": "all",
"recipients": [
{ "issuer": "CN=Example CA,O=Example", "serialNumber": "1A2B3C",
"permissions": { "print": "full", "modify": true, "copy": true, "accessibility": true,
"annotate": true, "fillForms": true, "assemble": true, "changeSecurity": true } },
{ "issuer": "CN=Example CA,O=Example", "serialNumber": "4D5E6F",
"permissions": { "print": "full", "modify": false, "copy": true, "accessibility": true,
"annotate": true, "fillForms": true, "assemble": true, "changeSecurity": false } }
]
}
}A recipientPermissions entry starts from everything allowed and applies only the members it names; it replaces permissions for that recipient rather than adding to it.
removeEncryption, removeRestrictions, changePasswords, changePermissions
These operations take no options except changePermissions, which takes the permissions object. Each returns changed and the pending security.
Example: lift the restrictions, keep the open password
pdf.UserPassword = "open-me";
pdf.OwnerPassword = "admin-secret";
pdf.LoadFile("protected.pdf");
pdf.Protection("removeRestrictions", jsonIn, jsonOut);
pdf.WriteFile("unrestricted.pdf");
jsonIn
{ }jsonOut
{
"changed": true,
"pending": {
"encrypted": true, "method": "password", "algorithm": "aes-256",
"pdfVersion": "1.7", "adobeExtensionLevel": 8, "scope": "all",
"userPasswordRequired": true, "ownerPasswordSet": true,
"permissions": { "print": "full", "modify": true, "copy": true, "accessibility": true,
"annotate": true, "fillForms": true, "assemble": true }
}
}Example: new passwords
pdf.OwnerPassword = "old-admin-secret";
pdf.LoadFile("protected.pdf");
pdf.UserPassword = "new-open-me"; // the new passwords are read at the time of the call
pdf.OwnerPassword = "new-admin-secret";
pdf.Protection("changePasswords", jsonIn, jsonOut);
pdf.WriteFile("rekeyed.pdf");
Example: new permissions only
jsonIn
{ "permissions": { "print": "none", "copy": false } }jsonOut
{
"changed": true,
"pending": {
"encrypted": true, "method": "password", "algorithm": "aes-256",
"pdfVersion": "1.7", "adobeExtensionLevel": 8, "scope": "all",
"userPasswordRequired": true, "ownerPasswordSet": true,
"permissions": { "print": "none", "modify": true, "copy": false, "accessibility": true,
"annotate": true, "fillForms": true, "assemble": true }
}
}pdf.Protection("changePermissions", jsonIn, jsonOut);
pdf.WriteFile("no-print.pdf");
op = getInfo
Reports two states of the loaded PDF: loaded (the security it was loaded with) and pending (the security it will be saved with), and changed, whether they differ. Passwords are never returned.
{
"changed": true,
"loaded": {
"encrypted": true,
"method": "password",
"algorithm": "aes-128",
"pdfVersion": "1.6",
"scope": "all",
"userPasswordRequired": true,
"ownerPasswordSet": true,
"openedWith": "owner",
"permissions": { "print": "lowQuality", "modify": false, "copy": false,
"accessibility": true, "annotate": false, "fillForms": true, "assemble": false }
},
"pending": {
"encrypted": true,
"method": "password",
"algorithm": "aes-256",
"pdfVersion": "1.7",
"adobeExtensionLevel": 8,
"scope": "all",
"userPasswordRequired": true,
"ownerPasswordSet": true,
"permissions": { "print": "full", "modify": false, "copy": false,
"accessibility": true, "annotate": false, "fillForms": true, "assemble": false }
}
}
| Member | Meaning |
|---|---|
encrypted | An unencrypted state is just { "encrypted": false, "openedWith": "notEncrypted" }. |
method, algorithm, scope | As for encrypt. |
pdfVersion, adobeExtensionLevel, isoExtensionLevel | The header version and the extension level, when one applies (isoExtensionLevel 32003 for AES-256-GCM). |
userPasswordRequired | Whether a password is needed to open the PDF. |
ownerPasswordSet | False when the owner password is empty, which gives anyone owner rights. |
openedWith | In loaded only: owner, user, emptyPassword, certificate, notOpened, or notEncrypted. |
permissions | Password method: the permissions object. |
recipients[i] | Certificate method: each recipient's issuer and serialNumber, with its permissions (including changeSecurity). In loaded, only the recipient that opened the PDF has readable permissions; in pending, every recipient shows its permissions. |
changed | When false, saving keeps the original security. |
PreLoadCheck: before loading
PreLoadCheck(path, jsonOut) and PreLoadCheckBd(bd, jsonOut) report what can be known about a file's encryption before it is loaded, so that an application can decide which password or certificate it needs. Nothing is loaded into the object and no password is needed. They return false only when the file cannot be read or is not a PDF; an unencrypted PDF returns true with encrypted false.
Chilkat.JsonObject info = new Chilkat.JsonObject();
pdf.PreLoadCheck("incoming.pdf", info);
if (info.BoolOf("encrypted") && info.StringOf("method") == "certificate") {
pdf.SetDecryptCert(myCertWithPrivateKey); // one of info.recipients[]
} else if (info.BoolOf("openPasswordRequired")) {
pdf.UserPassword = AskUserForPassword();
}
pdf.LoadFile("incoming.pdf");
Call
pdf.PreLoadCheck("protected.pdf", jsonOut);jsonOut: a password-protected file
{
"encrypted": true,
"method": "password",
"algorithm": "aes-256",
"pdfVersion": "1.7",
"adobeExtensionLevel": 8,
"scope": "all",
"openPasswordRequired": false,
"permissions": { "print": "full", "modify": false, "copy": true,
"accessibility": true, "annotate": false,
"fillForms": true, "assemble": false }
}Call
pdf.PreLoadCheck("for-alice-and-bob.pdf", jsonOut);jsonOut: a certificate-encrypted file
{
"encrypted": true,
"method": "certificate",
"algorithm": "aes-256",
"pdfVersion": "1.7",
"adobeExtensionLevel": 8,
"scope": "all",
"recipients": [
{ "issuer": "CN=Example CA,O=Example", "serialNumber": "1A2B3C" },
{ "issuer": "CN=Example CA,O=Example", "serialNumber": "4D5E6F" }
]
}| Member | Meaning |
|---|---|
encrypted | Whether the file is encrypted at all. When false, nothing else is returned. |
method | password, or certificate (a certificate and private key are needed to open it). |
algorithm | Same values as algorithmChoice. |
pdfVersion | Header version; adobeExtensionLevel or isoExtensionLevel is added when the file declares one. |
scope | all, allExceptMetadata or attachmentsOnly. |
openPasswordRequired | Password method only: false when the empty user password opens the file (restrictions may still apply). |
permissions | Password method only: readable without any password. |
recipients | Certificate method only: issuer and serial number of each recipient, which tells the program which certificate it needs. |
Saving and signed PDFs
- Saving (
WriteFile/WriteBd) applies the pending security. Unchanged security keeps the original algorithm, passwords, permissions, scope and recipients — even when the PDF was opened with only the user password, because either password yields the file's encryption key. Changed security re-encrypts the whole file. - The PDF version is raised to what the pending algorithm requires and never lowered.
- Signed PDFs. An appended update must keep the original encryption, so any security change means rewriting the whole file, which invalidates every signature. With the
PreserveSignaturesproperty true (the default), every operation exceptgetInfofails on a signed PDF; with it false, the change is allowed and the signatures are invalidated. - Other features respect the permissions: ExtractText needs the copy permission (or owner rights), and TextReplace, Watermark and Optimize need modify.
Option reference (op = encrypt)
{
"method": "password", // password | certificate
"algorithmChoice": "aes-256", // rc4-40 | rc4-128 | aes-128 | aes-256-r5 | aes-256 | aes-256-gcm
"scope": "all", // all | allExceptMetadata | attachmentsOnly
"permissions": {
"print": "full", // none | lowQuality | full
"modify": true,
"copy": true,
"accessibility": true,
"annotate": true,
"fillForms": true,
"assemble": true,
"changeSecurity": false // certificate method only
},
"recipientPermissions": [ { ...same members... }, ... ] // certificate method only
}
Reference documentation: Pdf.Protection, PreLoadCheck, AddEncryptCert, SetDecryptCert, OwnerPassword, UserPassword. Related: Pdf class overview.
New in Chilkat v11.7.0.