Digitally sign a PDF with a PFX certificate in C#
A digital signature proves who approved a PDF and that it has not changed since. PdfSecurityManager loads the PDF, Sign adds a signature made with the private key of a .pfx certificate, and Save writes the signed copy, with a visible signature box on the page.
Compiled and run: Linux ARM64 · Linux x64 · macOS ARM64 · Windows x64
Build it step by step.
-
1Install the NuGet packages
SelectPdf.Universalis the library. The native package carries the Chromium engine for one platform: add the one for the platform you run on.dotnet add package SelectPdf.Universal dotnet add package SelectPdf.Universal.Native.win-x64
dotnet add package SelectPdf.Universal dotnet add package SelectPdf.Universal.Native.linux-x64
dotnet add package SelectPdf.Universal dotnet add package SelectPdf.Universal.Native.linux-arm64
dotnet add package SelectPdf.Universal dotnet add package SelectPdf.Universal.Native.osx-arm64
-
2Create a console app
Any .NET 8 or .NET 10 project works. To follow along, create a console app, run the install commands above inside its folder, then replace
Program.cswith the code below.dotnet new console -n PdfSample cd PdfSample
-
3Import the namespaces
using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using SelectPdf.Universal; using SelectPdf.Universal.Drawing;
The two
System.Security.Cryptographynamespaces only make the test certificate. Signing usesSelectPdf.UniversalandSelectPdf.Universal.Drawing, which holdsRectangleF. -
4Make a test certificate
// 1. A self-signed test certificate, saved as signer.pfx. // In production, use the .pfx file your certificate authority issued. using (RSA key = RSA.Create(2048)) { var request = new CertificateRequest("CN=Jane Doe, O=Example Ltd", key, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); using X509Certificate2 certificate = request.CreateSelfSigned( DateTimeOffset.UtcNow.AddDays(-1), DateTimeOffset.UtcNow.AddYears(1)); File.WriteAllBytes("signer.pfx", certificate.Export(X509ContentType.Pfx, "pfx-password")); }Signing needs a PKCS#12 file (.pfx or .p12) with a certificate and its private key. To keep the sample self-contained, .NET creates a self-signed one for Jane Doe and exports it as
signer.pfxwith a password. In production, use the .pfx your certificate authority issued and skip this step. -
5Create the PDF to sign
// 2. The PDF to sign: a one-page contract made from HTML. HtmlToPdf converter = new HtmlToPdf(); PdfDocument contract = converter.ConvertHtmlString(""" <h1>Service agreement</h1> <p>Example Ltd provides the reporting service described below to the customer from 1 November 2026, for twelve months.</p> <p>Fees are invoiced monthly and due within 30 days.</p> <p style="margin-top:60px">Approved by:</p> """); contract.Save("contract.pdf"); contract.Close();HtmlToPdfturns a short contract intocontract.pdf. In your application this is the PDF you already have. -
6Sign it with the certificate
// 3. Load the PDF and sign it with the certificate, adding // a visible signature on the first page. PdfSecurityManager manager = new PdfSecurityManager(); manager.Load("contract.pdf"); manager.Sign("signer.pfx", "pfx-password", "Signature1", "Approved", "London", "jane.doe@example.com", 0, new RectangleF(40, 125, 260, 70));PdfSecurityManager.Loadopens the PDF.Signtakes the .pfx path and password, a name for the signature field, the reason, location and contact info, then the zero-based page index and the rectangle of the visible signature, in points from the top-left corner of the page. -
7Save the signed copy
// 4. Save the signed copy. Close releases the document and the certificate. manager.Save("contract-signed.pdf"); manager.Close();Savewritescontract-signed.pdfwith the signature.Closereleases the document and the certificate thatSignloaded. A PDF viewer shows the signer, reason, location and time in its signature panel. -
8Run it
dotnet run
The program writes
contract-signed.pdfto the working folder. Without a license key, every page carries a trial watermark; setGlobalProperties.LicenseKeybefore the first conversion to remove it.
The complete Program.cs.
This is the exact file our samples pipeline compiled and ran on Linux ARM64 and Linux x64 and macOS ARM64 and Windows x64 to produce the output on this page.
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using SelectPdf.Universal;
using SelectPdf.Universal.Drawing;
// 1. A self-signed test certificate, saved as signer.pfx.
// In production, use the .pfx file your certificate authority issued.
using (RSA key = RSA.Create(2048))
{
var request = new CertificateRequest("CN=Jane Doe, O=Example Ltd", key,
HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
using X509Certificate2 certificate = request.CreateSelfSigned(
DateTimeOffset.UtcNow.AddDays(-1), DateTimeOffset.UtcNow.AddYears(1));
File.WriteAllBytes("signer.pfx", certificate.Export(X509ContentType.Pfx, "pfx-password"));
}
// 2. The PDF to sign: a one-page contract made from HTML.
HtmlToPdf converter = new HtmlToPdf();
PdfDocument contract = converter.ConvertHtmlString("""
<h1>Service agreement</h1>
<p>Example Ltd provides the reporting service described below to the customer
from 1 November 2026, for twelve months.</p>
<p>Fees are invoiced monthly and due within 30 days.</p>
<p style="margin-top:60px">Approved by:</p>
""");
contract.Save("contract.pdf");
contract.Close();
// 3. Load the PDF and sign it with the certificate, adding
// a visible signature on the first page.
PdfSecurityManager manager = new PdfSecurityManager();
manager.Load("contract.pdf");
manager.Sign("signer.pfx", "pfx-password", "Signature1",
"Approved", "London", "jane.doe@example.com",
0, new RectangleF(40, 125, 260, 70));
// 4. Save the signed copy. Close releases the document and the certificate.
manager.Save("contract-signed.pdf");
manager.Close();
Open the project on GitHub → this program and the variations below, ready to dotnet run
The PDF it produces.
Download contract-signed.pdf ↓ the file this sample produced, unedited
Variations you will need next.
Each variation is a separate file in the same project, compiled and run with the program above.
Sign the converter output before it is saved
PdfDigitalSignatureElement adds the signature to a page of a document you are building, here the PDF that ConvertHtmlString returns. PdfDigitalCertificatesStore.GetCertificates reads the .pfx. The document is signed when it is saved, so the using keeps the certificates until after Save.
using SelectPdf.Universal;
using SelectPdf.Universal.Drawing;
static class SignNewDocument
{
public static void Run()
{
HtmlToPdf converter = new HtmlToPdf();
PdfDocument invoice = converter.ConvertHtmlString("""
<h1>Invoice 2026-118</h1>
<p>Reporting service, October 2026: 1,200.00 EUR</p>
""");
// the document is signed when it is saved, so keep the certificates until then
using PdfDigitalCertificatesCollection certificates =
PdfDigitalCertificatesStore.GetCertificates("signer.pfx", "pfx-password");
PdfDigitalSignatureElement signature =
new PdfDigitalSignatureElement(new RectangleF(40, 100, 260, 70), certificates[0]);
signature.Reason = "Issued";
signature.Location = "London";
invoice.Pages[0].Add(signature);
invoice.Save("invoice-signed.pdf");
invoice.Close();
}
}
A seal or logo behind the signature text
SignatureBackgroundImage takes the bytes of a PNG, JPEG, GIF, BMP or WebP image and draws it behind the text of the visible signature, in place of the default handwritten mark. SignatureBackgroundOpacity goes from 0 (no background) to 1 (the image's own colors); the default is 0.25.
using SelectPdf.Universal;
using SelectPdf.Universal.Drawing;
static class CustomAppearance
{
public static void Run()
{
// a round seal for the background; in your application, read your PNG file
HtmlToImage sealMaker = new HtmlToImage(200, 200);
byte[] seal = sealMaker.ConvertHtmlString("""
<body style="margin:0">
<div style="width:180px; height:180px; margin:10px; border:8px solid #1d4ed8;
border-radius:50%; box-sizing:border-box; display:flex; align-items:center;
justify-content:center; font:bold 28px sans-serif; color:#1d4ed8">EXAMPLE</div>
</body>
""");
PdfSecurityManager manager = new PdfSecurityManager();
manager.Load("contract.pdf");
manager.SignatureBackgroundImage = seal;
manager.SignatureBackgroundOpacity = 0.4f;
manager.Sign("signer.pfx", "pfx-password", "Signature1",
"Approved", "London", "",
0, new RectangleF(40, 125, 260, 70));
manager.Save("contract-logo-signed.pdf");
manager.Close();
}
}
A second signer countersigns
The customer signs the copy Jane Doe already signed, with a new field name and a box to the right. The second signature is added after the first, so the first one still covers everything it signed.
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using SelectPdf.Universal;
using SelectPdf.Universal.Drawing;
static class TwoSignatures
{
public static void Run()
{
// a second test certificate, for the customer
using (RSA key = RSA.Create(2048))
{
var request = new CertificateRequest("CN=John Smith, O=Customer Inc", key,
HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
using X509Certificate2 certificate = request.CreateSelfSigned(
DateTimeOffset.UtcNow.AddDays(-1), DateTimeOffset.UtcNow.AddYears(1));
File.WriteAllBytes("customer.pfx", certificate.Export(X509ContentType.Pfx, "customer-password"));
}
// the customer countersigns the copy Jane Doe already signed;
// a new field name keeps the first signature intact
PdfSecurityManager manager = new PdfSecurityManager();
manager.Load("contract-signed.pdf");
manager.Sign("customer.pfx", "customer-password", "Signature2",
"Accepted", "New York", "",
0, new RectangleF(320, 125, 260, 70));
manager.Save("contract-countersigned.pdf");
manager.Close();
}
}
Common problems and fixes.
The save fails after the certificates were disposed
A PdfDigitalSignatureElement signs when the document is saved. Keep the PdfDigitalCertificatesCollection alive, for example with a using around the whole block, until Save has returned.
The viewer says the signer's identity is unknown
The signature is intact, but the certificate does not chain to a root the viewer trusts. That is expected for the self-signed test certificate. A certificate from a public certificate authority, or your organization's own CA installed on the readers' machines, is trusted.
The signature box covers page content
The rectangle is in points from the top-left corner of the page, and the page index is zero-based. Leave space in the document for the box, as the contract does under "Approved by".
The key is on a smart card, a USB token or an HSM
Use PdfCertificateStoreSigner (a certificate from the Windows store, by thumbprint) or PdfPkcs11Signer (the vendor's PKCS#11 library) and pass it to Sign instead of the .pfx path.
Good to know.
- Adding a digital signature requires a PKCS#12 certificate, provided as a .pfx or a .p12 file. Source
- PdfSecurityManager applies security to an existing PDF: encryption and digital signing with a PFX certificate. Source
- Signatures are computed with SHA-256 by default; DigestAlgorithm selects SHA-384 or SHA-512. RSA and ECDSA certificates can sign. Source
- A visible signature shows the signer's name, the reason and location when given, and the date; SignatureBackgroundImage replaces the default background mark. Source
- A document is signed when it is saved, so certificates read with PdfDigitalCertificatesStore must stay undisposed until the save. Source
Questions, answered.
Short answers about this guide. The SelectPdf.Universal documentation covers every class and option.