SECURITY
The Enigma rotors on screen are decoration. The real work is done by modern, standard cryptography built into your browser. No system is perfectly secure; this page explains what ENIGMA does and where its limits are.
1. How it works
- Your browser generates a random 256-bit key, a 128-bit salt and a 96-bit IV with
crypto.getRandomValues(). - The message and its metadata are packed into a small JSON envelope, padded to a multiple of 256 bytes (to hide the exact length) and encrypted with AES-256-GCM through the Web Crypto API (
window.crypto.subtle). - Only the ciphertext, IV, salt, KDF parameters and two SHA-256 token hashes are sent to the server. The server generates the Dead Drop ID with PHP's
random_bytes()and stores the record as a JSON file. - The browser builds the secure link:
/m/ID#key=ENIGMA1-…. The key sits in the URL fragment, which browsers never send to the server. - The recipient's browser reads the key from the fragment, removes it from the address bar, derives an access proof, fetches the ciphertext and decrypts it locally. AES-GCM verifies an authentication tag: if the key is wrong or a single byte was changed, decryption fails and no text is shown.
linkKey = 32 random bytes (only in the link)
ikm = linkKey (no passphrase)
= linkKey ‖ PBKDF2-SHA256(pass, salt, 600 000) (with passphrase)
encKey = HKDF-SHA256(ikm, salt, "ENIGMA2026/v1/enc") → AES-256-GCM
authToken = HKDF-SHA256(ikm, salt, "ENIGMA2026/v1/auth") → server stores SHA-256(authToken)
deleteTok = 32 random bytes → server stores SHA-256(deleteTok)
2. Cryptography
| Component | Choice |
|---|---|
| Cipher | AES-256-GCM, 96-bit random IV, 128-bit tag, protocol parameters as additional authenticated data |
| Randomness | crypto.getRandomValues() in the browser, random_bytes() in PHP |
| Key derivation | HKDF-SHA256 with separate labels for the encryption key and the access token |
| Passphrase | PBKDF2-SHA256, 600 000 iterations, random 128-bit salt — never used directly as a key, never sent |
| Dead Drop ID | 28 characters Crockford Base32 = 140 bits of entropy |
| Implementation | Native Web Crypto only. No home-made algorithms. The rotor animation has no cryptographic function. |
3. URL fragment key handling
Everything after # stays in the browser. The page reads the key into a JavaScript variable, then calls history.replaceState() to remove it from the address bar. The key is never written to cookies, localStorage, sessionStorage or a server session. All pages send Referrer-Policy: no-referrer.
Split transmission lets you send the Dead Drop ID and the key through two different channels. Passphrase mode adds a second factor: the link alone is no longer enough, and because the 256-bit link key never reaches the server, a stolen server file cannot be brute-forced offline against the passphrase.
4. Access proof — why a typo never burns a message
To fetch the ciphertext, the browser must present authToken, which can only be derived from the correct key (and passphrase). The server compares its SHA-256 hash in constant time. Consequences: a person who only knows the Dead Drop ID cannot fetch or burn the transmission, and a wrong key or passphrase is rejected before a read is counted. The token is derived one-way and cannot be turned back into the encryption key.
5. Server-side storage
No database. Each transmission is one JSON file in enigma_private/data/messages/. The whole private folder — configuration, PHP core, encrypted messages, rate-limit counters and logs — lives outside the web root (public_html), so the web server has no URL that points to it. Extra .htaccess deny rules and empty index files remain as a second line of defence.
Stored per transmission: id, ciphertext, iv, salt, kdf, auth_hash, delete_hash, created, expires, max_views, views. Never stored: plaintext, key, passphrase, tokens.
6. File locking & burn after reading
New files are written to a temporary file and atomically renamed into place. Every read takes an exclusive flock(). After acquiring the lock, the server verifies the file was not deleted or replaced while it waited. For read-once transmissions, the file is deleted inside the same locked section that hands out the ciphertext, so two simultaneous requests cannot both receive it.
Limitation: decryption happens in the recipient's browser, so the server cannot know with certainty that the message was actually displayed. The access proof makes it very likely the requester holds the right key; if the connection drops at exactly that moment, a read-once message can be lost. We chose "destroy on delivery" because a "destroy on confirmation" step could be skipped by a malicious client.
Read-once links do not auto-open: the recipient presses DECRYPT & BURN. This prevents link-preview bots and e-mail scanners from burning a message nobody has seen.
7. Expiry & cleanup
Every transmission has a server-computed expiry (10 minutes to 7 days). Expired files are refused on access and deleted, by a cron job (scripts/cleanup.php) and/or automatically on 1 % of API requests, using the same locks as reads.
8. Web hardening
- Strict Content-Security-Policy: scripts and styles only from this origin, no inline JavaScript, no
eval, no framing, no third-party connections. X-Content-Type-Options: nosniff,X-Frame-Options: DENY,frame-ancestors 'none',Referrer-Policy: no-referrer, restrictivePermissions-Policy, HSTS on HTTPS.Cache-Control: no-storeon message pages and API responses.- API: POST only,
application/jsononly (blocks cross-site form CSRF), Origin / Sec-Fetch-Site checks, request size limits, strict validation of every field. - Rate limiting per keyed hash of the IP address (raw IPs are never stored); extra limits on rejected keys.
- Decrypted text is inserted with
textContent, neverinnerHTML— HTML or scripts in a message are displayed as plain text. - No endpoint lists messages; IDs are not sequential.
- Logs contain technical events and shortened IDs only — never plaintext, keys, passphrases, tokens or links.
9. Known limits
- JavaScript is delivered by the server. If the server itself were compromised, it could ship malicious code. This is a general limit of web-based encryption.
- Anyone who obtains the full secure link can read the message (unless a passphrase is used).
- The recipient can copy, photograph or forward the text. Nothing can undo that.
- Traffic metadata (IP addresses, timing, approximate size) is visible to network and hosting providers.
- Security depends on the devices, browsers and extensions of both parties.
- The passphrase strength meter is an estimate, not a guarantee.
10. Source
ENIGMA is plain PHP, HTML, CSS and vanilla JavaScript — no frameworks, no database, no external libraries (the QR generator is a small local file). All browser code is readable in assets/js/: crypto.js contains the complete cryptography.