SecurityE2E Encryption

E2E Encryption Deep Dive

HeySummon uses hybrid RSA-OAEP + AES-256-GCM encryption for all help request content.

Why Hybrid Encryption?

RSA can only encrypt small payloads (limited by key size). AES handles arbitrary-length data efficiently. The hybrid approach:

  1. Generate a random AES-256 key (32 bytes)
  2. Encrypt the message with AES-256-GCM (fast, authenticated)
  3. Encrypt the AES key with RSA-OAEP (asymmetric, key exchange)

Encryption Flow

Encrypting a Message

function encryptMessage(plaintext: string, publicKey: string): string {
  // 1. Generate random AES key (32 bytes) and IV (12 bytes)
  const aesKey = randomBytes(32);
  const iv = randomBytes(12);
 
  // 2. AES-256-GCM encrypt the plaintext
  const cipher = createCipheriv("aes-256-gcm", aesKey, iv);
  const encrypted = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
  const authTag = cipher.getAuthTag();
 
  // 3. RSA-OAEP encrypt the AES key with the recipient's public key
  const encryptedKey = publicEncrypt(
    { key: publicKey, padding: constants.RSA_PKCS1_OAEP_PADDING },
    aesKey
  );
 
  // 4. Bundle as dot-separated base64: encryptedKey.iv.authTag.ciphertext
  return [
    encryptedKey.toString("base64"),
    iv.toString("base64"),
    authTag.toString("base64"),
    encrypted.toString("base64"),
  ].join(".");
}

Decrypting a Message

function decryptMessage(ciphertext: string, privateKey: string): string {
  const [encKeyB64, ivB64, authTagB64, encDataB64] = ciphertext.split(".");
 
  // 1. RSA-OAEP decrypt the AES key
  const aesKey = privateDecrypt(
    { key: privateKey, padding: constants.RSA_PKCS1_OAEP_PADDING },
    Buffer.from(encKeyB64, "base64")
  );
 
  // 2. AES-256-GCM decrypt the plaintext
  const decipher = createDecipheriv("aes-256-gcm", aesKey, Buffer.from(ivB64, "base64"));
  decipher.setAuthTag(Buffer.from(authTagB64, "base64"));
  return Buffer.concat([
    decipher.update(Buffer.from(encDataB64, "base64")),
    decipher.final()
  ]).toString("utf8");
}

Wire Format

The encrypted blob is a dot-separated string of 4 base64 values:

<rsaEncryptedAesKey>.<iv>.<authTag>.<aesCiphertext>
PartSizeDescription
rsaEncryptedAesKey256 bytes (RSA-2048)AES key encrypted with RSA-OAEP
iv12 bytesAES-GCM initialization vector
authTag16 bytesAES-GCM authentication tag
aesCiphertextVariableEncrypted message content

Key Specifications

ParameterValue
RSA key size2048-bit
RSA paddingOAEP (PKCS#1 v2.1)
AES modeAES-256-GCM
AES key size256-bit (32 bytes)
IV size96-bit (12 bytes)
Auth tag size128-bit (16 bytes)

Two Key Pairs in Play

  1. Server key pair (per-request) — encrypts messages at rest. Server can decrypt for provider viewing.
  2. Consumer key pair — encrypts the response. Only the consumer can decrypt.