Skip to content

6. Security: encryption & signatures

Encrypting documents and applying digital signatures.

PDF security comes in two distinct flavours, and they answer different questions. Encryption asks who may open this file and what may they do with it (it is about access control. Digital signatures ask who produced this exact byte stream and has it changed since), it is about authenticity and integrity. This chapter covers both, and the one rule that ties encryption to archival output.

Both features go through PdfDocumentRenderer and PdfSaveOptions, not the one-line SavePdf extension: they need the options object.

Encryption settings live on PdfSaveOptions.SecuritySettings, a PdfSecuritySettings (namespace Rivoli.Pdf.Security):

using Rivoli.Pdf;
using Rivoli.Pdf.Security;
var renderer = new PdfDocumentRenderer("1.7");
renderer.RenderToFile(doc, "secure.pdf", new PdfSaveOptions
{
SecuritySettings = new PdfSecuritySettings
{
UserPassword = "open-me",
OwnerPassword = "owner-secret",
Algorithm = PdfEncryptionAlgorithm.Aes256,
Permissions = PdfPermissions.Print | PdfPermissions.CopyContents,
EncryptMetadata = true,
},
});

PDF defines two passwords, and which ones you set changes the behaviour:

Set…Effect
Owner password onlyAnyone can open it; permissions are enforced
User + owner passwordThe user password is required to open; permissions are enforced; the owner password lifts all restrictions

The owner password is the master key: it bypasses the permission flags. The user password gates opening. PdfSecuritySettings validates this for you: encryption requires an owner password, both passwords cap at 127 characters, and the user and owner passwords must differ (if they match, viewers treat it as owner-only). Two factory helpers cover the common shapes:

// Open freely, but restrict what readers can do:
var restricted = PdfSecuritySettings.CreateWithRestrictions(
ownerPassword: "owner-secret",
permissions: PdfPermissions.Print);
// Require a password to open at all:
var locked = PdfSecuritySettings.CreatePasswordProtected(
userPassword: "open-me",
ownerPassword: "owner-secret",
permissions: PdfPermissions.Print | PdfPermissions.ExtractForAccessibility);

PdfEncryptionAlgorithm selects the cipher:

ValueNotes
NoneNo encryption (the default for output without SecuritySettings)
Aes128AES-128, broad viewer support (PDF 1.6+)
Aes256AES-256, strongest; the default when you construct PdfSecuritySettings

Match your PdfVersion to the algorithm: AES-256 wants a 1.7-class output.

PdfPermissions is a [Flags] enum; combine the operations you want to allow. Permissions are advisory: standard-compliant viewers honour them, but they are not a hard cryptographic barrier on a determined user.

FlagAllows
NoneNothing (most restrictive)
PrintPrinting
PrintHighQualityHigh-resolution printing
ModifyContentsEditing page content
CopyContentsCopying / extracting text and graphics
ModifyAnnotationsEditing annotations and form fields
FillFormsFilling form fields
ExtractForAccessibilityExtraction for assistive technology
AssembleDocumentInsert / rotate / delete pages
AllEverything (least restrictive)
Permissions = PdfPermissions.Print | PdfPermissions.PrintHighQuality
| PdfPermissions.ExtractForAccessibility,

By default document metadata (title, author, …) stays in the clear so indexers and search can read it. Set EncryptMetadata = true to encrypt it along with the content.

This is a hard rule, not a guideline: a PDF/A document may not be encrypted. The archival profile forbids encryption outright, so Rivoli PDF refuses to produce both at once. PdfSaveOptions.Validate() throws an InvalidOperationException if you set a non-None PdfAMode together with SecuritySettings:

// This combination is rejected:
new PdfSaveOptions
{
PdfAMode = PdfAMode.PdfA1b,
SecuritySettings = new PdfSecuritySettings { OwnerPassword = "x" },
}.Validate(); // throws: disable one or the other

Decide up front: archival or encrypted, never both. PDF/A is covered in chapter 7.

Signing is a distinct, post-render step. You render the document to a PDF, then sign that file: signatures are applied as an incremental update so the original bytes are preserved and covered by the signature’s byte range. The entry point is PdfSigner (namespace Rivoli.Pdf.Signatures), driven by a PdfSignatureInfo carrying your certificate and metadata:

using System.Security.Cryptography.X509Certificates;
using Rivoli.Pdf.Signatures;
var cert = new X509Certificate2("signer.pfx", "cert-password");
var info = PdfSignatureInfo.Create(cert, reason: "I approve this document");
info.Location = "London, UK";
info.ContactInfo = "approvals@example.com";
var signer = new PdfSigner(info);
signer.SignDocument("secure.pdf", "secure-signed.pdf");

PdfSignatureInfo exposes Certificate, Reason, Location, ContactInfo, FieldName, SigningTime, and IncludeCertificateChain; its Validate() runs when you construct the PdfSigner. SignDocument has both file-path and Stream overloads.

PdfSigner fills a signature field you placed yourself. Declare a SignatureField when building the document (see chapter 5), then name it in PdfSignatureInfo.FieldName:

document.FormFields.Add(new SignatureField("ApproverSignature")
{
Rect = new Rectangle(72, 96, 272, 160),
PageIndex = 0
});
// ... render the document, then sign it:
var info = PdfSignatureInfo.Create(cert, reason: "Approved");
info.FieldName = "ApproverSignature"; // fills the field you placed
new PdfSigner(info).SignDocument("report.pdf", "report-signed.pdf");

The field keeps its position, page, and appearance; signing only adds the signature value. If FieldName names no existing field, an invisible signature field is created with that name. If you leave FieldName null, the first free Signature{N} name is used, so signing an already-signed document adds a second, distinctly named signature rather than colliding with the first. Naming a field that is already signed throws.