tls
Generating a CSR Shouldn't Mean Pasting Your Private Key Into a Website
A certificate signing request contains no secrets — it is your public key and subject details, self-signed to prove possession. So any CSR site that generates your key server-side or asks you to paste one has the trust model backwards. What RFC 2986 actually puts in a CSR, and how WebCrypto keeps the key on your machine.
Search for an online CSR generator and you’ll find pages that ask for your company details, spin up a key on their server, and hand you both files — right next to a quote for their certificate plans. Some go further and invite you to paste an existing private key “to reuse it.” Stop there, because a certificate signing request contains no secrets whatsoever — it is your public key plus your subject details, signed to prove you hold the private key — and the private key itself must never travel: not to a CA, not to a generator site, not anywhere. A tool that generates your key server-side, or accepts one through a form, has quietly inverted the entire trust model of TLS. The one secret the whole system depends on just passed through someone else’s web server, TLS termination layer, access logs, and memory.
Most of these sites aren’t malicious. They’re lead-generation forms for certificate vendors, built for convenience, and the key handling is an afterthought. But “probably not logging your key” is not a security property, and the fix isn’t caution — it’s understanding what a CSR actually is, at which point you’ll see the key never had any reason to leave your machine in the first place.
What a CSR actually is
A CSR is a PKCS#10 CertificationRequest, defined in RFC 2986. Strip away the base64 armour and there are exactly three things inside the signed portion (CertificationRequestInfo):
- A subject distinguished name — the CN, O, C, and friends you typed into the form.
SubjectPublicKeyInfo— your public key and its algorithm identifier.- Attributes — optional extras, most commonly the requested extensions, like the subjectAltName list that modern CAs actually match host names against.
Then that structure is signed — and here’s the elegant part — with your own private key. RFC 2986 spells it out: the CertificationRequestInfo value is DER-encoded, and “the result of step 1 is signed with the certification request subject’s private key under the specified signature algorithm.” That self-signature is proof of possession: it demonstrates to the CA that whoever built this request controls the private key matching the enclosed public key, without the key ever appearing. Without it, anyone could request a certificate over your public key.
Here’s a real one — ECDSA P-256, for example.com, decoded fields annotated below:
-----BEGIN CERTIFICATE REQUEST-----
MIIBMDCB1gIBADA6MRQwEgYDVQQDDAtleGFtcGxlLmNvbTEVMBMGA1UECgwMRXhh
bXBsZSBDb3JwMQswCQYDVQQGEwJVUzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IA
BIYKZlt/CNWbYTqLC691fsO++RcFJjrxO4m64frCGMk7D4IswPv2U2tdZWGXATO3
Hr2X8M0I4UMW6ZDEMnmLPBqgOjA4BgkqhkiG9w0BCQ4xKzApMCcGA1UdEQQgMB6C
C2V4YW1wbGUuY29tgg93d3cuZXhhbXBsZS5jb20wCgYIKoZIzj0EAwIDSQAwRgIh
AIGp+sFVJBoC3OU/cD0NlT+atTdM10d8c5hF7mOsw4dbAiEA0HMrS6+d8h1HPvqf
xFnANPWfOZFafiDdMf+69Er5sFA=
-----END CERTIFICATE REQUEST-----
Run it through a decoder and you get: subject CN=example.com, O=Example Corp, C=US, a P-256 public key, SANs example.com and www.example.com, signature algorithm ecdsa-with-SHA256. That’s the complete inventory. You could post this block on a billboard — everything in it is destined to be published inside your certificate anyway.
What the CA does with it — and why it never needs your key
The CA’s job, on receiving your CSR, is three checks and a signature:
- Verify the self-signature against the public key inside the request (proof of possession).
- Validate your control of the names — the DNS challenge, HTTP file, or email dance.
- Issue the certificate: your subject and public key, wrapped with the CA’s assertions and validity dates, signed with the CA’s private key.
Notice what’s absent: at no step does the CA touch, need, or want your private key. The certificate it returns is a public document binding your identity to your public key. Your private key’s only jobs happen on your own server — completing TLS handshakes and, once, signing that CSR. A certificate vendor asking for your private key is like a notary asking for your house keys to certify your address.
That’s why the advice is absolute rather than probabilistic. It isn’t “be careful which sites you paste keys into.” It’s: no legitimate step of certificate issuance ever requires the private key to leave the machine it was generated on. Any workflow that moves it is adding pure risk for zero function.
The traditional answer: generate locally with openssl
The classic, correct workflow has always been local. One command creates a fresh key and a CSR on your own machine:
openssl req -new -newkey rsa:2048 -nodes \
-keyout example.com.key -out example.com.csr \
-subj "/CN=example.com/O=Example Corp/C=US" \
-addext "subjectAltName=DNS:example.com,DNS:www.example.com"
The .csr file goes to the CA; the .key file goes into your server config with chmod 600 and never leaves. (Swap -newkey rsa:2048 for -newkey ec -pkeyopt ec_paramgen_curve:P-256 for an EC key, and drop -nodes if you want the key encrypted at rest.)
This is the trust model to hold in your head: the key is born and dies on hardware you control. The reason people end up on vendor CSR forms anyway is friction — no terminal handy, unfamiliar flags, a Windows box without OpenSSL. Which raises the real question: can a web page match the openssl trust model instead of breaking it?
WebCrypto: the browser is your machine too
It can, because modern browsers ship a native cryptography engine. SubtleCrypto.generateKey() — available only in secure contexts (HTTPS or localhost) — generates key pairs in the browser process itself, supporting exactly the algorithms certificates use: RSASSA-PKCS1-v1_5, RSA-PSS, ECDSA, and more. The same API signs the CertificationRequestInfo structure locally, completing the PKCS#10 recipe from RFC 2986 with zero network involvement.
WebCrypto even encodes “keys shouldn’t travel” into its type system: generateKey takes an extractable flag, and when it’s false the private key becomes un-exportable — exportKey() and wrapKey() refuse, and no script on the page, first-party or injected, can serialize the key material. (A CSR generator has to set extractable: true, since handing you the key file is the whole point — but the flag existing tells you how seriously the platform takes key movement.) The public key, fittingly, is always extractable.
So a well-built browser CSR tool has the same trust boundary as your laptop’s terminal: JavaScript runs on your CPU, keys live in your RAM, and the network is simply never invoked. The difference between that and a server-side generator isn’t a nuance — it’s which side of the wire the secret exists on.
| Vendor web form (server-side) | openssl on your machine | Browser WebCrypto | |
|---|---|---|---|
| Where the key is generated | Their server | Your machine | Your machine (browser process) |
| Does the key cross the network? | Yes — delivered to you over TLS | Never | Never |
| Who could observe it | Their app, logs, TLS terminator, backups | You (and root) | You (and your browser) |
| Works without a terminal | Yes | No | Yes |
| Verifiable behavior | Take their word for it | Read the source | View source / DevTools network tab |
That last row matters: a client-side tool is auditable in a way a server never is. Open DevTools, watch the network panel while you generate — nothing fires.
How our generator does it
This is exactly how our CSR generator is built, and since the site’s source is the receipt, here’s what’s in it. The crypto module’s own header comment states the contract: the private key is generated in the ambient WebCrypto context and “never leaves it: nothing in this module performs I/O.” Key generation is one crypto.subtle.generateKey() call; the PKCS#10 assembly and self-signature run on the same SubtleCrypto instance; the key is handed to you as unencrypted PKCS#8 PEM to download and lock away.
Four algorithm choices, matching what public CAs accept today:
- RSA 2048 — RSASSA-PKCS1-v1_5, signed with SHA-256. The most compatible default.
- RSA 4096 — same scheme, bigger modulus, noticeably slower to generate.
- ECDSA P-256 — signed with SHA-256. Small, fast, universally supported by modern CAs.
- ECDSA P-384 — signed with SHA-384, so the hash strength matches the curve.
It also promotes your CN into the SAN list automatically when it’s a host name — because public CAs match against subjectAltName and ignore the CN, a detail plenty of CSR forms still get wrong. And the decoder tab has one opinion baked in: paste something that begins -----BEGIN PRIVATE KEY----- and it refuses with “Never paste a private key into a web page.” Yes, even into ours — a client-side tool being trustworthy is not a reason to practice the habit that will eventually burn you on a tool that isn’t. (If you want a raw keypair without the CSR ceremony, the same client-side rule applies to our key pair generator.)
The industry is moving the same direction
If you want evidence that “keys must not travel” is the direction of the whole ecosystem, look at code signing. The CA/Browser Forum’s Baseline Requirements for code-signing certificates (§6.2.7.4.2) mandate that, effective June 1, 2023, CAs “SHALL ensure that the Subscriber’s Private Key is generated, stored, and used in a suitable Hardware Crypto Module.” Not “on a trusted server” — in tamper-resistant hardware the subscriber controls, for the key’s entire life. The days of a code-signing key living in a .pfx file that gets emailed around are formally over. TLS server keys aren’t under the same hardware mandate, but the principle the rule encodes is the one this whole post is about: private keys are location-bound secrets, and every system that handles them is being redesigned to stop them moving.
After the CSR
The workflow, end to end, with the secret staying put:
- Generate the key pair and CSR locally — in your browser or via
openssl req. - Save the private key immediately; it cannot be regenerated.
- Submit the CSR only to your CA and complete domain validation.
- Get the certificate back, and check it before deploying — paste it into the X.509 decoder to confirm the CA carried your SANs through, and see how to read an X.509 certificate for what all the fields mean.
- Install certificate + chain + your never-transmitted key on the server.
The pattern generalizes beyond TLS, too: SSH keys obey the identical rule — generate locally, publish only the public half — and if you’re choosing an algorithm there, Ed25519 vs RSA for SSH keys walks the same trade-offs.
A CSR is the rare piece of security plumbing that’s designed to be safe to share. The private key is the opposite: the one artifact whose entire value is that it has never existed anywhere but your hardware. Any tool that blurs that line — however convenient the form — is asking you to trade away the property the whole system exists to protect.