End-to-End Encryption

Hybrid RSA + AES-256-GCM encryption. Messages are encrypted client-side — the server never sees plaintext.

Threat Model

Trusted: The operator (desk/CRM) and the customer. Each holds their own private key.

Not trusted: The SMCI backend, database, and network attackers. The backend never holds a private key and never sees plaintext.

What is NOT protected

  • Metadata: who talks to whom, when, and message count.
  • Operator's own storage: the operator is responsible for protecting its private key.
  • Customer's device: if compromised, the private key can be stolen.

Cryptographic Scheme

PurposeAlgorithmParameters
Key wrappingRSA-OAEP2048-bit, SHA-256
Message contentAES-256-GCM256-bit key, 96-bit IV, 128-bit tag

Each message uses a fresh AES-256-GCM key. That AES key is wrapped (encrypted) to the recipient's RSA public key. This provides forward secrecy between messages.

Self-Read

Each message's AES key is wrapped twice: once to the recipient and once to the sender's own public key. This lets each side read back its own sent messages.

Message Envelope

FieldDescription
contentAES-256-GCM ciphertext (base64).
iv96-bit AES-GCM initialization vector (base64).
authTag128-bit AES-GCM authentication tag (base64).
encryptedKeyAES key wrapped to recipient's RSA public key.
selfEncryptedKeyAES key wrapped to sender's own RSA public key.
keyIdIdentifier of the sender's public key.

Key Exchange Flow

  1. Operator generates a persistent RSA keypair and registers the public key via PUT /api/companies/encryption-key.
  2. Operator creates a conversation. The backend snapshots the company's active key.
  3. Customer opens the deeplink. The browser generates an RSA keypair. The private key stays in IndexedDB.
  4. Customer registers the public key via POST /api/conversations/{token}/key.
  5. SMCI pushes a CUSTOMER_JOINED webhook with the customer's public key.
  6. Both sides can now encrypt messages to each other.

Encryption Example

TypeScript (SDK)
import { encryptToRecipient } from '@ixblix/sdk-js';

const envelope = encryptToRecipient(
  'Hello from Acme Corp!',
  customerPublicKeySpki,
  operatorKey.keyId,
  operatorKey.publicKeySpki,
);

// envelope.content — base64 ciphertext
// envelope.iv — base64 IV
// envelope.authTag — base64 auth tag
// envelope.encryptedKey — base64 wrapped AES key (to customer)
// envelope.selfEncryptedKey — base64 wrapped AES key (to self)
// envelope.keyId — operator key id