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

Video walkthrough · 4:04 · Watch on YouTube · Short · 1:13
§STEP BY STEP

Build it step by step.

  1. 1Install the NuGet packages

    SelectPdf.Universal is the library. The native package carries the Chromium engine for one platform: add the one for the platform you run on.

    terminal
    dotnet add package SelectPdf.Universal
    dotnet add package SelectPdf.Universal.Native.win-x64
    terminal
    dotnet add package SelectPdf.Universal
    dotnet add package SelectPdf.Universal.Native.linux-x64
    terminal
    dotnet add package SelectPdf.Universal
    dotnet add package SelectPdf.Universal.Native.linux-arm64
    terminal
    dotnet add package SelectPdf.Universal
    dotnet add package SelectPdf.Universal.Native.osx-arm64
  2. 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.cs with the code below.

    terminal
    dotnet new console -n PdfSample
    cd PdfSample
  3. 3Import the namespaces

    Program.cs · lines 1–4
    using System.Security.Cryptography;
    using System.Security.Cryptography.X509Certificates;
    using SelectPdf.Universal;
    using SelectPdf.Universal.Drawing;

    The two System.Security.Cryptography namespaces only make the test certificate. Signing uses SelectPdf.Universal and SelectPdf.Universal.Drawing, which holds RectangleF.

  4. 4Make a test certificate

    Program.cs · lines 6–15
    // 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.pfx with a password. In production, use the .pfx your certificate authority issued and skip this step.

  5. 5Create the PDF to sign

    Program.cs · lines 17–27
    // 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();

    HtmlToPdf turns a short contract into contract.pdf. In your application this is the PDF you already have.

  6. 6Sign it with the certificate

    Program.cs · lines 29–35
    // 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.Load opens the PDF. Sign takes 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.

  7. 7Save the signed copy

    Program.cs · lines 37–39
    // 4. Save the signed copy. Close releases the document and the certificate.
    manager.Save("contract-signed.pdf");
    manager.Close();

    Save writes contract-signed.pdf with the signature. Close releases the document and the certificate that Sign loaded. A PDF viewer shows the signer, reason, location and time in its signature panel.

  8. 8Run it

    terminal
    dotnet run

    The program writes contract-signed.pdf to the working folder. Without a license key, every page carries a trial watermark; set GlobalProperties.LicenseKey before the first conversion to remove it.

§COMPLETE PROGRAM

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.

Program.cs
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

§OUTPUT

The PDF it produces.

First page of contract-signed.pdf, the PDF this sample produces
contract-signed.pdf, page 1

Download contract-signed.pdf ↓ the file this sample produced, unedited

§VARIATIONS

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.

SignNewDocument.cs
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.

CustomAppearance.cs
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.

TwoSignatures.cs
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();
    }
}
§TROUBLESHOOTING

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

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
?FAQ

Questions, answered.

Short answers about this guide. The SelectPdf.Universal documentation covers every class and option.

Load the PDF with PdfSecurityManager, call Sign with the path and password of a .pfx certificate, and save. Pass a page index and a rectangle to Sign to show a visible signature on the page.
A PKCS#12 file (.pfx or .p12) that holds the certificate and its private key. RSA and ECDSA certificates can sign. For documents others must trust, use a certificate from a certificate authority they trust.
Yes. Load the signed PDF and call Sign again with a different signature field name. Each signature keeps covering what it signed.
No. Digital signatures are part of the commercial SelectPdf.Universal library. The Community Edition is HTML to PDF only.