Available for: UI for ASP.NET MVC | UI for ASP.NET AJAX | UI for Blazor | UI for WPF | UI for WinForms | UI for Silverlight | UI for Xamarin | UI for WinUI | UI for ASP.NET Core | UI for .NET MAUI

New to Telerik Document Processing? Download free 30-day trial

Externally Sign a PDF Document

Minimum Version Q4 2025

RadPdfProcessing provides support for creating a Signature instance configured for external signing. The external signing handler performs the actual signing operation. The private key is managed outside the PDF library (e.g., HSMs, smart cards, remote signing services) and it allows integration without exposing private key material to the library.

PdfProcessing Add Digital Signature External Demo

Using IExternalSigner

When you digitally sign a PDF, the signature data is typically embedded in the PDF file using CMS (Cryptographic Message Syntax). It is a standard used to digitally sign, encrypt, and authenticate data. CMS encapsulates the signature and it contains the signed hash of the document, the signer's certificate, and optionally a timestamp and other metadata.

The supported digest (hash) algorithms for producing CMS (PKCS #7) PDF signature values are Sha256, Sha384 and Sha512 specified by the DigestAlgorithm property of the SignatureSettings.

The following example demonstrates how to implement the IExternalSigner interface producing an external CMS (PKCS #7) detached signature over a PDF byte range:

[C#] CMS External Signer

internal class CmsExternalSigner : IExternalSigner
{
    private static readonly Oid SHA256 = new Oid("2.16.840.1.101.3.4.2.1", "SHA256");
    private static readonly Oid SHA384 = new Oid("2.16.840.1.101.3.4.2.2", "SHA384");
    private static readonly Oid SHA512 = new Oid("2.16.840.1.101.3.4.2.3", "SHA512");

    string fullCertificatePath = @"JohnDoe.pfx";
    string password = "johndoe";
    private readonly X509Certificate2 fullCertificate;

    public CmsExternalSigner()
    {
        fullCertificate = new X509Certificate2(
            File.ReadAllBytes(fullCertificatePath),
            password,
            X509KeyStorageFlags.EphemeralKeySet);
    }

    public byte[] Sign(byte[] dataToSign, SignatureSettings settings)
    {
        byte[] package;
        ContentInfo contentInfo = new ContentInfo(dataToSign);
        SignedCms signedCms = new SignedCms(contentInfo, detached: true);

        try
        {
            package = this.GetContentPackage(signedCms, settings, X509IncludeOption.WholeChain);
        }
        catch (CryptographicException)
        {
            package = this.GetContentPackage(signedCms, settings, X509IncludeOption.EndCertOnly);
        }

        return package;
    }
    private byte[] GetContentPackage(SignedCms signedCms, SignatureSettings settings, X509IncludeOption includeOption)
    {
        // Step 1: Create signer
        CmsSigner signer = new CmsSigner(this.fullCertificate);
        signer.DigestAlgorithm = GetDigestOid(settings.DigestAlgorithm);
        signer.IncludeOption = includeOption;

        // Step 2: Compute initial CMS signature
        signedCms.ComputeSignature(signer);
        byte[] encodedCms = signedCms.Encode();

        return encodedCms;
    }

    private static Oid GetDigestOid(DigestAlgorithmType type)
    {
        Oid signatureOid;
        switch (type)
        {
            case DigestAlgorithmType.Sha256:
                signatureOid = SHA256;
                break;
            case DigestAlgorithmType.Sha384:
                signatureOid = SHA384;
                break;
            case DigestAlgorithmType.Sha512:
                signatureOid = SHA512;
                break;
            default:
                throw new NotSupportedException($"Digest algorithm '{type}' is not supported.");
        }

        return signatureOid;
    }
}

Then, initialize a Signature instance using the CMS External Signer:

int signatureFieldWidth = 200;
int signatureFieldHeight = 50;
int signaturePositionLeft = 10;
int signaturePositionTop = 10;

TimeStampServer timeStampServer = new TimeStampServer("url", "user", "password", TimeSpan.FromHours(1));

SignatureField pdfSignature = new SignatureField("SignatureField");
IExternalSigner signDataHandler = new CmsExternalSigner();
Signature signature = new Signature(signDataHandler);
signature.Settings.DigestAlgorithm = DigestAlgorithmType.Sha512;
signature.Settings.TimeStampServer = timeStampServer;
pdfSignature.Signature = signature;

Form pdfForm = new Form();
pdfForm.FormSource = new FormSource();
pdfForm.FormSource.Size = new Size(signatureFieldWidth, signatureFieldHeight);
FixedContentEditor editor = new FixedContentEditor(pdfForm.FormSource);
pdfForm.Position.Translate(signaturePositionLeft, signaturePositionTop);
editor.DrawText($"{"Signed on"} {DateTime.Now.ToString("yyyy.MM.dd HH:mm")}");

SignatureWidget signatureWidget = pdfSignature.Widgets.AddWidget();
signatureWidget.Content.NormalContentSource = pdfForm.FormSource;
signatureWidget.Rect = new Rect(signaturePositionLeft, signaturePositionTop, signatureFieldWidth, signatureFieldHeight);
signatureWidget.RecalculateContent();

RadFixedDocument fixedDocument = new RadFixedDocument();
RadFixedPage pdfPage = fixedDocument.Pages.AddPage();
pdfPage.Annotations.Add(signatureWidget);

FixedContentEditor pageEditor = new FixedContentEditor(pdfPage);
pageEditor.Position.Translate(signaturePositionLeft, signaturePositionTop);
pageEditor.DrawForm(pdfForm.FormSource);
fixedDocument.AcroForm.FormFields.Add(pdfSignature);
signatureWidget.RecalculateContent();

PdfFormatProvider provider = new PdfFormatProvider();
string signedDocumentFilePath = "ExternallySigned.pdf";
File.Delete(signedDocumentFilePath);
using (System.IO.Stream output = new System.IO.FileStream(signedDocumentFilePath,
    System.IO.FileMode.OpenOrCreate, System.IO.FileAccess.ReadWrite))
{
    provider.Export(fixedDocument, output, TimeSpan.FromSeconds(10));
}
Process.Start(new ProcessStartInfo() { FileName = signedDocumentFilePath, UseShellExecute = true });

Using ExternalSignerBase

The PdfProcessing library allows creating a base helper implementation for building external (client supplied) digital signatures.

The following example implements external RSA-based digital signing for PDF documents deriving the ExternalSignerBase class. RSA (Rivest–Shamir–Adleman) algorithm is a widely used asymmetric cryptographic algorithm. RSA generates a private key and a public key where the private key is used to sign the PDF and the public key is used to verify the signature. During the signing process a hash (digest) of the PDF content is created (e.g., using SHA-512). This hash is then encrypted with the RSA private key to create the digital signature. The signature is embedded in the PDF file, typically in a signature field.

[C#] RSA External Signer

internal class RSAExternalSigner : ExternalSignerBase
{
    private readonly X509Certificate2 publicCertificate;
    private readonly X509Certificate2 fullCertificate;

    public RSAExternalSigner()
    {
        // If you have a separate .cer/.crt file use that.
        // Otherwise you can still load the PFX;
        // the private key will simply not be used at this stage.
        string publicCertificatePath = "JohnDoe.crt";
        this.publicCertificate = new X509Certificate2(publicCertificatePath);

        string fullCertificatePath = "JohnDoe.pfx";
        this.fullCertificate = new X509Certificate2(fullCertificatePath, "johndoe");
    }

    protected override X509Certificate2[] GetCertificateChain()
    {
        return new X509Certificate2[] { this.publicCertificate };
    }

    protected override byte[] SignData(byte[] dataToSign, SignatureSettings settings)
    {
        using (RSA rsa = this.fullCertificate.GetRSAPrivateKey())
        {
            if (rsa == null)
            {
                throw new InvalidOperationException("The certificate does not contain an RSA private key.");
            }

            HashAlgorithmName hashAlgorithm = GetHashAlgorithmName(settings.DigestAlgorithm);
            byte[] signature = rsa.SignData(dataToSign, hashAlgorithm, RSASignaturePadding.Pkcs1);

            return signature;
        }
    }

    private static HashAlgorithmName GetHashAlgorithmName(DigestAlgorithmType digestAlgorithm)
    {
        return digestAlgorithm switch
        {
            DigestAlgorithmType.Sha256 => HashAlgorithmName.SHA256,
            DigestAlgorithmType.Sha384 => HashAlgorithmName.SHA384,
            DigestAlgorithmType.Sha512 => HashAlgorithmName.SHA512,
            _ => throw new NotSupportedException($"Digest algorithm '{digestAlgorithm}' is not supported.")
        };
    }
}

Now, create a Signature that uses the above implementation:

TimeStampServer timeStampServer = new TimeStampServer("url",
    "user", "password", TimeSpan.FromHours(1));
RSAExternalSigner signDataHandler = new RSAExternalSigner();
Signature signature = new Signature(signDataHandler);
signature.Settings.DigestAlgorithm = DigestAlgorithmType.Sha512;
signature.Settings.TimeStampServer = timeStampServer;

See Also

In this article