Technical

Client-Side Encryption: Your Browser Is the Vault.

2026-04-02 7 min read

The Trust Problem

If the server encrypts your messages, you have to trust the server. You have to trust that it uses the algorithm it claims. That it doesn't log plaintext before encrypting. That it doesn't keep a copy of the key. That its employees can't access your data. That it won't be compelled to add a backdoor.

That's a lot of trust. And if the server is compromised—by a breach, a rogue employee, or a legal order—your messages are compromised with it. Server-side encryption means the server is the single point of failure.

Moving Encryption to the Client

sTELgano uses the Web Crypto API to perform all encryption and decryption in your browser. The server never sees plaintext. It never sees your encryption key. It never sees your phone number or PIN.

The server is a blind relay. It stores ciphertext and forwards it to the other party. It cannot read what it stores. It cannot decrypt what it relays. Even under subpoena, the data it holds is cryptographically useless.

The Web Crypto API

The Web Crypto API is built into every modern browser. It's not a library you download—it's part of the browser itself, maintained by the browser vendors and audited by security researchers worldwide.

Encryption
AES-256-GCM
Key Derivation
PBKDF2
Hashing
SHA-256
Randomness
crypto.getRandomValues

No external libraries needed. No npm packages for cryptography. No supply chain attacks through compromised dependencies. The browser provides everything.

Key Derivation

Your encryption key is derived from the steg number and room ID using PBKDF2 with 600,000 iterations. This is the OWASP 2023 recommendation for PBKDF2-HMAC-SHA256.

This happens entirely in your browser. The derivation takes approximately 2 seconds on a modern device—a minor inconvenience for you, but a massive barrier for an attacker trying to brute-force keys.

PBKDF2 Iterations 600,000

Each attempt to guess a key requires 600,000 hash operations. At scale, brute-force becomes computationally infeasible.

The derived key is a 256-bit AES key. It is never transmitted over the network. It is never stored on disk. It exists only in browser memory for the duration of the session, and is discarded when you close the tab.

Why the PIN Isn't Part of the Key

This is a design decision that often surprises people. If the PIN were part of the encryption key, each user would derive a different key—because they have different PINs. They wouldn't be able to decrypt each other's messages.

Both parties need the same encryption key to communicate. Since they share the same steg number but have different PINs, the PIN must be excluded from key derivation.

Instead, the PIN is used for access control. It's hashed into the access_hash, which proves you have the right to join the room. Think of it as the lock on the door—the encryption key is what's inside the vault.

The Encryption Flow

01

Plaintext

You type a message in the chat input. It exists only in browser memory.

02

Web Crypto API

The browser's built-in AES-256-GCM encrypt function is called with your derived key and a random 96-bit IV.

03

Ciphertext + IV

The encrypted output and the IV are base64-encoded. The plaintext is discarded from memory.

04

WebSocket Transit

The base64-encoded ciphertext and IV are sent over the WebSocket connection (already TLS-encrypted in transit).

05

Server Storage

The server stores the ciphertext and IV. It cannot decrypt them. It doesn't have the key.

06

Recipient Decryption

The recipient's browser receives the ciphertext, decrypts it with the same derived key, and displays the plaintext.

What the Server Sees

Data What the Server Has Useful?
Phone Number SHA-256 hash only
No
PIN SHA-256 hash (in access_hash)
No
Messages AES-256-GCM ciphertext
No
Encryption Key Nothing
N/A
Sender Identity SHA-256 hash only
No
IVs 96-bit random nonces
No

Even under subpoena, a full database dump reveals hashes and ciphertext. Without the client-side key—which was never transmitted and doesn't exist on the server—the data is cryptographically inert.

Zero External Dependencies

The entire cryptographic module is a single JavaScript file with no imports. No CryptoJS. No tweetnacl. No libsodium-wrappers. Just the Web Crypto API that ships with your browser.

Every external dependency is an attack vector. A compromised npm package, a malicious update, a supply chain injection—any of these could silently exfiltrate keys or plaintext. By depending on zero external cryptographic libraries, we eliminate this entire class of attack.

assets/js/crypto/anon.js

Single file. Zero imports. Complete crypto module.