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:
- Generate a random AES-256 key (32 bytes)
- Encrypt the message with AES-256-GCM (fast, authenticated)
- 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>| Part | Size | Description |
|---|---|---|
| rsaEncryptedAesKey | 256 bytes (RSA-2048) | AES key encrypted with RSA-OAEP |
| iv | 12 bytes | AES-GCM initialization vector |
| authTag | 16 bytes | AES-GCM authentication tag |
| aesCiphertext | Variable | Encrypted message content |
Key Specifications
| Parameter | Value |
|---|---|
| RSA key size | 2048-bit |
| RSA padding | OAEP (PKCS#1 v2.1) |
| AES mode | AES-256-GCM |
| AES key size | 256-bit (32 bytes) |
| IV size | 96-bit (12 bytes) |
| Auth tag size | 128-bit (16 bytes) |
Two Key Pairs in Play
- Server key pair (per-request) — encrypts messages at rest. Server can decrypt for provider viewing.
- Consumer key pair — encrypts the response. Only the consumer can decrypt.