BlogRSA Key Pairs Explained: Generating and Using RSA Keys for JWT Signing (RS256)
·Updated September 22, 2026·11 min read·JWTSecrets Team

RSA Key Pairs Explained: Generating and Using RSA Keys for JWT Signing (RS256)

How RSA key pairs work, how they differ from HS256 shared secrets, and how to generate and use them for RS256 JWT signing in Node.js, Python, and Go.

RSA Key Pairs Explained: Generating and Using RSA Keys for JWT Signing (RS256)

If you've compared HS256 vs RS256 and decided you need asymmetric signing — for a multi-service architecture, a public API where third parties need to verify your tokens, or an OAuth/OIDC provider — the next question is practical: how do you actually generate the key pair, and what do you do with the two halves once you have them?

This guide walks through RSA key pairs specifically for JWT signing: what the public/private split means in practice, how to generate a pair correctly, and common mistakes that trip people up the first time they wire up RS256.

Public and private keys, in practical terms

RSA is asymmetric: you generate one key pair, consisting of a private key and a public key, mathematically linked but computationally infeasible to derive one from the other.

For JWT signing (RS256), the roles are:

  • Private key — used to *sign* tokens. Only your auth service should ever hold this. If it leaks, an attacker can forge valid tokens for your system.
  • Public key — used to *verify* tokens. This is safe to share freely — publish it, embed it in client SDKs, expose it via a JWKS endpoint. Anyone with the public key can confirm a token was signed by you, but they cannot use it to create new tokens.

This is the core advantage over HS256: with a shared secret (HS256), anything that can verify a token can also forge one, because it's the same key. With RS256, you can hand out unlimited verification capability (public key) without handing out any forging capability. That's exactly what you want when multiple services, or third parties outside your control, need to verify tokens your auth service issues.

Choosing a key size

2048-bit RSA is the current practical minimum — anything smaller (1024-bit) is considered breakable with enough resources and shouldn't be used for anything security-sensitive today. 4096-bit is more conservative and still common, at the cost of somewhat slower signing/verification and larger tokens (the signature itself is larger). For most JWT use cases, 2048-bit is the standard choice and what libraries default to; reach for 4096-bit if you have a specific compliance requirement or a long key-lifetime concern.

Generating an RSA key pair

Node.js:

const crypto = require('crypto');

const { publicKey, privateKey } = crypto.generateKeyPairSync('rsa', {
  modulusLength: 2048,
  publicKeyEncoding: { type: 'spki', format: 'pem' },
  privateKeyEncoding: { type: 'pkcs8', format: 'pem' },
});

console.log(privateKey); // -----BEGIN PRIVATE KEY-----
console.log(publicKey);  // -----BEGIN PUBLIC KEY-----

Python (using `cryptography`):

from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives import serialization

private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)

private_pem = private_key.private_bytes(
    encoding=serialization.Encoding.PEM,
    format=serialization.PrivateFormat.PKCS8,
    encryption_algorithm=serialization.NoEncryption(),
)
public_pem = private_key.public_key().public_bytes(
    encoding=serialization.Encoding.PEM,
    format=serialization.PublicFormat.SubjectPublicKeyInfo,
)

Go:

package main

import (
	"crypto/rand"
	"crypto/rsa"
	"crypto/x509"
	"encoding/pem"
	"fmt"
)

func main() {
	privateKey, _ := rsa.GenerateKey(rand.Reader, 2048)

	privBytes := x509.MarshalPKCS1PrivateKey(privateKey)
	privPem := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: privBytes})

	pubBytes, _ := x509.MarshalPKIXPublicKey(&privateKey.PublicKey)
	pubPem := pem.EncodeToMemory(&pem.Block{Type: "PUBLIC KEY", Bytes: pubBytes})

	fmt.Println(string(privPem))
	fmt.Println(string(pubPem))
}

Or via OpenSSL (language-agnostic, useful for one-off generation or CI scripts):

openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out private.pem
openssl rsa -pubout -in private.pem -out public.pem

If you just need a pair quickly without writing code, our RSA Key Generator generates one client-side in your browser and gives you both PEM files directly — nothing is transmitted anywhere.

Using the pair to sign and verify a JWT

Once you have both keys, RS256 signing looks like this (Node.js, using jsonwebtoken):

const jwt = require('jsonwebtoken');

// Signing (auth service, holds the private key)
const token = jwt.sign({ sub: 'user-123' }, privateKey, { algorithm: 'RS256' });

// Verifying (any service, only needs the public key)
const decoded = jwt.verify(token, publicKey, { algorithms: ['RS256'] });

The critical detail: always pin algorithms: ['RS256'] explicitly on verification. This closes the classic "algorithm confusion" attack, where a token is crafted with alg: HS256 and an attacker uses your *public* RSA key as if it were an HMAC shared secret — since the public key is, well, public, a verifier that blindly trusts the token's alg header can be tricked into accepting a forged token. Pinning the expected algorithm on the verify call prevents this class of attack entirely.

Publishing your public key via JWKS

If third parties or multiple independent services need to verify your tokens, don't distribute the raw PEM file — publish a JWKS (JSON Web Key Set) endpoint instead. This is the standard OIDC/OAuth pattern: your public key(s) are exposed at a well-known URL (commonly /.well-known/jwks.json), in a structured JSON format that libraries can consume directly, and it supports key rotation (multiple keys, identified by kid) without downtime. We cover the operational side of this — publishing, caching, and rotating — in JWKS in Production.

Protecting the private key

The private key is the entire security boundary of RS256 — treat it the same way (or more carefully) than you would an HS256 shared secret:

  • Never commit it to source control, ever, even in a "private" repo.
  • Store it in a secrets manager or KMS, not a config file on disk, in production.
  • Restrict which services can access it — ideally only the signing service, not every service that merely verifies tokens (those only need the public key).
  • Have a rotation plan; RSA keys don't need to rotate as often as symmetric secrets in threat-driven scenarios, but should still rotate on a schedule and immediately if compromise is suspected.

See how to store JWT secrets securely — the same storage hierarchy (env vars → secrets manager → KMS) applies here.

FAQ

Can I use the same RSA key pair for JWT signing and general encryption?

Best practice is no — use separate key pairs per purpose. Signing and encryption have different security properties, and reusing a key across both can, in some configurations, weaken guarantees for either.

What's the difference between RS256 and ES256?

Both are asymmetric, but ES256 uses elliptic curve cryptography instead of RSA — smaller keys and signatures for equivalent security, and faster operations, at the cost of slightly less universal library support. See RS256 vs ES256 for the full comparison.

Do I need RS256 if I only have one backend service?

Probably not — if there's exactly one service that both signs and verifies tokens, HS256's shared secret is simpler to manage and equally secure for that setup. RS256 earns its complexity when verification needs to happen somewhere the private key shouldn't live. See HS256 vs RS256.

What format should I store the private key in — PEM or something else?

PEM (PKCS#8) is the most broadly supported format across languages and libraries; stick with it unless a specific tool requires DER or another format.

---

Generate an RSA key pair now: our free RSA Key Generator runs entirely client-side in your browser.

*Related: HS256 vs RS256 · Symmetric vs Asymmetric JWT Signing · JWKS in Production*

Written by

JWTSecrets Team

Editorial Team

The JWTSecrets editorial team writes practical guides on JWT authentication, cryptographic key management, and browser-based security tooling. Our content is reviewed against IETF RFCs and current library documentation.