RSA Key Generator Guide: Public/Private Keys, PEM, DER, JWK, and Key Sizes
RSA key pairs are the backbone of TLS certificates, SSH authentication, JWT signing, and code-signing workflows. Yet there are surprisingly many ways to get the details wrong: wrong key size, wrong file format, unprotected private key, wrong algorithm choice. Use the RSA Key Generator to create key pairs and follow along with the examples in this guide.
Public vs Private Key Roles
RSA is an asymmetric algorithm: two mathematically linked keys that can never be derived from each other in reasonable time. They have complementary roles depending on the operation:
| Operation | Who uses the private key | Who uses the public key |
|---|---|---|
| Signing | Signer computes signature | Anyone verifies the signature |
| Encryption | Owner decrypts ciphertext | Sender encrypts plaintext |
| SSH auth | Client proves identity (signs challenge) | Server checks against authorized_keys |
| TLS handshake | Server decrypts pre-master secret | Client encrypts pre-master secret |
The private key is secret and must never leave the machine or service that owns it. The public key is meant to be shared freely — embed it in certificates, push it to SSH servers, publish it in a JWK Set endpoint.
Key Sizes and Security Levels
RSA security comes from the difficulty of factoring large numbers. Larger keys are harder to factor, but are slower to use. NIST recommendations as of 2024:
| Key Size | Security Level | NIST Status | Notes |
|---|---|---|---|
| 1024 bits | 80-bit (broken) | Disallowed since 2013 | Factored in practice; reject on sight |
| 2048 bits | 112-bit security | Acceptable through 2030 | Minimum for new deployments today |
| 3072 bits | 128-bit security | Recommended after 2030 | Good balance of security and performance |
| 4096 bits | 140-bit security | Strong long-term option | Noticeably slower; use for CA root keys |
For most server TLS certificates and SSH keys, 2048 bits remains the practical baseline. Use 4096 bits when the key is long-lived (certificate authority root keys, code-signing keys) and performance is not critical. The marginal security gain of 4096 over 2048 is real but small — the bigger wins come from key rotation policy and passphrase protection.
PEM vs DER vs JWK
An RSA key pair is ultimately just a set of large integers. The format describes how those integers are serialized and transported:
PEM (Privacy Enhanced Mail)
PEM is the most common format for file-based key storage. It wraps a base64-encoded DER structure in ASCII header/footer lines:
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2a2rwplBQLF29amygykE
...
-----END PUBLIC KEY-----PEM files are text, so they survive copy-paste, email, and environment variables without corruption. The header line tells parsers what kind of key or certificate to expect. PEM is used by OpenSSL, nginx, Apache, most SSH tools, and TLS libraries.
DER (Distinguished Encoding Rules)
DER is the binary encoding that PEM wraps. It is the raw ASN.1 structure without the base64 or header lines. DER files are not human-readable, use less space, and are required by some Java (keytool/JSSE) and Windows (CryptoAPI) environments. The file extension is typically .der or .cer.
Convert between PEM and DER with OpenSSL:
# PEM to DER
openssl rsa -in key.pem -outform DER -out key.der
# DER to PEM
openssl rsa -in key.der -inform DER -out key.pemJWK (JSON Web Key)
JWK represents keys as JSON objects, making them native to web APIs. A JWK Set (JWKS) is a JSON document containing multiple public keys, typically served at a /.well-known/jwks.json endpoint for JWT signature verification:
{
"keys": [
{
"kty": "RSA",
"use": "sig",
"kid": "2026-04",
"alg": "RS256",
"n": "2a2rwplBQLzHPZe5RJr9...",
"e": "AQAB"
}
]
}Fields: kty = key type (RSA), use = sig (signing) or enc (encryption), kid = key ID for rotation, n = modulus (base64url), e = public exponent (base64url). Private JWKs also include d, p, q, and CRT parameters.
PKCS#1 vs PKCS#8
Two PEM header variants appear in the wild for RSA private keys, and confusing them causes parsing errors:
| Header | Standard | Algorithm info | Common usage |
|---|---|---|---|
-----BEGIN RSA PRIVATE KEY----- | PKCS#1 | RSA-only; algorithm implicit | OpenSSL legacy, older SSH tools |
-----BEGIN PRIVATE KEY----- | PKCS#8 | Algorithm OID embedded | Modern standard; works for RSA, EC, Ed25519 |
-----BEGIN ENCRYPTED PRIVATE KEY----- | PKCS#8 encrypted | Algorithm OID + encryption params | Passphrase-protected private key |
PKCS#8 is the modern format. Prefer it for new keys. Convert a PKCS#1 key to PKCS#8 with OpenSSL:
# PKCS#1 → PKCS#8 (unencrypted)
openssl pkcs8 -topk8 -nocrypt -in pkcs1.pem -out pkcs8.pem
# PKCS#1 → PKCS#8 (AES-256 encrypted)
openssl pkcs8 -topk8 -v2 aes-256-cbc -in pkcs1.pem -out pkcs8-enc.pemFor public keys, the equivalent split is PKCS#1 (-----BEGIN RSA PUBLIC KEY-----) vs X.509 SubjectPublicKeyInfo (-----BEGIN PUBLIC KEY-----). Most modern tools expect the X.509 SPKI form.
RSA vs ECDSA vs Ed25519
RSA is not the only asymmetric algorithm. For new systems, elliptic-curve alternatives are often preferable:
| Algorithm | Key size for 128-bit security | Signature size | Speed | Notes |
|---|---|---|---|---|
| RSA-3072 | 3072 bits | 384 bytes | Slow keygen, fast verify | Universal compatibility |
| ECDSA P-256 | 256 bits | 64 bytes | Fast | Requires good randomness per signature |
| ECDSA P-384 | 384 bits | 96 bytes | Fast | Suite B compliant; used in government PKI |
| Ed25519 | 256 bits | 64 bytes | Fastest | No per-signature randomness; deterministic |
When to use RSA: legacy systems that do not support elliptic curves, Java environments with limited provider support, X.509 certificates for broad compatibility, JWT with RS256 when the receiver cannot handle ES256.
When to use Ed25519: SSH keys on modern OpenSSH (6.5+), new JWT signing (EdDSA), and any system where you control both ends. Smaller keys, smaller signatures, no randomness dependency, and faster than RSA at equivalent security.
Generating Keys
OpenSSL
# Generate RSA-2048 private key (PKCS#8, unencrypted)
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out private.pem
# Generate RSA-2048 private key (PKCS#8, AES-256 encrypted)
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 \
-aes-256-cbc -out private-enc.pem
# Extract public key from private key
openssl pkey -in private.pem -pubout -out public.pem
# Generate RSA-4096
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:4096 -out private4096.pemssh-keygen
# RSA-4096 SSH key with passphrase prompt
ssh-keygen -t rsa -b 4096 -C "user@example.com" -f ~/.ssh/id_rsa
# Ed25519 SSH key (recommended for new keys)
ssh-keygen -t ed25519 -C "user@example.com" -f ~/.ssh/id_ed25519
# Change passphrase on existing key
ssh-keygen -p -f ~/.ssh/id_rsaNode.js (crypto module)
import { generateKeyPair } from 'node:crypto';
import { promisify } from 'node:util';
const generateKeyPairAsync = promisify(generateKeyPair);
const { privateKey, publicKey } = await generateKeyPairAsync('rsa', {
modulusLength: 2048,
publicKeyEncoding: {
type: 'spki', // X.509 SubjectPublicKeyInfo
format: 'pem',
},
privateKeyEncoding: {
type: 'pkcs8', // PKCS#8
format: 'pem',
// Optional: encrypt with passphrase
// cipher: 'aes-256-cbc',
// passphrase: 'your-passphrase',
},
});
console.log(publicKey); // -----BEGIN PUBLIC KEY-----
console.log(privateKey); // -----BEGIN PRIVATE KEY-----Python (cryptography library)
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives import serialization
# Generate private key
private_key = rsa.generate_private_key(
public_exponent=65537,
key_size=2048,
)
# Serialize private key to PEM (PKCS#8, unencrypted)
pem_private = private_key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.PKCS8,
encryption_algorithm=serialization.NoEncryption(),
)
# Serialize public key to PEM (SPKI)
pem_public = private_key.public_key().public_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PublicFormat.SubjectPublicKeyInfo,
)
print(pem_public.decode())
print(pem_private.decode())Passphrase Protection
A private key stored on disk without a passphrase is only as secure as the file system permissions protecting it. If an attacker gains read access to the file — through a server breach, backup leak, or misconfigured permissions — they have your private key immediately.
Passphrase protection encrypts the private key using a symmetric cipher (typically AES-256 in CBC mode) derived from the passphrase via a KDF (PBKDF2 or scrypt). The result is an -----BEGIN ENCRYPTED PRIVATE KEY----- PEM block. Without the passphrase, the file is useless.
The tradeoffs:
- Interactive systems (SSH, git): Use a passphrase and add the key to ssh-agent so you only enter it once per session. The agent holds the decrypted key in memory.
- Automated systems (CI/CD, daemons): Passphrase protection is less practical. Use a secrets manager (Vault, AWS Secrets Manager, GCP Secret Manager) to store the raw key and inject it at runtime via environment variable or tmpfs mount.
- TLS certificates on servers: Most web servers load the private key unencrypted at startup. Protect the file with strict permissions and consider HSM-backed keys for high-value certificates.
# Add key to ssh-agent (decrypts once, holds in memory for the session)
ssh-add ~/.ssh/id_rsa
# List keys in agent
ssh-add -l
# Remove all keys from agent
ssh-add -DStorage Best Practices
- File permissions: Private key files must be readable only by their owner. On Linux/macOS:
chmod 600 ~/.ssh/id_rsa. OpenSSH refuses to use keys with world-readable permissions. - Never commit private keys to version control. Add
*.pem,id_rsa, and*.keyto.gitignore. If a key is ever committed, treat it as compromised and rotate immediately — git history is not erasable in practice. - Secrets managers: For application keys in production, store in HashiCorp Vault, AWS Secrets Manager, or GCP Secret Manager. Retrieve at runtime; do not bake keys into container images or config files.
- HSM / KMS: For CA root keys, code-signing keys, and high-value certificates, use a Hardware Security Module or cloud KMS (AWS KMS, GCP Cloud KMS, Azure Key Vault). The private key never leaves the hardware boundary; only the sign/decrypt operation is exposed.
- Key rotation: Plan for rotation before you need it. Document which services depend on each key, and automate certificate renewal (ACME / Let's Encrypt for TLS).
# Set correct permissions on SSH private key
chmod 600 ~/.ssh/id_rsa
chmod 644 ~/.ssh/id_rsa.pub
# Verify permissions
ls -la ~/.ssh/SSH Key Workflow
SSH public-key authentication replaces password authentication: the server holds your public key in ~/.ssh/authorized_keys and your client proves identity by signing a challenge with the corresponding private key.
Setting up a new SSH key
# 1. Generate the key pair
ssh-keygen -t ed25519 -C "user@example.com" -f ~/.ssh/id_ed25519
# 2. Copy public key to server
ssh-copy-id -i ~/.ssh/id_ed25519.pub user@server.example.com
# Or manually: append ~/.ssh/id_ed25519.pub to ~/.ssh/authorized_keys on the server
# 3. Test the connection
ssh -i ~/.ssh/id_ed25519 user@server.example.comThe authorized_keys format
Each line in ~/.ssh/authorized_keys contains one public key in OpenSSH wire format:
ssh-ed25519 AAAA...base64... user@example.com
ssh-rsa AAAA...base64... user@laptop
# Options prefix restricts usage:
from="10.0.0.0/8",no-pty ssh-ed25519 AAAA... deploy-botUsing ~/.ssh/config for multiple keys
# ~/.ssh/config
Host github.com
IdentityFile ~/.ssh/id_ed25519_github
User git
Host prod-server
HostName 203.0.113.10
User deploy
IdentityFile ~/.ssh/id_rsa_prod
Port 2222
Host bastion
HostName bastion.example.com
IdentityFile ~/.ssh/id_ed25519
ForwardAgent yesForwardAgent yes forwards the ssh-agent connection through the bastion so you can reach internal servers without copying private keys to the bastion host. Only enable it for trusted jump hosts.
Common Pitfalls
- Storing private keys alongside public keys in a repo. The public key is safe to commit; the private key is not. Automate a check with pre-commit hooks or secret scanning (GitGuardian, TruffleHog).
- Using RSA-1024 in legacy code. Audit dependencies and TLS configurations. Any RSA-1024 key should be replaced immediately.
- Confusing PKCS#1 and PKCS#8 headers. If a library throws a parse error on a valid-looking PEM, check the header line — convert with
openssl pkcs8as shown above. - Forgetting to protect private key file permissions in Docker. Files copied with
COPYdefault to world-readable. Use--chmod=600or a multi-stage build to avoid leaking secrets in intermediate layers. - Relying on key size alone. A 4096-bit RSA key with a weak passphrase stored world-readable on a public S3 bucket provides zero security. Key management matters more than key size.
Generate RSA key pairs of any size and export them in PEM, DER, or JWK format directly in your browser with the RSA Key Generator — all computation happens client-side, no keys are transmitted. For related reading, see X.509 Certificate Decoding Explained and Hashes, Encryption, and Password Hashing.