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 printingPrintLowResolution— Degraded printing onlyCopyText— Allow copy/paste of text and graphicsModifyContents— Allow document editingModifyAnnotations— Add/modify annotationsFillForms— Fill form fieldsExtractForAccessibility— Screen reader text extractionAssembleDocument— Insert/rotate/delete pagesAll— UnrestrictedNone— 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
/Lengthvalues - File header
Zero external dependencies — uses only System.Security.Cryptography:
Aesfor content encryptionSHA256(AES-256) /MD5(legacy AES-128) for PDF-mandated key derivationRandomNumberGeneratorfor 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, orENCRYPTION_AES_256if 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
- 5-line setup — Clean, fluent API
- Zero dependencies — No external packages or native binaries
- Fine-grained permissions — 8 distinct permission flags
- Production-ready — Enterprise AES-256 encryption by default, AES-128 on request
- MIT licensed — No licensing restrictions
- Universal compatibility — Works in every PDF viewer
- Fast — Pure C# with no external calls
- 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: