Securing PDF Documents: AES-256 Encryption with TerraPDF vs Other Libraries

Securing PDF Documents: AES-256 Encryption with TerraPDF vs Other Libraries

If you're generating confidential reports, financial statements, or sensitive invoices in .NET, you need encryption. PDFs with passwords. Restricted permissions. Copy-paste protection.

Most developers reach for iText, PdfSharp, or QuestPDF. They work, but they're heavyweight, require external dependencies, or lack clean APIs for permission control.

TerraPDF changes this. With just 5 lines, you get enterprise-grade AES-256 encryption — on by default since v1.4 — plus user/owner passwords and granular permission flags. No external libraries. No licensing concerns. Zero boilerplate.

Updated for TerraPDF 2.2. AES-256 (Standard Security Handler Revision 6, SHA-2 key derivation, PDF 2.0 output) is now the default. AES-128 is still available as an opt-in legacy mode for very old readers — see Choosing the Algorithm below.

The Problem: PDF Encryption Is Messy

Standard PDF libraries make encryption tedious:

Aspect iText PdfSharp QuestPDF TerraPDF
Lines to encrypt 15+ 12+ 8+ 5
Permission control Complex flags Unclear API Basic Fine-grained
External dependencies Yes (bouncy-castle) Yes Optional No
Zero-dependency ❌ ❌ ❌ ✅
AES-128 support ✅ ✅ ✅ ✅
AES-256 support ✅ (opt-in flag) ✅ 6.x (SetEncryptionToV5) ✅ 2024.12+ ✅ Default

The TerraPDF Way: 5 Lines to Encryption

Document.Create(container =>
{
    container.Encrypt(new EncryptionOptions
    {
        UserPassword  = "confidential",    // Required to open
        OwnerPassword = "admin123",        // Bypass permissions
        Permissions   = PdfPermissions.Print | PdfPermissions.ExtractForAccessibility,
    });

    container.Page(page =>
    {
        page.Size(PageSize.A4);
        page.Margin(2, Unit.Centimetre);
        page.Content().Text("TOP SECRET FINANCIAL REPORT").Bold().FontSize(20);
        page.Content().Text("Encryption: AES-256 | User Password Protected");
    });
})
.PublishPdf("report.pdf");

That's it. The PDF opens in any viewer—Adobe Acrobat, Chrome, Edge, Firefox, Preview—with password protection enforced.

Choosing the Algorithm

You get AES-256 without asking for it. The only reason to change that is a reader old enough to predate Acrobat 9:

// Default — AES-256, Revision 6, SHA-2 key derivation, PDF 2.0 output.
container.Encrypt(new EncryptionOptions
{
    UserPassword = "open123",
    Permissions  = PdfPermissions.Print,
});

// Opt-in legacy — AES-128, Revision 4, MD5 key derivation, PDF 1.6 output.
container.Encrypt(new EncryptionOptions
{
    UserPassword = "open123",
    Permissions  = PdfPermissions.Print,
    Algorithm    = EncryptionAlgorithm.Aes128,
});
AES-256 (default) AES-128 (EncryptionAlgorithm.Aes128)
Security handler revision 6 4
Key derivation SHA-2 MD5 (PDF-mandated)
PDF version written 2.0 1.6
Reader support Acrobat 9+ (2008) and every modern viewer Everything, including very old readers

Unless you know you are shipping to a museum-piece reader, leave it alone.

Why This Matters: Permission Granularity

Traditional libraries offer generic "encrypt yes/no" options. TerraPDF gives you fine-grained control:

// View-only: No printing, no copying
container.Encrypt(new EncryptionOptions
{
    UserPassword = "readonly",
    Permissions = PdfPermissions.None,
});

// Allow printing, but not copying or modification
container.Encrypt(new EncryptionOptions
{
    UserPassword = "print_only",
    Permissions = PdfPermissions.Print,
});

// Restrict all except text extraction for accessibility
container.Encrypt(new EncryptionOptions
{
    UserPassword = "access_friendly",
    Permissions = PdfPermissions.ExtractForAccessibility,
});

// Full encryption, no user password, owner-only control
container.Encrypt(new EncryptionOptions
{
    OwnerPassword = "admin",
    Permissions = PdfPermissions.None,   // Viewer opens unlocked but can't print/copy
});

Permission flags available:

  • Print — Allow high-quality printing
  • PrintLowResolution — Degraded printing only
  • CopyText — Allow copy/paste of text and graphics
  • ModifyContents — Allow document editing
  • ModifyAnnotations — Add/modify annotations
  • FillForms — Fill form fields
  • ExtractForAccessibility — Screen reader text extraction
  • AssembleDocument — Insert/rotate/delete pages
  • All — Unrestricted
  • None — View-only

Real-World Use Cases

Scenario 1: Confidential Invoices

public byte[] GenerateConfidentialInvoice(Invoice inv)
{
    return Document.Create(container =>
    {
        // Restrict to view and print only—no copying invoice numbers
        container.Encrypt(new EncryptionOptions
        {
            UserPassword = $"inv_{inv.Number}",  // Generate per invoice
            Permissions = PdfPermissions.Print,
        });

        container.Page(page =>
        {
            page.Content().Text($"Invoice #{inv.Number}").Bold().FontSize(18);
            page.Content().Table(t => BuildInvoiceTable(t, inv));
        });
    })
    .PublishPdf();
}

Scenario 2: Internal Reports (Admin-Protected)

var reportPdf = Document.Create(container =>
{
    container.Encrypt(new EncryptionOptions
    {
        OwnerPassword = "C0rp_Admin!2025",   // Only admins can remove restrictions
        UserPassword = null,                  // Opens without password
        Permissions = PdfPermissions.Print,  // Users can only print
    });

    container.Page(page =>
    {
        page.Content().Text("Q1 2025 Financial Summary");
        page.Content().Text("⚠️ Confidential — Print-only, no copy/export allowed");
    });
})
.PublishPdf("q1-report.pdf");

Scenario 3: Public PDFs with Accessibility

container.Encrypt(new EncryptionOptions
{
    UserPassword = "open",
    Permissions = PdfPermissions.ExtractForAccessibility | PdfPermissions.Print,
});
// Users can print and screen readers can extract text,
// but copy-paste from the document is blocked

Under the Hood: Enterprise-Grade Encryption

TerraPDF doesn't cut corners on security:

Algorithm (default):

  • Cipher: AES-256 CBC
  • Key derivation: SHA-2, per Standard Security Handler Revision 6
  • IV: 16-byte random per object
  • Revision: 6 (PDF 2.0 output)

Algorithm (legacy opt-in):

  • Cipher: AES-128 CBC
  • Key derivation: MD5 (per PDF spec §7.6.3.3)
  • Revision: 4 (PDF 1.6 output — the minimum version that permits AES)

What gets encrypted:

  • ✅ Page content streams (drawing operators)
  • ✅ Image XObjects (PNG/JPEG pixel data)
  • ✅ Document metadata
  • ✅ Bookmark titles and hyperlink URIs

What doesn't (per PDF spec):

  • The encryption dictionary itself
  • Cross-reference data and trailer
  • Stream /Length values
  • File header

Zero external dependencies — uses only System.Security.Cryptography:

  • Aes for content encryption
  • SHA256 (AES-256) / MD5 (legacy AES-128) for PDF-mandated key derivation
  • RandomNumberGenerator for IV generation

Comparison: iText vs PdfSharp vs QuestPDF vs TerraPDF

iText Example (boilerplate-heavy)

using iText.Kernel.Pdf;

// Requires external license or AGPL compliance
PdfDocument pdfDocument = new PdfDocument(
    new PdfWriter("document.pdf", 
        new WriterProperties()
            .SetStandardEncryption(
                "userpass".GetBytes(Encoding.UTF8),
                "ownerpass".GetBytes(Encoding.UTF8),
                EncryptionConstants.ALLOW_PRINTING,
                EncryptionConstants.STANDARD_ENCRYPTION_128)));

Document doc = new Document(pdfDocument);
doc.Add(new Paragraph("Hello World"));
doc.Close();

Downsides:

  • Complex license (AGPL for open source)
  • External dependency (BouncyCastle)
  • Verbose flag setup — note the encryption strength is spelled out per call (STANDARD_ENCRYPTION_128, or ENCRYPTION_AES_256 if you want the stronger cipher)
  • 15+ lines for basic encryption

PdfSharp Example (clunky API)

using PdfSharp.Pdf;
using PdfSharp.Pdf.Security;

PdfDocument document = new PdfDocument();
document.SecuritySettings.UserPassword = "user";
document.SecuritySettings.OwnerPassword = "owner";
document.SecuritySettings.PermitAccessibilityExtractContent = true;
document.SecuritySettings.PermitAssembleDocument = false;
document.SecuritySettings.PermitChangeDocument = false;
document.SecuritySettings.PermitCopy = false;
document.SecuritySettings.PermitEditAnnotations = false;
document.SecuritySettings.PermitFillIn = false;
document.SecuritySettings.PermitModifyDocument = false;
document.SecuritySettings.PermitPrint = true;

PdfPage page = document.AddPage();
// ... drawing code
document.Save("document.pdf");

Downsides:

  • Verbose property assignments
  • No fluent API
  • Unclear permission semantics
  • 12+ lines of config

QuestPDF Example (close cousin)

Document.Create(container =>
{
    // QuestPDF gained native encryption in 2024.12:
    // 40-bit, 128-bit, or 256-bit AES with permission flags.

    container.Page(page =>
    {
        page.Size(PageSizes.A4);
        page.Content().Text("Encrypted Document");
    });
})
.GeneratePdf("document.pdf");

Trade-offs:

  • Encryption strength is chosen per document rather than defaulting to AES-256
  • Community MIT license only under a revenue threshold — commercial licensing applies above it
  • Pulls in a native SkiaSharp rendering dependency, so it is not zero-dependency

TerraPDF (clean, fluent, zero-dependency)

Document.Create(container =>
{
    container.Encrypt(new EncryptionOptions
    {
        UserPassword = "open123",
        OwnerPassword = "admin456",
        Permissions = PdfPermissions.Print | PdfPermissions.CopyText,
    });

    container.Page(page =>
    {
        page.Content().Text("Secure Document");
    });
})
.PublishPdf("document.pdf");

Advantages:

  • ✅ 5 lines of encryption config
  • ✅ AES-256 by default — no flag to remember
  • ✅ Fluent, readable API
  • ✅ Fine-grained permission flags
  • ✅ Zero external dependencies
  • ✅ MIT license
  • ✅ No vendor lock-in
  • ✅ All major PDF viewers supported

Common Encryption Patterns

View-Only (No Printing or Copying)

container.Encrypt(new EncryptionOptions
{
    UserPassword = "readonly",
    Permissions = PdfPermissions.None,
});

Print-Only (No Copying or Editing)

container.Encrypt(new EncryptionOptions
{
    UserPassword = "print_only",
    Permissions = PdfPermissions.Print,
});

Owner Password Only (No User Password)

container.Encrypt(new EncryptionOptions
{
    OwnerPassword = "admin_only",
    Permissions = PdfPermissions.None,
});
// Document opens freely but can't be printed/copied without owner password

Full Encryption, All Permissions

container.Encrypt(new EncryptionOptions
{
    UserPassword = "secret",
    Permissions = PdfPermissions.All,
});
// Content encrypted but full interactivity allowed

Integration Pattern: Reusable Encryption Component

public class SecureDocument : IComponent
{
    private readonly PdfSecurityLevel _level;
    private readonly string _title;

    public SecureDocument(string title, PdfSecurityLevel level)
    {
        _title = title;
        _level = level;
    }

    public void Compose(IContainer container)
    {
        // Apply encryption based on security level
        container.Encrypt(GetEncryptionOptions(_level));

        container.Page(page =>
        {
            page.Content().Text(_title).Bold().FontSize(20);
            page.Content().Text($"Security Level: {_level}");
        });
    }

    private EncryptionOptions GetEncryptionOptions(PdfSecurityLevel level) => level switch
    {
        PdfSecurityLevel.Public => new() 
        { 
            Permissions = PdfPermissions.All 
        },
        PdfSecurityLevel.Internal => new() 
        { 
            UserPassword = "internal",
            Permissions = PdfPermissions.Print | PdfPermissions.ExtractForAccessibility
        },
        PdfSecurityLevel.Confidential => new() 
        { 
            UserPassword = "confidential",
            OwnerPassword = "admin_override",
            Permissions = PdfPermissions.Print
        },
        _ => throw new ArgumentException("Unknown security level"),
    };
}

public enum PdfSecurityLevel { Public, Internal, Confidential }

// Usage
Document.Create(new SecureDocument("Financial Report", PdfSecurityLevel.Confidential))
    .PublishPdf("report.pdf");

Technical FAQ

Q: Do I need to do anything to get AES-256? A: No. It is the default. container.Encrypt(new EncryptionOptions { ... }) writes a Revision 6, PDF 2.0 encrypted document with SHA-2 key derivation. AES-128 is now the thing you have to ask for, via Algorithm = EncryptionAlgorithm.Aes128.

Q: Is AES-128 still strong enough if I need the legacy mode? A: Yes. AES-128 is NIST-approved and fine for standard business documents. The reason to prefer AES-256 is not that AES-128 is broken — it is that Revision 6 replaces the PDF spec's MD5-based key derivation with SHA-2.

Q: Can users bypass the encryption? A: No. The user password is required to open. The owner password bypasses permission flags (printing, copying) but the document remains encrypted. There's no "password bypass" mode.

Q: Do all PDF viewers support TerraPDF encryption? A: In practice, yes. AES-256 (Revision 6) has been supported since Acrobat 9 in 2008, and works in Chrome, Edge, Firefox, Preview (Mac), Foxit, and Okular. If you are targeting something older than that, switch to EncryptionAlgorithm.Aes128 and you are back to Revision 4 / PDF 1.6 compatibility.

Q: Can I generate a per-user password? A: Yes. Generate passwords dynamically:

var userPassword = $"user_{invoice.CustomerId}_{DateTime.Now:yyyyMMdd}";
container.Encrypt(new EncryptionOptions { UserPassword = userPassword });

Q: What if I only want to encrypt but not require a password? A: Leave UserPassword null and set OwnerPassword. The document opens freely but permissions are enforced.

Why Choose TerraPDF for Encryption

  1. 5-line setup — Clean, fluent API
  2. Zero dependencies — No external packages or native binaries
  3. Fine-grained permissions — 8 distinct permission flags
  4. Production-ready — Enterprise AES-256 encryption by default, AES-128 on request
  5. MIT licensed — No licensing restrictions
  6. Universal compatibility — Works in every PDF viewer
  7. Fast — Pure C# with no external calls
  8. Maintainable — Easy to read, test, and update

Conclusion

If you're building .NET applications that generate confidential PDFs—financial reports, invoices, legal documents, medical records—you need encryption.

TerraPDF delivers it with less code, fewer dependencies, and more control than iText, PdfSharp, or QuestPDF.

5 lines. AES-256 by default. All major viewers. MIT license. Zero external packages.

That's the TerraPDF difference.

Get started:

dotnet add package TerraPDF

Documentation: