DevTools Hub

Search tools

Search for a developer tool

How a CSR Proves You Own the Private Key

Part of the Encryption Toolkit

A certificate signing request contains a public key, a domain name, and a signature — and that signature is the interesting part. It's not there to prove who you are; the certificate authority hasn't checked your identity yet at this point, and a CSR on its own proves nothing about ownership of a domain. What it proves is narrower and, once you see it, obvious: that whoever submitted this request actually holds the private key matching the public key sitting right there in the same document.

The CSR signs itself

A CSR is built from one block of data — the subject name, the public key, and any requested extensions, all DER-encoded together as a structure called CertificationRequestInfo — and then that exact block is signed with the private key. The signature and the public key both end up in the final document, sitting next to the data they describe:

CertificationRequest
CertificationRequestInfo (this is what gets signed)
subject (CN, O, C, ...)
subjectPublicKeyInfo (the public key)
attributes → extensionRequest → subjectAltName
signatureAlgorithm (e.g. sha256WithRSAEncryption)
signature (private key signs the CertificationRequestInfo bytes above)

original CertificationRequestInfo bytes

signature checked against the embedded public key

✓ signatureValid: true

same CSR, one byte flipped in the body

structure still parses fine — only the signature check catches it

✗ signatureValid: false

Checking it is just as self-contained: take the embedded public key, use it to verify the signature against the embedded CertificationRequestInfo bytes. If it checks out, the requester provably holds the private key — not because any third party vouched for it, but because producing a valid signature without the private key is computationally infeasible. The real CSR above and its tampered copy (one base64 character flipped in the body) were both generated and decoded with this site's actual CSR tools to produce those two results.

Why this matters: it stops a very specific attack

Without this check, nothing would stop someone from submitting a CSR built around your public key — maybe one they copied off an old certificate — paired with a domain they control. If a CA only looked at domain control and the public key, it could hand out a certificate binding your public key to someone else's identity, and now anything encrypted to "you" under that certificate is readable by whoever actually holds the matching private key, which isn't you. Proof of possession closes exactly that gap: the CSR cryptographically demonstrates the submitter holds the private key for the public key they're asking to be certified, before the CA does anything else.

SAN lives one level deeper than you'd expect

The subject's Common Name is a direct field on the request. The Subject Alternative Names a modern browser actually requires — the list of every domain the certificate should cover — aren't a field at all. They're smuggled in through a generic attributes slot on the request, wrapped three layers deep:

attributes
  → extensionRequest attribute (identified by OID 1.2.840.113549.1.9.14)
    → a SEQUENCE of requested extensions
      → the subjectAltName extension (OID 2.5.29.17)
        → a SEQUENCE of GeneralName entries (dNSName, tag [2])

That nesting is exactly why a hand-assembled CSR — built with a one-off OpenSSL config that forgot the [v3_req] / subjectAltName section — so often comes out missing SAN entirely, with nothing about the request itself looking obviously wrong. CN alone used to be enough; it hasn't been for years, and the extension living in a generic attribute bucket instead of a core field is the structural reason it's easy to leave out by accident.

The signature algorithm travels with the request

A CSR also states which algorithm was used to sign it — sha256WithRSAEncryption, identified by OID 1.2.840.113549.1.1.11, is the common case — right alongside the signature itself. Verifying a CSR means reading that OID, hashing the CertificationRequestInfo bytes with the algorithm it names, and checking the result against the signature using the embedded public key. Every piece needed to verify the request travels inside the request. Nothing is assumed or configured out-of-band.

Try it yourself

CSR Generator builds a real PKCS#10 request — generating or reusing an RSA key pair, assembling the subject and SAN extension, and signing it exactly as described above. CSR Decoder does the reverse: decode any CSR's subject, public key, and SAN list, and actually run the signature verification against its own embedded public key rather than just displaying the fields. Both run entirely in your browser using the native Web Crypto API — no key material ever leaves your machine.

Related tools