RSA Encrypt / Decrypt

Generate RSA key pairs in the browser, encrypt and decrypt with RSA-OAEP, sign with RSA-PSS or PKCS#1 v1.5, and convert keys between SPKI, PKCS#8 and PKCS#1 PEM formats. Everything runs locally with the WebCrypto engine — no key or message ever leaves this page.
What RSA is for: small messages and signatures, not bulk data. RSA encrypts at most k−2·hashLen−2 bytes per call (190 bytes for a 2048-bit key with SHA-256) and is ~1000× slower than AES. Real systems use hybrid encryption: RSA wraps a random AES key, AES encrypts the data (see the AES tool). This page exists for learning, testing and interop checks.
Modulus size:
Hash: Label:
PKCS#1 v1.5 encryption is deliberately not implemented: it has no ciphertext integrity and falls to padding-oracle attacks (Bleichenbacher). OAEP is the only encryption mode a new design should use.
Scheme: Hash:
Scheme: Hash:
How much can one RSA call carry? OAEP message limit = key bytes − 2×hash bytes − 2:
ModulusSHA-256SHA-384SHA-512
1024-bit62 bytes30 bytes0 bytes
2048-bit190 bytes126 bytes62 bytes
3072-bit318 bytes254 bytes190 bytes
4096-bit446 bytes382 bytes318 bytes
Signatures have no message limit — the message is hashed first; the signature itself is always exactly the key size (256 bytes for 2048-bit).
Key strength and lifetime (NIST SP 800-57 Part 1 Rev. 5): 1024-bit was disallowed for new use after 2013; 2048-bit is the minimum today and remains acceptable through 2030; 3072-bit and beyond are the comfortable choice for 2031+. 8192-bit works here but generation can take a minute or more in the browser and buys security nobody currently needs — prefer 3072/4096.
OpenSSL interop — the PEMs this page produces round-trip on the command line:
openssl pkeyutl -encrypt -pubin -inkey pub.pem -pkeyopt rsa_padding_mode:oaep -pkeyopt rsa_oaep_md:sha256 -pkeyopt rsa_mgf1_md:sha256
openssl pkeyutl -decrypt -inkey priv.pem -pkeyopt rsa_padding_mode:oaep ...
openssl dgst -sha256 -sign priv.pem -out msg.sig msg.txt (PKCS#1 v1.5)  /  openssl dgst -sha256 -verify pub.pem -signature msg.sig msg.txt
PSS: add -sigopt rsa_padding_mode:pss -sigopt rsa_pss_saltlen:32 to both dgst calls. Key format conversion: openssl rsa -in key.pem -RSAPublicKey -out pubkey.pem does SPKI ↔ PKCS#1 on disk; this page does it in the browser.
Self-test
Runs the page engine against independent anchors: key generation at three modulus sizes, OAEP encrypt/decrypt roundtrips, PSS and PKCS#1 v1.5 sign/verify across hashes, a deterministic v1.5 signature vector byte-for-byte (generated by an independent native implementation), a fixed OAEP ciphertext and PSS signature produced by OpenSSL that this page must decrypt/verify (cross-implementation interop), PEM format conversion roundtrips, and negative tests (tampered signature, wrong key must fail). Nothing is sent anywhere.
RSA encryption and signatures explained from scratch
In one sentence: RSA is public-key cryptography — anyone can encrypt to you with the public key or verify your signatures, but only the private key can decrypt or sign — and its security rests on the fact that factoring a 2048-bit number into two primes is beyond any computer that exists.
What RSA actually computes

Two primes and a trapdoor. An RSA key starts as two large random primes p and q; their product n (the modulus, hence "2048-bit key") is published in the public key together with an exponent e (almost always 65537). The private key is the pair d, the modular inverse of e − computed from p and q — plus shortcuts derived from the primes that speed private-key operations up 4×.

Encryption and signing are the same exponentiation. Encrypting a message block m means computing me mod n; decrypting is cd mod n. Signing a pre-formatted block with the private exponent and verifying with the public one is the same operation in the other direction. Everything else on this page — OAEP, PSS, PKCS#1 — is about formatting that block safely so raw exponentiation is never exposed.

Why it is slow. One RSA operation on a 2048-bit key costs on the order of a million times more CPU than encrypting one block with AES. That is why TLS uses RSA (or ECC) only to agree on a random AES key, then switches. The same rule applies to anything you build: RSA for the key, AES for the data.

Key size. The modulus length is the security dial: 1024-bit is broken-by-policy (factoring is near the reach of nation-states), 2048-bit ≈ 112-bit symmetric strength, 3072-bit ≈ 128-bit, 15360-bit would match a 256-bit symmetric key. Post-quantum note: Shor’s algorithm breaks RSA outright on a large quantum computer — one more reason to keep key lifetimes finite.

OAEP: the only encryption padding you should use

The problem with raw RSA. Textbook RSA (me mod n on the raw message) is deterministic and malleable: identical messages encrypt identically, an attacker can multiply a ciphertext by the encryption of 2 to make the plaintext double, and short messages fall to meet-in-the-middle. It must never be used directly.

What OAEP does. OAEP (PKCS#1 v2, RFC 8017) mixes the message with a random seed through a hash-based Feistel network before exponentiation. The result: encryption is randomized (the same plaintext gives a different ciphertext every time — run the OAEP Sample twice and compare), tampering is detected on decryption, and the message must fit inside the modulus with room for the hash — the k−2·hashLen−2 limit tabulated above.

The label. An optional public string bound into the padding. Both sides must supply the identical label or decryption fails — like the AAD in AES-GCM, it lets you bind ciphertext to context (a file name, an algorithm id). Empty is normal.

PKCS#1 v1.5 encryption is absent on purpose. The 1993-era padding has no integrity and leaks information through decryption error behaviour (Bleichenbacher’s 1998 attack turns a padding oracle into full decryption with a few million crafted ciphertexts; it still breaks real systems as ROBOT, 2017). WebCrypto refuses to implement it; so does this page.

PSS vs PKCS#1 v1.5 signatures

Sign = hash, then pad, then exponentiate. Both schemes hash the message (SHA-256/384/512 here), format the digest into a block, and apply the private exponent. Verify re-formats the block with the public key and compares. The difference is the formatting:

SchemeBlock contentsProperties
RSASSA-PKCS1-v1_5digest + digest-info header, deterministicEvery signature of the same message is byte-identical (check the self-test: its v1.5 vector is fixed). Universal legacy support: X.509 certificates, JWT RS*, TLS before 1.3. No known forge when verification is implemented correctly.
RSA-PSSdigest + random salt of hash length, MGF1 maskingRandomized — each signature differs. Provably secure in the random-oracle model; required in TLS 1.3 certificates-of-the-future and newer protocols. Verifier must know the salt length (this page uses hash length, the common default).

Which to pick. New designs: PSS. Interop with existing certificates, JWTs (RS256) or old peers: v1.5. Both are safe as signature schemes — the v1.5 disaster story is about *encryption* padding, a different construction despite the similar name.

PKCS#1 vs PKCS#8 vs SPKI: why the same key has four PEMs

PEM is just base64-wrapped DER. A PEM file is a label plus base64 of a DER (binary ASN.1) structure. The label tells you which structure — and that is the whole difference between the four formats this page generates:

PEM labelDER structureContains
-----BEGIN PUBLIC KEY-----SubjectPublicKeyInfo (SPKI)Algorithm OID + public key (n, e). The default "public key" everywhere.
-----BEGIN RSA PUBLIC KEY-----PKCS#1 RSAPublicKeyBare (n, e). Old OpenSSL and some embedded stacks.
-----BEGIN PRIVATE KEY-----PKCS#8 PrivateKeyInfoAlgorithm OID + private key. Algorithm-agnostic container, the modern default.
-----BEGIN RSA PRIVATE KEY-----PKCS#1 RSAPrivateKeyn, e, d, p, q, …. The classic OpenSSL genrsa output.

Same key, different wrappers. All four boxes above after Generate hold the same key pair — SPKI and PKCS#1 public contain identical n, e; PKCS#8 and PKCS#1 private contain identical private values. This page converts between them losslessly in the browser (its self-test round-trips the conversions). Confusing the formats is the single most common cause of "invalid key" errors when pasting keys between tools.

Encrypted PEMs (-----BEGIN ENCRYPTED PRIVATE KEY-----) are PKCS#8 with a password-based encryption layer. This page does not accept them — decrypt them offline (openssl pkcs8 -in enc.pem -out plain.pem) first.

Common mistakes
  • Encrypting bulk data with RSA. Beyond the hard OAEP size limit it is absurdly slow. The pattern is: generate a random AES key, RSA-OAEP-encrypt the AES key, AES-GCM-encrypt the data, send both parts.
  • Shipping the private key because "it’s a test". The sample keys embedded in this page’s vectors are published deliberately — they can only ever sign test data. Your keys are not like that.
  • Verifying with the private key. It usually "works" mathematically and some tools allow it, but distributing a private key to verifiers defeats the whole model. Verify with the public key (this page enforces it).
  • Comparing signatures byte-for-byte to check code. PSS (and OAEP) are randomized: two correct runs produce different outputs. Compare by verifying, not by equality — only v1.5 signatures are deterministic.
  • 1024-bit keys "because they’re faster". They are also within reach of well-funded factoring. The speed argument died with hybrid encryption: RSA runs once per session, not per byte.
  • Hardcoding a salt length other than hash length for PSS. The verifier must agree; hash length (this page’s choice and OpenSSL’s -sigopt rsa_pss_saltlen:32) is the interoperable default.
FAQ

Why did my decryption fail with "wrong key, wrong hash or corrupted ciphertext"? OAEP decryption fails when any of: it is not the matching private key, the hash differs from the encryption side, the label differs, or a ciphertext byte changed. The error is deliberately identical for all cases — revealing which check failed is itself a padding-oracle leak.

Can I get the public key from the private key? Yes — the private formats contain n and e too. This page shows both after Generate; OpenSSL does it with openssl pkey -in priv.pem -pubout.

Why 65537 as the exponent? It is prime, has only two 1-bits (fast exponentiation), and is large enough to avoid the low-exponent attacks that hit e=3. It has been the standard choice since the 1990s; a "weird" exponent usually means a weak or non-standard key.

Is RSA dead because of quantum computers? Not yet — no machine exists that can run Shor’s algorithm on a 2048-bit modulus. But data that must stay secret into the 2030s+ is already being migrated; PQC hybrid schemes are the engineering answer.

What about openssl rsautl? It is the deprecated ancestor of pkeyutl. It defaults to v1.5 encryption padding — one more reason it was retired.