Hayato Kimura and colleagues (NICT, Osaka University, NEC) spent 2.5 years fully breaking the end-to-end encryption of Rocket.Chat, a popular open-source Slack/Teams alternative used on-prem and as SaaS. The threat model is the one E2EE exists to defeat: a malicious server (rogue admin or compromised SaaS) that should not be able to read messages. It can. The keystone is the E2EE password, the root of the whole key hierarchy, which Rocket.Chat generated with a custom, non-cryptographic routine. It drew 9 characters from a 62-character set using JavaScript's Math.random() (a Mersenne Twister, not a CSPRNG), then, catastrophically, called toLowerCase() on the result, collapsing the alphabet from 62 to 36 and introducing heavy bias (letters that had an uppercase form now appear twice as often). That guts the password's entropy enough to recover it, and with it every RSA private key and group key. Stacked on top: AES-CBC with no integrity (message forgery via ciphertext malleability), no key rotation on password change (a compromised key lives forever), trust-on-first-use public keys (server-in-the-middle key replacement), and a downgrade that strips the E2EE flag entirely. The team's method is a model: formal analysis in ProVerif, plus source review, plus OSINT to learn why the design was so fragile. Rocket.Chat has since moved to a CSPRNG, AES-GCM, and proper rotation.
Post 12 of my Black Hat Asia 2026 series. This is fully-patched, peer-reviewed crypto research (ACSAC 2025); I keep it analysis-first, the ideas and lessons, not attack code.
1. The threat model E2EE promises to survive
E2EE means the server is a relay that cannot read your messages, even if it's evil. So the primary adversary here is a malicious server: a compromised admin or rogue employee at the SaaS, or anyone who owns an on-prem instance, who collects the encrypted RSA key backups and encrypted group keys the server stores. A secondary adversary is a network attacker (active MITM), and that's not hypothetical: the team's Shodan scan found roughly half of in-support Rocket.Chat servers exposed plaintext HTTP, so outsider attacks are viable on many real deployments, not just insider ones. E2EE is supposed to protect users in all these cases. The paper shows it protects them in none.
2. The architecture and its three "smells"
Rocket.Chat's E2EE has three components, and the researchers flagged a "smell" (a warning sign) in each before proving each one broken:
| Component | What it does | The smell |
|---|---|---|
| RSA key backups | Your RSA private key is encrypted under a key derived from an auto-generated E2EE password and stored on the server | Password generation is "automatic", but how? (custom generator, low PBKDF2 iterations) |
| Session establishment | Group session keys are shared using RSA | Legacy RSA key exchange |
| Message encryption | Messages encrypted with AES-CBC | No integrity protection (CBC without a MAC) |
The E2EE spec exists but "lacks detail", no entropy figures, no generation algorithm, so the team read the open-source code to find the truth. The gap between the documented protocol and the actual implementation is exactly where the fatal bugs lived.
3. The keystone bug: a password generator that eats its own entropy
The E2EE password is the root of the key hierarchy: password → (PBKDF2) master key → decrypts your RSA private key → decrypts the RSA-wrapped group session keys → decrypts messages. Recover the password and everything below falls. Rocket.Chat's mobile/legacy generator did this:
- Pick 9 characters from a 62-character set (a–z, A–Z, 0–9) using a custom
random(). - On mobile, that
random()wasMath.random(), a Mersenne Twister, not cryptographic. - Then, fatally, call
toLowerCase()on the whole password.
Step 3 is the killer, and it's the kind of one-line appsec mistake that a cryptographer wouldn't expect and a developer wouldn't flag. Every uppercase letter is folded to lowercase, so:
- The alphabet collapses from 62 to 36 (26 letters + 10 digits), because A–Z and a–z now map to the same 26 outputs.
- Strong bias appears: each letter can be produced two ways (it was drawn as lowercase, or as uppercase then folded), so letters occur about twice as often as digits. A biased distribution is far easier to brute-force than a uniform one, because you try the high-probability strings first.
The effective password space drops to roughly \(2^{46.5}\) with a ~32% bias toward letters, small enough to attack, and on mobile the Mersenne-Twister generator is itself predictable, compounding it. (Post-2022 web builds switched to 5 random words from a 1,633-word list, larger but still the same architectural single point of failure.)
4. What the recovered password unlocks
Because the password is the root, recovering it is game over for confidentiality:
- Decrypt the victim's RSA private key from the backup.
- Use it to decrypt every RSA-wrapped group session key.
- Use those to decrypt every AES-CBC message, past, present, and future.
But confidentiality is only half. The other flaws add integrity and persistence failures that make recovery impossible:
| Vulnerability | Consequence |
|---|---|
| AES-CBC without a MAC (malleability) | Message forgery: with a known plaintext/ciphertext pair (e.g. from a predictable message ID), an attacker forges a one-block ciphertext, no key needed for the forgery itself |
| Trust-on-first-use public keys (no authenticity check) | MITM key replacement: the server hands Alice the adversary's public key instead of Bob's; Alice wraps the group key for the adversary, the server unwraps it and re-wraps for Bob, and nobody notices |
| No key rotation on password change | A password update doesn't regenerate the RSA key pair; a password reset makes a new key pair but does not rotate the group key, so the same group key lives forever. An attacker with an old password decrypts all past and future messages |
| Downgrade (metadata not integrity-protected) | Strip the E2EE flag and inject plaintext |
| Replay (key reuse in a room) | Replay old messages |
The "no rotation" bug is the cruelest, and it was undocumented, found only in the source. It means the recovery mechanism doesn't recover you: the standard response to compromise (change your password) leaves the compromised group key in place. Before the attack the malicious server saw only encrypted blobs and metadata; after it, the server sees every plaintext message, every user's RSA private key, and can forge messages as anyone, total compromise.
5. The methodology: formal + code + OSINT
The audit approach is worth copying:
- Formal analysis (ProVerif). Model the protocol in symbolic logic (Dolev-Yao attacker) to detect MITM, offline-attack, downgrade, and replay paths systematically rather than by intuition.
- Manual source review. Compare the open-source code against the official spec and hunt undocumented behaviors, this is where the CBC malleability, the weak password generator, and the missing key rotation surfaced.
- Proof of concept. Build working exploits for every attack on live test instances, proving exploitability end-to-end rather than asserting it.
- OSINT. Read GitHub PR threads and historical commits to understand why the fragile design emerged: Rocket.Chat had planned a Signal-protocol implementation (GSoC 2017) but didn't ship it, and chose password-based encryption so "users can use any device." Understanding the why is what lets you recommend the right structural fix rather than just patching symptoms.
6. Fixes and lessons
Disclosed May 2024; fixes rolled out Oct 2024 – Dec 2025:
| Was | Now |
|---|---|
Math.random() / custom generator | CSPRNG (cryptographically secure) |
| AES-CBC (no integrity) | AES-GCM (authenticated encryption) |
| Broken/absent key rotation | Proper rotation |
One issue remains by design: public-key authenticity still uses trust-on-first-use, so a malicious-server MITM is still possible; Rocket.Chat treats this as an accepted risk. That's an honest disclosure of a residual limitation.
Lessons for developers (straight from the talk):
- Don't roll your own crypto protocol. Use the Signal Protocol or MLS (RFC 9420). Rocket.Chat literally planned to and didn't.
- Don't roll your own password generator. Audit entropy and test for exactly this kind of bug, the
toLowerCase()collapse would be caught by a distribution test. - Key rotation must cover every scenario: lost device, compromised password, compromised key, all must rotate the actual session keys, not just the wrapping keys.
- Move off password-based architectures to modern key management and device-bound keys (e.g. OPAQUE for password auth, device keys for multi-device). A single human-memorable password as the root of E2EE is a single point of failure.
Is my Rocket.Chat instance still vulnerable?
The core issues (weak RNG, AES-CBC, broken rotation) were patched between Oct 2024 and Dec 2025, update to a current version. Two caveats remain: public-key authenticity is still trust-on-first-use (accepted MITM risk), and if your server exposes plaintext HTTP (half did, per the scan) you have a separate, serious exposure regardless of the E2EE fixes.
How did one toLowerCase() matter so much?
Because the password is the root of the entire key hierarchy, so its entropy is the entire system's security margin. Folding case collapses 62 symbols to 36 and biases the distribution toward letters, dropping the effective space to about 2^46 and making it brute-forceable. Everything below the password, RSA keys, group keys, messages, then unwraps.
Why is AES-CBC without a MAC dangerous even without the key?
CBC is malleable: flipping bits in a ciphertext block predictably flips bits in the decrypted plaintext of the next block. With a known plaintext/ciphertext pair (message IDs made some predictable), an attacker can craft a ciphertext that decrypts to chosen content, forgery without ever recovering the key. AES-GCM (authenticated encryption) prevents this.
What's the single most important takeaway?
Use a vetted protocol (Signal/MLS) instead of a bespoke password-rooted design, and remember that the spec is not the implementation, audit the actual code (and its utility functions) with formal tools plus manual review. The fatal bug was invisible at the protocol level.
Takeaways
- The E2EE password is the root of the key hierarchy, and a custom generator's
toLowerCase()collapsed it from 62 to 36 symbols with heavy bias, making it recoverable (~\(2^{46.5}\)). - Recovering it yields every RSA private key and group key, full decryption of all messages, past and future.
- AES-CBC without a MAC enables message forgery; TOFU keys enable server MITM; no key rotation means a compromise is permanent, undocumented, found only in source.
- ~50% of live servers expose plaintext HTTP, so network attackers, not just malicious servers, can exploit many deployments.
- Methodology to emulate: ProVerif formal analysis + manual code review + end-to-end PoC + OSINT on why the design emerged.
- Fixes shipped (CSPRNG, AES-GCM, rotation); the durable lesson is don't roll your own, use Signal/MLS and device-bound keys.
References & further reading
- Black Hat Asia 2026 — this talk's official page — abstract, speaker bio, and slides/video when posted.
- Black Hat Asia 2026 — full Briefings schedule — every talk in the conference.
- Kimura et al. — full paper (IACR ePrint 2025/2300, ACSAC 2025) — the six vulnerabilities, formal analysis, and PoCs.
- PoC repository — working exploits for the demonstrated attacks.
- RFC 9420 — Messaging Layer Security (MLS) · Signal Protocol — the vetted alternatives recommended.
- cvam.sight — Post-Quantum Cryptography course · OpenPGP Internals series — key-management and protocol background.