Technical Article

AES-256 PDF Encryption in Delphi: HotPDF Setup and Pitfalls

The support ticket read: "Statements open fine on our desktops, but the customer's records-management system flags every file as unreadable." The PDFs were encrypted with AES-256, exactly as the contract required. The root cause sat in a single boolean: the documents were written with encryption revision 6, the PDF 2.0 variant from ISO 32000-2, and the customer's archive toolchain only understood revision 5. Same algorithm, same key length, same passwords — a different key-derivation handshake, and a hard failure that never appeared on any development machine running current Acrobat.

UK teams should align this hotpdf aes256 encryption delphi security workflow with local governance, audit, and data quality requirements before production release

For UK environments, align the hotpdf aes256 encryption delphi security implementation with local quality gates that include governance approval, versioned fixture baselines, and evidence retention for every publication candidate. Keep an explicit review log for accessibility, redaction policy, and data residency checks so your deployment audit is repeatable without changing output binaries

UK technical governance addendum

Encryption is one of the few PDF features where a wrong configuration produces no visible symptom locally and a total failure remotely. HotPDF, a native VCL PDF component for Delphi and C++Builder, exposes the full ISO 32000 protection model through a handful of properties; this article maps each property to the decision it actually controls.

What the two passwords actually promise

PDF encryption defines two credentials with different jobs, and conflating them is the most common design error in protected-output code. The user password gates decryption: without it (or the owner password) a conforming reader cannot reconstruct the file key, and the content is cryptographically unreadable. The owner password gates the permission settings: a reader that receives it grants full access regardless of any restriction flags.

The permissions themselves — printing, content extraction, form filling — are a weaker kind of promise. They are flags a viewer reads and agrees to honor (ISO 32000-2 §7.6.4). The encryption protects the bytes; the permission flags merely instruct conforming software. A user who legitimately opens a document with the user password has the decrypted content in memory, so 'no copy' and 'no print' are policy signals to well-behaved viewers, not cryptographic guarantees. Build the threat model around that split: confidentiality comes from the user password, whilst permission flags shape behaviour in mainstream viewers and nothing more.

Configuration order: everything before BeginDoc

HotPDF establishes the encryption dictionary and file key when BeginDoc runs. Every protection property must therefore be assigned first — above all CryptKeyLength, which selects the scheme through the THPDFKeyType values k40, k128, aes128, and aes256. Assigning it after BeginDoc raises no exception; the document simply keeps the parameters it started with, which is exactly the kind of silent divergence that surfaces months later as a compliance finding.

var
  Pdf: THotPDF;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.FileName := 'statement.pdf';
    Pdf.ActivateProtection := True;
    Pdf.CryptKeyLength := aes256;        // must be set before BeginDoc
    Pdf.UserPassword := 'open-secret';
    Pdf.OwnerPassword := 'admin-secret';
    Pdf.UseAES256R6 := False;            // R=5: widest viewer support
    Pdf.BeginDoc;
    Pdf.CurrentPage.SetFont('Arial', [], 11);
    Pdf.CurrentPage.TextOut(50, 720, 0, 'Account statement, June 2026');
    Pdf.EndDoc;
  finally
    Pdf.Free;
  end;
end;

Passwords are UTF-8 and capped at 127 bytes, matching the ISO 32000-2 limit for the AES-256 schemes. If your password policy generates longer secrets, truncate deliberately on your side instead of letting the library and a future viewer disagree about where the cutoff lands.

Revision 5 or revision 6: one boolean, two ecosystems

UseAES256R6 chooses between the two AES-256 handshakes. With False, HotPDF writes revision 5, the AES-256 scheme introduced as an extension to PDF 1.7 and supported by roughly fifteen years of viewer releases. With True, it writes revision 6, the hardened key-derivation algorithm standardized in ISO 32000-2 for PDF 2.0 — the variant that closes the known weakness in revision 5's password verification step.

The engineering trade is compatibility against standardization. Revision 6 requires viewers built for PDF 1.7 Extension Level 3 or PDF 2.0; older archive systems, embedded renderers, and unmaintained line-of-business tools fail to open the file at all, exactly like the ticket at the top of this article. Unless a security policy explicitly names ISO 32000-2 revision 6, ship revision 5 and write the decision down — it is the safer default, and you can revisit it once your slowest-moving consumer upgrades.

The same reasoning applies one level down. THPDFKeyType still offers k40, k128, and aes128 for compatibility with older toolchains, but all three belong to maintenance work on legacy systems, not to new designs: 40-bit RC4 is breakable on commodity hardware, and the 128-bit schemes predate the AES-256 revisions that current security reviews expect to see. For any document produced in 2026, the realistic decision space is AES-256 revision 5 versus revision 6 — the older key types exist so you can reproduce historical archives, not so you can write new ones.

Permission flags without an open password

A frequent requirement is the inverse of secrecy: anyone may read the document, but printing or extraction should be restricted. The configuration is an empty user password plus a non-empty owner password — open-password mode — with the allowed operations listed in ProtectOptions.

Pdf.ActivateProtection := True;
Pdf.CryptKeyLength := aes256;
Pdf.UserPassword := '';                      // anyone can open the file
Pdf.OwnerPassword := 'rotate-me-quarterly';  // guards the permission set
Pdf.ProtectOptions := [prPrint, prPrint12bit, prExtractContent];
Pdf.BeginDoc;
// ... page content ...
Pdf.EndDoc;

The THPDFProtectOptions set covers the ISO permission bits: prPrint, prPrint12bit for high-resolution printing, prInformationCopy for general copy and extraction, prExtractContent for assistive-technology extraction, prModifyStructure, prEditAnnotations, prFillAnnotations, and prAssemble. Two of them earn specific advice. Keep prExtractContent enabled in nearly every profile — it is the bit that lets screen readers and other assistive technology reach the content, and revoking it turns a rights decision into an accessibility defect. And note that prPrint without prPrint12bit yields degraded printing in some viewers, which end users report as a rendering bug instead of a permission.

Verification is quick and worth automating into release checks: open a example of each profile's output in Acrobat and read the Security tab of Document Properties, which names the algorithm ('AES 256-bit') and itemizes the allowed operations one by one. Then repeat the open in the oldest viewer your customers actually run. The five minutes that second check costs is exactly the gap that let the revision 6 ticket at the top of this article reach production.

Removing protection from existing files

Decryption is the same property model in reverse: load the document with its credential, switch protection off, save the result.

var
  Pdf: THotPDF;
  PageCount: Integer;
begin
  Pdf := THotPDF.Create(nil);
  try
    PageCount := Pdf.LoadFromFile('encrypted.pdf', 'open-secret');
    if PageCount > 0 then
    begin
      Pdf.ActivateProtection := False;   // drop encryption on save
      Pdf.SaveLoadedDocument('plain.pdf');
    end;
  finally
    Pdf.Free;
  end;
end;

This route parses the full document, which is fine for ordinary files. For multi-hundred-megabyte inputs there is a cheaper path: DecryptFile decrypts during a file-level copy, taking a direct AES-256 rewrite path that avoids building the object tree whenever the input allows it. It belongs to the Direct File API described in the companion article on processing large PDFs from Delphi.

Constraints that interact with encryption

Archival profiles conflict outright: ISO 19005 forbids encryption in PDF/A files, so a workflow that both encrypts a document and claims PDF/A conformance is invalid by construction. When both requirements exist, deliver two artefacts — an encrypted distribution copy and an unencrypted archival copy — instead of trying to satisfy them in one file.

There is also no recovery path. PDF encryption has no escrow mechanism: a lost user password on an R5 or R6 file means brute force or nothing. Treat owner and user secrets as production credentials — generated, vaulted, rotated — never as constants in a unit, where they end up in version control and in every developer's working copy.

FAQ: protecting PDFs from Delphi

Does disabling prInformationCopy stop people from copying text?

It stops conforming viewers from offering copy commands. Anyone who can open the document already holds the plaintext in memory, so treat the flag as workflow guidance, not data-loss prevention.

Should a new project enable UseAES256R6?

Only when every consumer is verified to handle PDF 2.0 encryption. Revision 5 provides the same AES-256 content encryption with far broader viewer coverage, which explains why it is the default.

Can I change permissions on a PDF I did not create?

Yes — load it with its password through LoadFromFile, adjust ProtectOptions or the passwords, and write the result with SaveLoadedDocument, exactly as in the decryption example above.

The protection properties shown here are part of the standard HotPDF Component for Delphi and C++Builder; the product page carries the full encryption reference, including the complete permission enumeration.