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
| Purpose | Algorithm | Parameters |
|---|---|---|
| Key wrapping | RSA-OAEP | 2048-bit, SHA-256 |
| Message content | AES-256-GCM | 256-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
| Field | Description |
|---|---|
content | AES-256-GCM ciphertext (base64). |
iv | 96-bit AES-GCM initialization vector (base64). |
authTag | 128-bit AES-GCM authentication tag (base64). |
encryptedKey | AES key wrapped to recipient's RSA public key. |
selfEncryptedKey | AES key wrapped to sender's own RSA public key. |
keyId | Identifier of the sender's public key. |
Key Exchange Flow
- Operator generates a persistent RSA keypair and registers the public key via
PUT /api/companies/encryption-key. - Operator creates a conversation. The backend snapshots the company's active key.
- Customer opens the deeplink. The browser generates an RSA keypair. The private key stays in IndexedDB.
- Customer registers the public key via
POST /api/conversations/{token}/key. - SMCI pushes a
CUSTOMER_JOINEDwebhook with the customer's public key. - 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