Encrypt Data with RSA Public Key

Step-by-step guide to encrypting data with an RSA public key using RSA-OAEP padding. Covers key generation, hybrid encryption patterns, and decryption. Try it in your browser.

RSA Encryption

Detailed Explanation

Encrypting Data with RSA: A Practical Guide

RSA public-key encryption allows anyone with your public key to encrypt data that only your private key can decrypt. This guide walks through the complete process from key generation to decryption.

Step 1: Generate an RSA Key Pair

An RSA key pair consists of a public key (shared openly) and a private key (kept secret). Key size should be at least 2048 bits:

const keyPair = await crypto.subtle.generateKey(
  {
    name: "RSA-OAEP",
    modulusLength: 2048,
    publicExponent: new Uint8Array([1, 0, 1]), // 65537
    hash: "SHA-256",
  },
  true,
  ["encrypt", "decrypt"]
);

The public exponent 65537 (0x10001) is standard — it provides a good balance between security and performance.

Step 2: Export the Public Key

To share the public key with others, export it in a standard format:

const publicKeyData = await crypto.subtle.exportKey("spki", keyPair.publicKey);
const publicKeyBase64 = btoa(String.fromCharCode(...new Uint8Array(publicKeyData)));

The SPKI (Subject Public Key Info) format is the standard for public key exchange.

Step 3: Encrypt with the Public Key

RSA-OAEP can only encrypt small messages (190 bytes for 2048-bit keys with SHA-256). For larger data, use hybrid encryption:

// Generate a random AES key
const aesKey = await crypto.subtle.generateKey(
  { name: "AES-GCM", length: 256 },
  true, ["encrypt", "decrypt"]
);

// Encrypt the data with AES-GCM
const iv = crypto.getRandomValues(new Uint8Array(12));
const encryptedData = await crypto.subtle.encrypt(
  { name: "AES-GCM", iv },
  aesKey, plaintext
);

// Export and encrypt the AES key with RSA-OAEP
const rawAesKey = await crypto.subtle.exportKey("raw", aesKey);
const encryptedKey = await crypto.subtle.encrypt(
  { name: "RSA-OAEP" },
  keyPair.publicKey,
  rawAesKey
);

Step 4: Package the Result

The encrypted output includes three components:

const payload = {
  encryptedKey: arrayBufferToBase64(encryptedKey),
  iv: arrayBufferToBase64(iv),
  ciphertext: arrayBufferToBase64(encryptedData),
};

Step 5: Decrypt with the Private Key

The recipient reverses the process:

// Decrypt the AES key with RSA private key
const decryptedAesKeyData = await crypto.subtle.decrypt(
  { name: "RSA-OAEP" },
  keyPair.privateKey,
  encryptedKey
);

// Import the AES key
const decryptedAesKey = await crypto.subtle.importKey(
  "raw", decryptedAesKeyData,
  { name: "AES-GCM" },
  false, ["decrypt"]
);

// Decrypt the data with AES-GCM
const decryptedData = await crypto.subtle.decrypt(
  { name: "AES-GCM", iv },
  decryptedAesKey,
  encryptedData
);

Security Considerations

  • Always use RSA-OAEP, never PKCS#1 v1.5 for new applications
  • Use at least 2048-bit keys (3072 or 4096 for long-term security)
  • Never encrypt directly with RSA for data longer than the key allows — always use hybrid encryption
  • Store private keys securely — consider using the non-extractable flag in Web Crypto API

Use Case

RSA public-key encryption is essential for scenarios where the sender and recipient cannot share a secret key in advance. Common use cases include end-to-end encrypted messaging in web applications, secure file sharing where only the intended recipient can decrypt, encrypting database fields with per-user keys, and implementing secure key exchange protocols without a pre-shared secret.

Try It — Encryption Playground

Open full tool