---
title: "Sign a PDF and verify its signatures"
url: https://recursiveai.org/onepdf/cookbook/sign-and-verify/
description: "Sign a PDF in C# with a certificate from a .pfx file, verify the signature against your own root, and see how a change after signing is reported."
version: "1.11.0"
---

# Sign a PDF and verify its signatures

Sign a PDF in C# with a certificate from a .pfx file, verify the signature against your own root, and see how a change after signing is reported.

The sample uses `signer.pfx`, a certificate with its private key, and `root.cer`, the root certificate that issued it. Use your own, or make a test pair with .NET’s `CertificateRequest` as in [Getting started](https://recursiveai.org/onepdf/docs/getting-started/).

```csharp
using System.Security.Cryptography.X509Certificates;
using OnePdf;

using X509Certificate2 signer = X509CertificateLoader.LoadPkcs12FromFile("signer.pfx", "test");
using X509Certificate2 root = X509CertificateLoader.LoadCertificateFromFile("root.cer");

// Sign: an invisible PAdES signature, written as an incremental update.
using (PdfDocument doc = PdfDocument.Open("report.pdf", new PdfOpenOptions { AllowEditing = true }))
{
    PdfSigningResult signed = doc.Sign("signed.pdf", PdfSigningCredential.FromCertificate(signer), new PdfSigningOptions
    {
        FieldName = "Approval",
        Reason = "Approved for release",
        Location = "Springfield",
    });
    Console.WriteLine($"signed field {signed.FieldName}, {signed.ContentsUsed:N0} of {signed.ContentsCapacity:N0} bytes reserved");
}

Verify("signed.pdf");

// Change the signed file: stamp a page and save the change as another incremental update.
using (PdfDocument doc = PdfDocument.Open("signed.pdf", new PdfOpenOptions { AllowEditing = true }))
{
    doc.AddTextStamp("COPY", doc.AddStandardFont("Helvetica"), 48, pageIndexes: [0]);
    using FileStream output = File.Create("changed.pdf");
    doc.SaveIncremental(output);
}

Verify("changed.pdf");

void Verify(string path)
{
    using PdfDocument doc = PdfDocument.Open(path);
    PdfSignatureReport report = doc.VerifySignatures(new PdfSignatureVerifyOptions { TrustAnchors = [root] });
    foreach (PdfSignatureVerification v in report.Signatures)
    {
        Console.WriteLine($"{path}: {v.Signature.FieldName} valid={v.IsValid} integrity={v.Integrity} signature={v.SignatureCheck} chain={v.Chain} coverage={v.Coverage}");
        foreach (PdfSignatureChange change in v.Changes.Where(c => !c.Allowed))
            Console.WriteLine($"  not allowed: object {change.ObjectNumber}, {change.Category}");
    }
}
```

```text
signed field Approval, 1,691 of 3,175 bytes reserved
signed.pdf: Approval valid=True integrity=Valid signature=Valid chain=Trusted coverage=WholeDocument
changed.pdf: Approval valid=False integrity=Valid signature=Valid chain=Trusted coverage=EarlierRevisionDisallowedChanges
  not allowed: object 7, PageContent
  not allowed: object 74, PageContent
  not allowed: object 75, PageContent
  not allowed: object 76, PageContent
  not allowed: object 77, PageContent
  not allowed: object 78, PageContent
```

The signature over the original bytes is still mathematically intact in `changed.pdf`, but the page content changed after it, so the signature is no longer valid for the document as it now reads.

## Signing

- Signing is always an incremental update. The original bytes stay an exact prefix of the output, so earlier signatures stay valid. The document must be opened with `AllowEditing`, and changes made before the call, such as a filled field, are part of what is signed.
- The default format is PAdES baseline B-B (`ETSI.CAdES.detached`). `Format = PdfSignatureFormat.Pkcs7Detached` writes `adbe.pkcs7.detached` for older consumers.
- **Time-stamps.** Set `Timestamp` to a callback that sends `request.RequestDer` to your time-stamp authority and returns its response, and the signature becomes PAdES B-T. `AddDocumentTimestamp` adds a document time-stamp.
- **Visible signatures.** Set `Rectangle` (and `PageIndex`) for a visible signature with the signer’s name, the date and the reason. Leave it out for an invisible one.
- **Certification.** `Certify` makes a certification signature that states which changes are allowed afterwards. Only the first signature of a document can certify.
- **Keys you can’t export.** `PdfSigningCredential.FromExternalSigner` hands OnePDF’s digest to your code, for a hardware module or a cloud key service, and checks the signature value you return.
- RSA (PKCS#1 v1.5 and PSS) and ECDSA keys are supported. Long-term validation data (PAdES B-LT and B-LTA) isn’t written.
- To sign twice, sign, open the output with `AllowEditing`, and sign again.

## Verifying

- `IsValid` is true only when the byte range is sound, the digest matches, the signature verifies, the chain is trusted at the validation time, any time-stamp is valid, and nothing disallowed follows the signature.
- Nothing is fetched. A chain is trusted only when it leads to a certificate in `TrustAnchors`, or to the system store with `UseSystemTrustStore`. Revocation is reported as not checked unless you pass a `RevocationCheck` function that answers from CRLs or OCSP responses you already have.
- `Changes` lists every object written after the signature with a category (`FormFieldValue`, `NewSignature`, `Annotation`, `PageContent`, `ValidationData`, `Other`) and whether the document’s permissions allow it. Filling a form field after signing is allowed by default; changing page content isn’t.
- `Warnings` explains anything that failed or couldn’t be checked, such as a weak algorithm.

The checks, the change rules and how known attacks on PDF signatures are reported are in [Signatures](https://recursiveai.org/onepdf/docs/signatures/).

A longer walk-through with PAdES B-T time stamps, visible signatures, HSM and cloud keys: [Digitally sign a PDF in C#](https://recursiveai.org/onepdf/articles/sign-pdf-csharp/).
