Decorative header image for Public Key Cryptography with RSA: Complete Implementation Guide

Public Key Cryptography with RSA: Complete Implementation Guide

Master RSA cryptography fundamentals, implement secure key generation, understand digital signatures, and build robust public key infrastructure with comprehensive best practices and real-world applications.

By Gray-wolf Security Team Cryptography Engineering
Updated 11/3/2025 ~1200 words
rsa-encryption public-key-cryptography asymmetric-encryption digital-signatures cryptography-implementation security-architecture

Introduction

Public key cryptography, pioneered by the RSA algorithm in 1977, revolutionized digital security by solving one of cryptography’s fundamental challenges: how to securely communicate without first sharing a secret key. Named after its inventors Ron Rivest, Adi Shamir, and Leonard Adleman, RSA encryption enables secure communications, digital signatures, and authentication in a world where parties have never met and cannot trust intermediaries.

Today, RSA forms the backbone of internet security, protecting everything from email communications and online banking to software distribution and cryptocurrency transactions. Understanding RSA cryptography is essential for security professionals, developers implementing authentication systems, and anyone building applications that require secure data exchange or identity verification.

This comprehensive guide explores RSA’s mathematical foundations, practical implementation strategies, key management best practices, and real-world applications. Whether you’re implementing SSH authentication, creating digital signatures, or building a complete Public Key Infrastructure (PKI), this knowledge will guide you through secure RSA deployment. Our RSA Key Generator tool demonstrates these concepts in action, providing hands-on experience with professional-grade key generation.

Background and Mathematical Foundations

The Problem RSA Solves

Before public key cryptography, all encryption was symmetric - the same key encrypted and decrypted messages. This created a fundamental challenge: how do two parties establish a shared secret when communicating over an insecure channel? Meeting in person to exchange keys didn’t scale, and sending keys electronically risked interception.

The Symmetric Key Problem:

Alice wants to send encrypted message to Bob

Traditional approach (symmetric):
1. Alice and Bob must first meet to exchange secret key
2. Or send key over insecure channel (vulnerable to interception)
3. Single key compromise exposes all communications
4. Scaling problem: n users require n(n-1)/2 shared keys

Public key solution:
1. Bob publishes public key (can be shared openly)
2. Alice encrypts with Bob's public key
3. Only Bob's private key can decrypt
4. No prior secure key exchange required
5. Scaling: n users require only 2n keys (public + private each)

How RSA Works: The Mathematics

RSA security relies on the mathematical difficulty of factoring large composite numbers into their prime factors.

Key Generation Process:

Step 1: Select two large prime numbers (p and q)
- Example: p = 61, q = 53 (real RSA uses 300+ digit primes)

Step 2: Compute n = p × q
- n = 61 × 53 = 3233
- n is the modulus, determines key strength

Step 3: Compute φ(n) = (p - 1) × (q - 1)
- φ(3233) = 60 × 52 = 3120
- φ (Euler's totient function)

Step 4: Choose public exponent e
- Typically e = 65537 (0x10001 in hex)
- Must be coprime with φ(n): gcd(e, φ(n)) = 1

Step 5: Compute private exponent d
- d ≡ e^(-1) mod φ(n)
- d × e ≡ 1 mod φ(n)
- Example: d = 2753

Result:
Public Key: (n, e) = (3233, 65537)
Private Key: (n, d) = (3233, 2753)

Encryption Process:

Message: m (must be < n)
Ciphertext: c = m^e mod n

Example: Encrypt m = 123
c = 123^65537 mod 3233 = 855

Decryption Process:

Ciphertext: c
Plaintext: m = c^d mod n

Example: Decrypt c = 855
m = 855^2753 mod 3233 = 123 ✓

Why It Works:

The RSA algorithm relies on Euler’s theorem and the Chinese Remainder Theorem:

  • Encryption then decryption: (m^e)^d ≡ m^(ed) ≡ m mod n
  • Since ed ≡ 1 mod φ(n), we have m^(ed) = m^(1 + kφ(n)) = m × (m^φ(n))^k ≡ m mod n

Security Foundation:

Knowing the public key (n, e), an attacker needs to compute the private exponent d. This requires:

  1. Factoring n into p and q (hard problem)
  2. Computing φ(n) = (p-1)(q-1)
  3. Finding d = e^(-1) mod φ(n)

For 2048-bit keys (617-digit numbers), factoring is computationally infeasible with current technology.

RSA Security Strength

Key Size vs. Security:

Key SizeDecimal DigitsSecurity BitsComparable AESEstimated Security Lifespan
1024-bit308 digits~80 bitsAES-80❌ Deprecated (broken 2010)
2048-bit617 digits~112 bitsAES-112✅ Secure through 2030
3072-bit925 digits~128 bitsAES-128✅ Secure through 2040
4096-bit1234 digits~152 bitsAES-192✅ Secure beyond 2050

Attack Complexity:

The best known factoring algorithm is the General Number Field Sieve (GNFS):

  • Time complexity: exp((64/9)^(1/3) × (log n)^(1/3) × (log log n)^(2/3))
  • 2048-bit RSA: ~2^112 operations (infeasible)
  • 3072-bit RSA: ~2^128 operations (impossible with current computing)

Known Attacks and Mitigations:

  1. Factorization Attacks:

    • Direct factoring of n into p and q
    • Mitigation: Use sufficiently large key sizes (2048+ bits)
  2. Timing Attacks:

    • Analyze decryption time to deduce private key
    • Mitigation: Constant-time algorithms, blinding
  3. Padding Oracle Attacks:

    • Exploit vulnerabilities in padding schemes
    • Mitigation: Use OAEP padding, not PKCS#1 v1.5
  4. Weak Prime Selection:

    • Using primes with special properties
    • Mitigation: Proper random prime generation
  5. Low Private Exponent Attack:

    • If d is small, can be found efficiently
    • Mitigation: Ensure d is sufficiently large (automatic in proper implementations)

RSA vs. Other Asymmetric Algorithms

Elliptic Curve Cryptography (ECC):

  • Smaller keys for equivalent security (256-bit ECC ≈ 3072-bit RSA)
  • Faster operations
  • Less mature, fewer security audits
  • Patent concerns (historically)

DSA (Digital Signature Algorithm):

  • Designed specifically for signatures (not encryption)
  • Smaller signatures than RSA
  • Less flexible than RSA

ElGamal:

  • Based on discrete logarithm problem
  • Ciphertext doubles plaintext size
  • Less widely adopted than RSA

Post-Quantum Algorithms (NIST standardization 2024):

  • Lattice-based (CRYSTALS-Kyber, CRYSTALS-Dilithium)
  • Hash-based (SPHINCS+)
  • Resistant to quantum computer attacks
  • Larger key sizes and signatures
  • Future replacement for RSA

Implementation Workflows

Secure RSA Key Generation

Generating secure RSA keys requires careful attention to prime selection, randomness, and parameter validation.

Step 1: Prime Number Generation

class RSAKeyGenerator {
  constructor(keySize = 2048) {
    this.keySize = keySize;
    this.publicExponent = 65537; // Standard value
  }
  
  // Generate cryptographically secure prime
  async generatePrime(bitLength) {
    while (true) {
      // Generate random odd number
      const candidate = this.generateRandomOdd(bitLength);
      
      // Test primality
      if (await this.isProbablyPrime(candidate)) {
        return candidate;
      }
    }
  }
  
  generateRandomOdd(bitLength) {
    const bytes = new Uint8Array(bitLength / 8);
    crypto.getRandomValues(bytes);
    
    // Ensure highest bit is set (full bit length)
    bytes[0] |= 0x80;
    
    // Ensure number is odd
    bytes[bytes.length - 1] |= 0x01;
    
    return this.bytesToBigInt(bytes);
  }
  
  // Miller-Rabin primality test
  async isProbablyPrime(n, rounds = 40) {
    // Small prime checks
    const smallPrimes = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37];
    for (const prime of smallPrimes) {
      if (n % BigInt(prime) === 0n) {
        return n === BigInt(prime);
      }
    }
    
    // Miller-Rabin test
    return this.millerRabinTest(n, rounds);
  }
  
  millerRabinTest(n, rounds) {
    // Write n-1 as 2^r × d
    let d = n - 1n;
    let r = 0;
    while (d % 2n === 0n) {
      d /= 2n;
      r++;
    }
    
    // Perform rounds of testing
    for (let i = 0; i < rounds; i++) {
      const a = this.randomRange(2n, n - 2n);
      let x = this.modPow(a, d, n);
      
      if (x === 1n || x === n - 1n) continue;
      
      let continueOuter = false;
      for (let j = 0; j < r - 1; j++) {
        x = this.modPow(x, 2n, n);
        if (x === n - 1n) {
          continueOuter = true;
          break;
        }
      }
      
      if (!continueOuter) return false;
    }
    
    return true;
  }
  
  // Modular exponentiation: base^exp mod mod
  modPow(base, exp, mod) {
    let result = 1n;
    base = base % mod;
    
    while (exp > 0n) {
      if (exp % 2n === 1n) {
        result = (result * base) % mod;
      }
      exp = exp / 2n;
      base = (base * base) % mod;
    }
    
    return result;
  }
}

Step 2: RSA Parameter Calculation

class RSAKeyGenerator {
  async generateKeyPair() {
    console.log('Generating RSA key pair...');
    
    // Step 1: Generate two distinct primes
    const p = await this.generatePrime(this.keySize / 2);
    const q = await this.generatePrime(this.keySize / 2);
    
    // Ensure p ≠ q
    if (p === q) {
      return this.generateKeyPair(); // Regenerate
    }
    
    // Step 2: Compute n = p × q
    const n = p * q;
    
    // Step 3: Compute φ(n) = (p-1) × (q-1)
    const phi = (p - 1n) * (q - 1n);
    
    // Step 4: Choose e (public exponent)
    const e = BigInt(this.publicExponent);
    
    // Verify gcd(e, φ(n)) = 1
    if (this.gcd(e, phi) !== 1n) {
      throw new Error('Invalid public exponent');
    }
    
    // Step 5: Compute d = e^(-1) mod φ(n)
    const d = this.modInverse(e, phi);
    
    // Step 6: Compute CRT parameters for optimization
    const dp = d % (p - 1n); // d mod (p-1)
    const dq = d % (q - 1n); // d mod (q-1)
    const qInv = this.modInverse(q, p); // q^(-1) mod p
    
    return {
      publicKey: {
        n: n,
        e: e,
        keySize: this.keySize
      },
      privateKey: {
        n: n,
        e: e,
        d: d,
        p: p,
        q: q,
        dp: dp,
        dq: dq,
        qInv: qInv,
        keySize: this.keySize
      }
    };
  }
  
  // Extended Euclidean algorithm for modular inverse
  modInverse(a, m) {
    const [gcd, x, y] = this.extendedGCD(a, m);
    
    if (gcd !== 1n) {
      throw new Error('Modular inverse does not exist');
    }
    
    return (x % m + m) % m;
  }
  
  extendedGCD(a, b) {
    if (b === 0n) {
      return [a, 1n, 0n];
    }
    
    const [gcd, x1, y1] = this.extendedGCD(b, a % b);
    const x = y1;
    const y = x1 - (a / b) * y1;
    
    return [gcd, x, y];
  }
  
  gcd(a, b) {
    while (b !== 0n) {
      [a, b] = [b, a % b];
    }
    return a;
  }
}

Step 3: Key Export and Serialization

class RSAKeyExporter {
  // Export to PEM format
  exportPrivateKeyPEM(privateKey, format = 'PKCS1') {
    const der = this.privateKeyToDER(privateKey, format);
    const base64 = this.arrayBufferToBase64(der);
    
    const header = format === 'PKCS1' 
      ? '-----BEGIN RSA PRIVATE KEY-----'
      : '-----BEGIN PRIVATE KEY-----';
    const footer = format === 'PKCS1'
      ? '-----END RSA PRIVATE KEY-----'
      : '-----END PRIVATE KEY-----';
    
    return `${header}\n${this.formatBase64(base64)}\n${footer}`;
  }
  
  exportPublicKeyPEM(publicKey) {
    const der = this.publicKeyToDER(publicKey);
    const base64 = this.arrayBufferToBase64(der);
    
    return `-----BEGIN PUBLIC KEY-----\n${this.formatBase64(base64)}\n-----END PUBLIC KEY-----`;
  }
  
  // Export to SSH format
  exportSSHPublicKey(publicKey) {
    const parts = [
      this.encodeString('ssh-rsa'),
      this.encodeBigInt(publicKey.e),
      this.encodeBigInt(publicKey.n)
    ];
    
    const buffer = this.concatenateBuffers(parts);
    const base64 = this.arrayBufferToBase64(buffer);
    
    return `ssh-rsa ${base64} generated-key`;
  }
  
  // Export to JWK format
  exportJWK(publicKey, privateKey = null) {
    const jwk = {
      kty: 'RSA',
      n: this.base64UrlEncode(this.bigIntToBytes(publicKey.n)),
      e: this.base64UrlEncode(this.bigIntToBytes(publicKey.e))
    };
    
    if (privateKey) {
      jwk.d = this.base64UrlEncode(this.bigIntToBytes(privateKey.d));
      jwk.p = this.base64UrlEncode(this.bigIntToBytes(privateKey.p));
      jwk.q = this.base64UrlEncode(this.bigIntToBytes(privateKey.q));
      jwk.dp = this.base64UrlEncode(this.bigIntToBytes(privateKey.dp));
      jwk.dq = this.base64UrlEncode(this.bigIntToBytes(privateKey.dq));
      jwk.qi = this.base64UrlEncode(this.bigIntToBytes(privateKey.qInv));
    }
    
    return jwk;
  }
  
  formatBase64(base64, lineLength = 64) {
    const lines = [];
    for (let i = 0; i < base64.length; i += lineLength) {
      lines.push(base64.substring(i, i + lineLength));
    }
    return lines.join('\n');
  }
}

Encryption and Decryption Implementation

class RSACrypto {
  // Encrypt with public key using OAEP padding
  async encrypt(message, publicKey) {
    // Convert message to bytes
    const messageBytes = new TextEncoder().encode(message);
    
    // Apply OAEP padding
    const paddedMessage = await this.applyOAEPPadding(
      messageBytes,
      publicKey.keySize / 8
    );
    
    // Convert to BigInt
    const m = this.bytesToBigInt(paddedMessage);
    
    // Encrypt: c = m^e mod n
    const c = this.modPow(m, publicKey.e, publicKey.n);
    
    // Convert to bytes
    return this.bigIntToBytes(c, publicKey.keySize / 8);
  }
  
  // Decrypt with private key
  async decrypt(ciphertext, privateKey) {
    // Convert ciphertext to BigInt
    const c = this.bytesToBigInt(ciphertext);
    
    // Decrypt using CRT for optimization
    const m = this.decryptCRT(c, privateKey);
    
    // Convert to bytes
    const paddedMessage = this.bigIntToBytes(m, privateKey.keySize / 8);
    
    // Remove OAEP padding
    const message = await this.removeOAEPPadding(
      paddedMessage,
      privateKey.keySize / 8
    );
    
    // Convert to string
    return new TextDecoder().decode(message);
  }
  
  // Optimized decryption using Chinese Remainder Theorem
  decryptCRT(c, privateKey) {
    const { p, q, dp, dq, qInv } = privateKey;
    
    // m1 = c^dp mod p
    const m1 = this.modPow(c, dp, p);
    
    // m2 = c^dq mod q
    const m2 = this.modPow(c, dq, q);
    
    // h = qInv × (m1 - m2) mod p
    let h = (m1 - m2) % p;
    if (h < 0n) h += p;
    h = (qInv * h) % p;
    
    // m = m2 + h × q
    const m = m2 + h * q;
    
    return m;
  }
  
  // OAEP padding (Optimal Asymmetric Encryption Padding)
  async applyOAEPPadding(message, targetLength) {
    const hashLength = 32; // SHA-256
    const maxMessageLength = targetLength - 2 * hashLength - 2;
    
    if (message.length > maxMessageLength) {
      throw new Error(`Message too long (max ${maxMessageLength} bytes)`);
    }
    
    // Generate random seed
    const seed = crypto.getRandomValues(new Uint8Array(hashLength));
    
    // Create data block
    const dataBlock = new Uint8Array(targetLength - hashLength - 1);
    const labelHash = await crypto.subtle.digest('SHA-256', new Uint8Array(0));
    dataBlock.set(new Uint8Array(labelHash), 0);
    dataBlock[hashLength + maxMessageLength - message.length] = 0x01;
    dataBlock.set(message, hashLength + maxMessageLength - message.length + 1);
    
    // MGF1 masking
    const dbMask = await this.mgf1(seed, dataBlock.length);
    const maskedDB = this.xor(dataBlock, dbMask);
    
    const seedMask = await this.mgf1(maskedDB, hashLength);
    const maskedSeed = this.xor(seed, seedMask);
    
    // Construct padded message
    const padded = new Uint8Array(targetLength);
    padded[0] = 0x00;
    padded.set(maskedSeed, 1);
    padded.set(maskedDB, hashLength + 1);
    
    return padded;
  }
  
  async removeOAEPPadding(paddedMessage, expectedLength) {
    const hashLength = 32; // SHA-256
    
    if (paddedMessage[0] !== 0x00) {
      throw new Error('Decryption error: Invalid padding');
    }
    
    const maskedSeed = paddedMessage.slice(1, hashLength + 1);
    const maskedDB = paddedMessage.slice(hashLength + 1);
    
    // Reverse MGF1 masking
    const seedMask = await this.mgf1(maskedDB, hashLength);
    const seed = this.xor(maskedSeed, seedMask);
    
    const dbMask = await this.mgf1(seed, maskedDB.length);
    const db = this.xor(maskedDB, dbMask);
    
    // Find 0x01 separator
    let separatorIndex = -1;
    for (let i = hashLength; i < db.length; i++) {
      if (db[i] === 0x01) {
        separatorIndex = i;
        break;
      } else if (db[i] !== 0x00) {
        throw new Error('Decryption error: Invalid padding format');
      }
    }
    
    if (separatorIndex === -1) {
      throw new Error('Decryption error: Separator not found');
    }
    
    return db.slice(separatorIndex + 1);
  }
  
  // MGF1 (Mask Generation Function)
  async mgf1(seed, length) {
    const mask = new Uint8Array(length);
    const hashLength = 32;
    let counter = 0;
    let offset = 0;
    
    while (offset < length) {
      const counterBytes = new Uint8Array(4);
      new DataView(counterBytes.buffer).setUint32(0, counter, false);
      
      const input = new Uint8Array(seed.length + 4);
      input.set(seed);
      input.set(counterBytes, seed.length);
      
      const hash = await crypto.subtle.digest('SHA-256', input);
      const hashArray = new Uint8Array(hash);
      
      const copyLength = Math.min(hashLength, length - offset);
      mask.set(hashArray.slice(0, copyLength), offset);
      
      offset += copyLength;
      counter++;
    }
    
    return mask;
  }
  
  xor(a, b) {
    const result = new Uint8Array(a.length);
    for (let i = 0; i < a.length; i++) {
      result[i] = a[i] ^ b[i];
    }
    return result;
  }
}

Digital Signature Implementation

class RSASignature {
  // Sign message with private key
  async sign(message, privateKey) {
    // Hash message
    const messageBytes = new TextEncoder().encode(message);
    const hashBuffer = await crypto.subtle.digest('SHA-256', messageBytes);
    const hash = new Uint8Array(hashBuffer);
    
    // Apply PKCS#1 v1.5 signature padding
    const padded = this.applySignaturePadding(hash, privateKey.keySize / 8);
    
    // Convert to BigInt
    const m = this.bytesToBigInt(padded);
    
    // Sign: s = m^d mod n
    const s = this.modPow(m, privateKey.d, privateKey.n);
    
    // Convert to bytes and encode
    const signature = this.bigIntToBytes(s, privateKey.keySize / 8);
    return this.arrayBufferToBase64(signature);
  }
  
  // Verify signature with public key
  async verify(message, signature, publicKey) {
    try {
      // Decode signature
      const signatureBytes = this.base64ToArrayBuffer(signature);
      const s = this.bytesToBigInt(signatureBytes);
      
      // Verify: m = s^e mod n
      const m = this.modPow(s, publicKey.e, publicKey.n);
      const decrypted = this.bigIntToBytes(m, publicKey.keySize / 8);
      
      // Extract hash from padding
      const extractedHash = this.removeSignaturePadding(
        decrypted,
        publicKey.keySize / 8
      );
      
      // Hash original message
      const messageBytes = new TextEncoder().encode(message);
      const hashBuffer = await crypto.subtle.digest('SHA-256', messageBytes);
      const expectedHash = new Uint8Array(hashBuffer);
      
      // Compare hashes
      return this.constantTimeCompare(extractedHash, expectedHash);
    } catch (error) {
      return false;
    }
  }
  
  applySignaturePadding(hash, targetLength) {
    // PKCS#1 v1.5 signature padding
    // 0x00 || 0x01 || PS || 0x00 || DigestInfo || Hash
    
    const digestInfo = new Uint8Array([
      0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
      0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0x04, 0x20
    ]);
    
    const paddingLength = targetLength - hash.length - digestInfo.length - 3;
    
    if (paddingLength < 8) {
      throw new Error('Key size too small for signature');
    }
    
    const padded = new Uint8Array(targetLength);
    padded[0] = 0x00;
    padded[1] = 0x01;
    padded.fill(0xff, 2, 2 + paddingLength);
    padded[2 + paddingLength] = 0x00;
    padded.set(digestInfo, 3 + paddingLength);
    padded.set(hash, 3 + paddingLength + digestInfo.length);
    
    return padded;
  }
  
  removeSignaturePadding(paddedData, expectedLength) {
    if (paddedData[0] !== 0x00 || paddedData[1] !== 0x01) {
      throw new Error('Invalid signature padding');
    }
    
    let separatorIndex = 2;
    while (separatorIndex < paddedData.length && paddedData[separatorIndex] === 0xff) {
      separatorIndex++;
    }
    
    if (paddedData[separatorIndex] !== 0x00) {
      throw new Error('Invalid signature padding format');
    }
    
    // Skip DigestInfo and return hash
    const digestInfoLength = 19; // SHA-256 DigestInfo
    return paddedData.slice(separatorIndex + 1 + digestInfoLength);
  }
  
  constantTimeCompare(a, b) {
    if (a.length !== b.length) {
      return false;
    }
    
    let diff = 0;
    for (let i = 0; i < a.length; i++) {
      diff |= a[i] ^ b[i];
    }
    
    return diff === 0;
  }
}

Comparison with Symmetric Encryption

When to Use RSA vs. AES

RSA (Asymmetric) - Best For:

  • Digital signatures and authentication
  • Key exchange in hybrid encryption
  • Identity verification
  • Certificate-based systems
  • Decentralized trust models

AES (Symmetric) - Best For:

  • Bulk data encryption
  • File and database encryption
  • Fast real-time encryption
  • Large data volumes
  • Known, trusted parties

Hybrid Encryption (Recommended):

class HybridEncryption {
  async encrypt(data, recipientRSAPublicKey) {
    // 1. Generate random AES key
    const aesKey = await crypto.subtle.generateKey(
      { name: 'AES-GCM', length: 256 },
      true,
      ['encrypt', 'decrypt']
    );
    
    // 2. Encrypt data with AES (fast)
    const iv = crypto.getRandomValues(new Uint8Array(12));
    const encryptedData = await crypto.subtle.encrypt(
      { name: 'AES-GCM', iv: iv },
      aesKey,
      new TextEncoder().encode(data)
    );
    
    // 3. Export AES key
    const exportedKey = await crypto.subtle.exportKey('raw', aesKey);
    
    // 4. Encrypt AES key with RSA (small, secure)
    const encryptedKey = await this.rsaEncrypt(
      new Uint8Array(exportedKey),
      recipientRSAPublicKey
    );
    
    return {
      encryptedData: this.arrayBufferToBase64(encryptedData),
      encryptedKey: this.arrayBufferToBase64(encryptedKey),
      iv: this.arrayBufferToBase64(iv)
    };
  }
  
  async decrypt(encryptedPackage, recipientRSAPrivateKey) {
    // 1. Decrypt AES key with RSA
    const encryptedKey = this.base64ToArrayBuffer(encryptedPackage.encryptedKey);
    const aesKeyBytes = await this.rsaDecrypt(encryptedKey, recipientRSAPrivateKey);
    
    // 2. Import AES key
    const aesKey = await crypto.subtle.importKey(
      'raw',
      aesKeyBytes,
      { name: 'AES-GCM' },
      false,
      ['decrypt']
    );
    
    // 3. Decrypt data with AES
    const encryptedData = this.base64ToArrayBuffer(encryptedPackage.encryptedData);
    const iv = this.base64ToArrayBuffer(encryptedPackage.iv);
    
    const decryptedData = await crypto.subtle.decrypt(
      { name: 'AES-GCM', iv: iv },
      aesKey,
      encryptedData
    );
    
    return new TextDecoder().decode(decryptedData);
  }
}

Use our AES Encryptor/Decryptor for the symmetric encryption component.

Performance Comparison

OperationRSA-2048AES-256Speedup
Key Generation~100ms<1ms100x
Encryption (1KB)~5ms<0.1ms50x
Decryption (1KB)~15ms<0.1ms150x
Signature~15msN/A-
Verification~2msN/A-

Conclusion: Use RSA for key exchange and signatures, AES for data encryption.

Best Practices and Security Guidelines

Key Generation Best Practices

Prime Selection:

// ✅ Good: Use strong random primes
const p = await generateStrongPrime(keySize / 2);
const q = await generateStrongPrime(keySize / 2);

// Ensure sufficient distance
if (Math.abs(p - q) < 2^(keySize/2 - 100)) {
  // Regenerate, primes too close
}

// ✅ Check that p and q are not special-form primes
// (e.g., Mersenne primes, Sophie Germain primes without sufficient randomness)

Public Exponent Selection:

// ✅ Recommended: e = 65537 (F4)
const e = 65537;

// ⚠️ Acceptable: e = 3 (faster but less margin of security)
// const e = 3;

// ❌ Never: e = 1 (completely insecure)

Private Key Protection:

// ✅ Encrypt private key for storage
async function encryptPrivateKey(privateKey, passphrase) {
  const salt = crypto.getRandomValues(new Uint8Array(16));
  
  // Derive encryption key from passphrase
  const key = await deriveKeyFromPassword(passphrase, salt, 100000);
  
  // Encrypt private key
  const iv = crypto.getRandomValues(new Uint8Array(12));
  const encrypted = await crypto.subtle.encrypt(
    { name: 'AES-GCM', iv: iv },
    key,
    privateKey
  );
  
  return { encrypted, salt, iv };
}

Key Management

Key Rotation Schedule:

class KeyRotationManager {
  constructor(rotationPolicy) {
    this.rotationPolicy = rotationPolicy; // e.g., { maxAge: 365 * 24 * 60 * 60 * 1000 }
  }
  
  shouldRotate(keyMetadata) {
    const keyAge = Date.now() - keyMetadata.createdAt;
    
    // Rotate based on age
    if (keyAge > this.rotationPolicy.maxAge) {
      return true;
    }
    
    // Rotate based on usage count
    if (keyMetadata.usageCount > this.rotationPolicy.maxUsage) {
      return true;
    }
    
    // Rotate if compromise suspected
    if (keyMetadata.compromiseSuspected) {
      return true;
    }
    
    return false;
  }
  
  async rotateKey(oldKeyId, purpose) {
    // Generate new key pair
    const newKeyPair = await generateRSAKeyPair(2048);
    
    // Transition period: accept both keys
    await this.enableDualKeyAcceptance(oldKeyId, newKeyPair.id);
    
    // Update all systems with new public key
    await this.distributeNewPublicKey(newKeyPair.publicKey);
    
    // Wait for propagation
    await this.wait(this.rotationPolicy.transitionPeriod);
    
    // Revoke old key
    await this.revokeKey(oldKeyId);
    
    // Securely delete old private key
    await this.secureDelete(oldKeyId);
    
    return newKeyPair;
  }
}

Key Storage Options:

Security Hierarchy (Best to Worst):

1. ✅✅✅ Hardware Security Module (HSM)
   - FIPS 140-2 Level 3/4
   - Tamper-evident/resistant
   - Physical key protection
   - Cost: $$$$$

2. ✅✅ Trusted Platform Module (TPM)
   - Hardware-backed key storage
   - Available on modern laptops
   - OS integration
   - Cost: $ (built-in)

3. ✅ Key Management Service (KMS)
   - AWS KMS, Azure Key Vault, Google Cloud KMS
   - Centralized management
   - Audit logging
   - Cost: $$

4. ✅ Password-Protected Files
   - Encrypted with strong passphrase
   - Stored on encrypted disk
   - Regular backups
   - Cost: Free

5. ⚠️ Environment Variables
   - Better than hardcoding
   - Still in memory
   - Limited access control
   - Cost: Free

6. ❌ Hardcoded in Source Code
   - Exposed in version control
   - No access control
   - High compromise risk
   - Never do this!

Security Audit Checklist

Before deploying RSA in production:

  • Key size ≥ 2048 bits (preferably 3072 or 4096)
  • Using standard public exponent (65537)
  • Cryptographically secure random number generation
  • OAEP padding for encryption (not PKCS#1 v1.5)
  • PSS padding for signatures (or PKCS#1 v1.5 with caution)
  • Private keys encrypted at rest
  • Key rotation policy implemented
  • Key usage logged and monitored
  • Backup and recovery procedures tested
  • Constant-time implementations (avoid timing attacks)
  • Input validation on all cryptographic operations
  • Error handling doesn’t leak sensitive information
  • Certificate validation for PKI systems
  • Regular security audits and penetration testing

Case Study: Building a Secure Document Signing System

Challenge

A legal technology company needed to implement a digital signature system for legally binding contracts, requiring:

  • Legal compliance: eIDAS (EU), ESIGN Act (US) requirements
  • Non-repudiation: Signers cannot deny signing documents
  • Integrity verification: Detect any document tampering
  • Long-term validity: Signatures remain valid for decades
  • Audit trail: Complete signing history with timestamps
  • Performance: Handle 10,000+ signatures per day

Solution Architecture

Document Signing Workflow:

1. User Registration
   ├── Generate 4096-bit RSA key pair
   ├── Store private key encrypted with user password
   ├── Register public key with Certificate Authority
   └── Issue identity certificate

2. Document Preparation
   ├── User uploads document (PDF, DOCX, etc.)
   ├── System generates cryptographic hash (SHA-256)
   ├── Create signature metadata (signer, timestamp, purpose)
   └── Prepare signature request

3. Signature Generation
   ├── User authenticates (MFA)
   ├── Decrypt private key with password
   ├── Sign document hash with RSA-PSS
   ├── Include timestamp from trusted TSA
   └── Create signature bundle

4. Signature Verification
   ├── Extract document and signature
   ├── Retrieve signer's public key certificate
   ├── Verify certificate chain to root CA
   ├── Verify document hash matches signature
   ├── Check timestamp authenticity
   └── Generate verification report

Implementation:

class DocumentSigningSystem {
  async signDocument(document, signerPrivateKey, signerCertificate) {
    // 1. Hash document
    const documentHash = await crypto.subtle.digest(
      'SHA-256',
      document
    );
    
    // 2. Get trusted timestamp
    const timestamp = await this.getTrustedTimestamp(documentHash);
    
    // 3. Create signature metadata
    const metadata = {
      signer: signerCertificate.subject,
      timestamp: timestamp.time,
      algorithm: 'RSA-PSS-SHA256',
      documentHash: this.arrayBufferToBase64(documentHash)
    };
    
    // 4. Create data to sign (metadata + hash)
    const dataToSign = new TextEncoder().encode(
      JSON.stringify(metadata) + this.arrayBufferToBase64(documentHash)
    );
    
    // 5. Generate RSA-PSS signature
    const signature = await this.rsaSign(dataToSign, signerPrivateKey);
    
    // 6. Create signature bundle
    return {
      document: this.arrayBufferToBase64(document),
      signature: this.arrayBufferToBase64(signature),
      metadata: metadata,
      timestamp: timestamp,
      certificate: signerCertificate
    };
  }
  
  async verifySignature(signedDocument) {
    const {
      document,
      signature,
      metadata,
      timestamp,
      certificate
    } = signedDocument;
    
    // 1. Verify certificate chain
    const certValid = await this.verifyCertificateChain(certificate);
    if (!certValid) {
      return { valid: false, reason: 'Invalid certificate' };
    }
    
    // 2. Check certificate not revoked
    const revoked = await this.checkCertificateRevocation(certificate);
    if (revoked) {
      return { valid: false, reason: 'Certificate revoked' };
    }
    
    // 3. Verify timestamp
    const timestampValid = await this.verifyTimestamp(timestamp);
    if (!timestampValid) {
      return { valid: false, reason: 'Invalid timestamp' };
    }
    
    // 4. Recompute document hash
    const documentBytes = this.base64ToArrayBuffer(document);
    const computedHash = await crypto.subtle.digest('SHA-256', documentBytes);
    
    // 5. Verify hash matches metadata
    if (this.arrayBufferToBase64(computedHash) !== metadata.documentHash) {
      return { valid: false, reason: 'Document modified' };
    }
    
    // 6. Reconstruct signed data
    const dataToVerify = new TextEncoder().encode(
      JSON.stringify(metadata) + metadata.documentHash
    );
    
    // 7. Verify RSA signature
    const signatureBytes = this.base64ToArrayBuffer(signature);
    const signatureValid = await this.rsaVerify(
      dataToVerify,
      signatureBytes,
      certificate.publicKey
    );
    
    if (!signatureValid) {
      return { valid: false, reason: 'Invalid signature' };
    }
    
    return {
      valid: true,
      signer: metadata.signer,
      signedAt: metadata.timestamp,
      algorithm: metadata.algorithm,
      certificate: certificate
    };
  }
}

Results

After 18 months of operation:

  • ✅ 250,000+ documents signed with zero repudiation incidents
  • ✅ 100% legal compliance across EU and US jurisdictions
  • ✅ <500ms average signing time (excellent UX)
  • ✅ <100ms average verification time
  • ✅ Zero false positives in signature verification
  • ✅ Complete audit trail for regulatory compliance
  • ✅ Long-term archive with 30-year signature validity

Key Lessons

  1. Use 4096-bit keys for long-term signatures (30+ year validity)
  2. Include trusted timestamps to prove signing time
  3. Implement certificate revocation checking (CRL or OCSP)
  4. Hash-then-sign approach separates document from signature
  5. Comprehensive logging essential for legal compliance
  6. User experience matters - make signing seamless but secure

Call to Action

Implement RSA Security Today

For Developers:

  • Generate production-grade RSA keys with our RSA Key Generator
  • Implement JWT authentication with RSA signatures
  • Add SSH public key authentication to your servers
  • Replace shared secret authentication with PKI
  • Implement code signing for software distribution

For Security Teams:

  • Audit existing RSA implementations
  • Establish key rotation policies
  • Deploy centralized key management systems
  • Implement certificate lifecycle management
  • Conduct regular cryptographic assessments

For Organizations:

  • Transition to passwordless authentication
  • Implement digital signature workflows
  • Deploy Public Key Infrastructure (PKI)
  • Enable encrypted communications
  • Ensure regulatory compliance (eIDAS, ESIGN Act)

Enhance Your Cryptography Toolkit

Explore complementary tools:

Continue Learning

Advanced Topics:

  • Post-Quantum Cryptography (PQC)
  • Zero-Knowledge Proofs
  • Threshold Cryptography
  • Homomorphic Encryption
  • Blockchain and Cryptocurrency

Stay Updated:

  • Follow NIST cryptographic standards
  • Monitor quantum computing advances
  • Subscribe to security advisories
  • Participate in cryptography conferences

External References

  1. PKCS #1 v2.2: RSA Cryptography Standard. RSA Laboratories. https://www.rfc-editor.org/rfc/rfc8017

  2. NIST FIPS 186-5: Digital Signature Standard (DSS). National Institute of Standards and Technology. https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-5.pdf

  3. “A Method for Obtaining Digital Signatures and Public-Key Cryptosystems” by Rivest, Shamir, and Adleman. Communications of the ACM, 1978. The original RSA paper.

  4. OWASP Cryptographic Storage Cheat Sheet: Best practices for cryptographic key management. https://cheatsheetseries.owasp.org/cheatsheets/Cryptographic_Storage_Cheat_Sheet.html


Last updated: November 3, 2025 | Article ID: rsa-key-generator-guide | Gray-wolf Tools Security Division