Concept · Digital signatures

What is HSM signing,
and why does the key stay put?

Last updated: 2026-06-30

A signing key is as powerful as your handwritten signature, so the safest place for it is locked inside hardware that never lets it out. HSM signing lets you sign a PDF while the private key stays in your hardware module, smartcard or cloud key service. The document comes to the key, not the other way around, and the library never sees the secret.

The simplest analogy: a signing pen bolted inside a vault

Imagine a pen that can sign legally binding documents in your name. You would not carry it around or lend it out. You would bolt it inside a vault and bring each document to the vault to be signed, so the pen never leaves and no one can walk off with it.

That is exactly what an HSM (hardware security module) is for a digital key. The key is generated inside the hardware and is built so it can never be exported. To sign, you send the hardware a short fingerprint of the document (a hash), and it sends back the signature. The secret stays sealed in, and you still get a standard, fully valid signature out.

The risk of handing your key to a library

The moment a private key is loaded into application memory or read from disk, it can be stolen: by a crash dump, a memory-scraping process, a backup that leaks, or a dependency you did not audit. And for many keys this is simply not allowed. Smartcards, USB tokens and qualified-signature devices make the key non-exportable, so a library that demands the raw key cannot use them at all.

Deferred signing removes the question entirely. The library never receives the key, so there is nothing to leak, and the device stays the single source of truth for who can sign.

How signing without the key works

The library does all the PDF work, computes the bytes that need to be signed, and asks your hardware for the one thing only it can do: the raw signature. There are two shapes, depending on whether your signer is local or remote.

Signer callback synchronous

The library builds the signature structure and calls your function with the exact bytes to sign. You return the raw RSA signature from the HSM, and the library assembles and embeds the CMS.

  • Library prepares the document and the signed attributes
  • Your callback asks the HSM to sign the hash
  • Library embeds the result, the key never appears

Two-phase prepare and complete asynchronous

Phase one returns the hash to sign. You send it to a remote HSM or signing service across any boundary, even a separate HTTP request, then phase two embeds the container you get back.

  • begin_signing returns the document and its hash
  • Your service signs the hash, whenever it is ready
  • complete embeds the finished CMS container

The advantages

Keeping the key in hardware is not just safer, it unlocks deployments a key-in-memory library cannot reach.

The key cannot leak

There is no key in memory, in a config file or in a backup to steal. A compromised host still cannot extract the secret from the hardware.

Compliance by design

Frameworks like eIDAS qualified signatures, FIPS 140 and Common Criteria require keys to stay in certified hardware. Deferred signing is how you meet them.

Works across the network

The two-phase flow sends only a hash, so the signer can be a remote HSM or a separate microservice reached over an asynchronous call.

Any backend

Cloud KMS, network HSM over PKCS#11, smartcard, USB token or the OS keystore. If it can sign a hash, you can wire it to one callback.

Standard, valid output

The result is an ordinary PAdES signature, identical to a local-key one. It validates in Adobe Acrobat, pdfsig and national conformance validators.

One auditable choke point

Every signature goes through the HSM, which logs each operation. You get a single, tamper-evident record of everything signed in your name.

Where the key can live

rust-pdf does not talk to the device itself. You connect your callback to whichever SDK or PKCS#11 module your hardware uses, and the key stays there.

Cloud KMS

Azure Key Vault, AWS KMS and Google Cloud KMS sign a hash through their SDK. Your callback forwards the bytes and returns the signature.

HSM over PKCS#11

Network and PCIe HSMs expose a standard PKCS#11 module. The key is generated and used inside the appliance, never on the host.

Smartcards & tokens

National e-ID cards and qualified-signature USB tokens hold a non-exportable key. Deferred signing is the only way to use them.

Key in memory vs key in hardware

 Key handed to the libraryHSM / deferred signing
Where the key livesApplication memory and diskInside the hardware, sealed
If the host is breachedKey can be stolenKey stays protected
Non-exportable tokensCannot be usedFully supported
Remote / async signerNot possibleTwo-phase flow
eIDAS QES / FIPS / CCOut of reachMeets the requirement
Resulting signatureStandard PAdESIdentical PAdES

How to sign with an HSM in rust-pdf

You pass the certificate and a callback that returns the raw RSA signature from your HSM or token. The library builds the PAdES signature around it. The private key is never an argument.

# pip install rustpdf
import rustpdf

# Your HSM/token signs the bytes. The private key never enters the library.
def hsm_sign(data: bytes) -> bytes:
    return kms.sign_rsa_sha256(key_id, data)       # Azure/AWS/GCP KMS, PKCS#11, token...

opts = rustpdf.SigningOptions(pades=True)
signed = rustpdf.sign_with(pdf, cert_der, hsm_sign, chain=chain, options=opts)
// dotnet add package RustPdf
using RustPdf;

// The HSM signs; the private key never enters the library.
byte[] HsmSign(byte[] data) => kms.SignRsaSha256(keyId, data);

var opts = new SigningOptions { Pades = true };
byte[] signed = Pdf.SignWith(pdf, certDer, HsmSign, chain, opts);
// go get github.com/rustpdf/rustpdf-go@latest
opts := &rustpdf.SigningOptions{Pades: true}

// hsmSign returns the raw RSA signature; the key stays in your HSM.
signed, _ := rustpdf.SignWith(pdf, certDer, hsmSign, chain, opts)
// npm install rustpdf
const { signWith } = require("rustpdf");

// hsmSign(data) returns a Buffer; the key never enters the library.
const signed = signWith(pdf, certDer, hsmSign, chain, { pades: true });

Talking to a remote signing service? Use the two-phase flow: begin_signing returns the hash to sign, and the session's complete embeds the container once it comes back. Add a SignaturePolicy and Certify for policy-based and certified (DocMDP) signatures. More in the documentation and what is PAdES.

HSM signing FAQ

What is HSM signing?

HSM signing is digital signing where the private key stays inside a hardware security module, smartcard, USB token or cloud key-management service, and never leaves it. The software prepares the document and asks the hardware to produce the signature over a hash, then embeds it. The key is never exported, copied into memory, or written to disk. It is also called deferred or external signing.

Why should the private key never leave the HSM?

A signing key carries the weight of your handwritten signature, so whoever holds it can sign as you. Keeping it in tamper-resistant hardware means it cannot be stolen from a server, leaked in a memory dump, or copied by a compromised process. Many keys, like those on smartcards and qualified-signature tokens, are non-exportable by design, so deferred signing is the only way to use them. It is also required under eIDAS qualified signatures, FIPS 140 and Common Criteria.

How do you sign a PDF without giving the key to the library?

Two models. In the signer-callback model the library builds the signature structure, hands you the exact bytes to sign, and calls your code for the raw RSA signature from the HSM. In the two-phase model the library returns a hash, you sign it on the HSM across any boundary including an async network call, and then complete the signature with the container you get back. In both, the private key never reaches the library. rust-pdf exposes both as sign_with and begin_signing/complete.

Which HSMs, tokens and key services does this work with?

Any backend that can produce an RSA PKCS#1 v1.5 signature over a SHA-256 hash: cloud KMS (Azure Key Vault, AWS KMS, Google Cloud KMS), network and PCIe HSMs over PKCS#11, smartcards and USB tokens, and the OS keystore (Windows CNG, macOS Keychain). rust-pdf does not talk to the device; you wire your callback to whichever SDK or PKCS#11 module your hardware uses.

Does HSM signing still produce a standard PAdES signature?

Yes. The output is an ordinary PAdES (ETSI.CAdES.detached) signature, byte-for-byte the same as a local-key one. It can carry the signing-certificate-v2 attribute, an explicit signature policy, DocMDP certification and a full embedded chain, and it validates in Adobe Acrobat, pdfsig and national conformance validators. Where the key lives changes nothing about the signature, only who holds the secret.

Sign with your HSM in any language

One core, the same deferred signing across nine languages, with the key always in your hardware. Prototype for free, license the signing feature when you ship.