Blog | G5 Cyber Security

Web Messaging: End-to-End Encryption

TL;DR

This guide shows you how to add end-to-end encryption to your web messaging app using asymmetric cryptography (public/private key pairs). This means only the sender and receiver can read messages. We’ll cover key generation, message encryption/decryption, and basic implementation considerations.

1. Understanding Asymmetric Encryption

Asymmetric encryption uses a pair of keys: a public key and a private key.

Think of it like a lock and key. The public key is the open lock – anyone can use it to secure something (encrypt). The private key is the only key that opens that specific lock (decrypt).

2. Choosing an Encryption Library

Don’t try to implement asymmetric encryption yourself! Use a well-vetted library.

For this guide, we’ll assume you are using the Web Crypto API as it requires no external dependencies.

3. Key Generation

Each user needs a key pair. This is usually done when they create an account or first start using the app.

async function generateKeyPair() {
  const keyPair = await window.crypto.subtle.generateKey(
    {
      name: "RSA-OAEP",
      modulusLength: 2048,
      publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
      hash: "SHA-256",
    },
    true, // extractable
    ["encrypt", "decrypt"]
  );
  return keyPair;
}

This code generates an RSA key pair with a modulus length of 2048 bits. The `extractable` flag being set to `true` allows you to export the keys (see step 6). The `hash` algorithm is used for padding.

4. Encryption

To encrypt a message, use the recipient’s public key.

async function encryptMessage(message, publicKey) {
  const enc = new TextEncoder();
  const data = enc.encode(message);
  const encrypted = await window.crypto.subtle.encrypt(
    {
      name: "RSA-OAEP",
    },
    publicKey,
    data
  );
  return Array.from(new Uint8Array(encrypted)); // Convert to array for storage/transmission
}

This code encrypts the message using the provided public key. The result is an `Uint8Array` which needs to be converted into a format suitable for storage or transmission (e.g., base64 encoding).

5. Decryption

To decrypt a message, use your private key.

async function decryptMessage(encryptedMessage, privateKey) {
  const decrypted = await window.crypto.subtle.decrypt(
    {
      name: "RSA-OAEP",
    },
    privateKey,
    encryptedMessage
  );
  return new TextDecoder().decode(decrypted); // Convert back to string
}

This code decrypts the message using your private key. The result is a `Uint8Array` which needs to be converted into a string.

6. Key Storage and Exchange

This is the hardest part! Securely storing and exchanging keys is crucial.

Example of exporting a public key:

async function exportPublicKey(publicKey) {
  const exported = await crypto.subtle.exportKey(
    "jwk", // format
    publicKey
  );
  return JSON.stringify(exported);
}

7. Implementation Considerations

Exit mobile version