Proton's crypto is not Transparent and not OPAQUE
Proton pitches itself as a privacy-respecting alternative to Google and friends. Their app suite (Mail, Password Manager, VPN, etc.) uses cryptography such that, in their words:
Our end-to-end encryption and zero-access encryption mean that no one (not even Proton) has the technical means to access your data without your permission. […] At Proton, privacy isn't a promise, it's mathematically ensured.
If Proton plays by the rules, their protocols probably provide adequate protection and give them a credible basis to deny lawful access requests. But the moment that changes, or their servers are compromised (a "malicious server" threat model), there are several technical paths into user data. In this post, I show new attack paths I discovered and responsibly disclosed to Proton: a flaw in their Key Transparency implementation allows for man-in-the-middle (MITM) attacks, and their desktop auto-updater can install unsigned executables without asking for permission. Read my conclusion on why I still use Proton to host my email and VPN.
Key Transparency
When two Proton users communicate, say over email, Proton uses public-key cryptography. The hard part is making sure the sender gets the right public key of the recipient in the first place. Encrypt with the wrong one and a man-in-the-middle reads the message instead of the intended recipient. Every end-to-end encrypted protocol has to solve this. On the web, browsers and operating systems ship an extensive list of certificate authorities, that serve as a root of "trust". End-to-end encrypted messengers like Signal show you a fingerprint and ask you to compare it over a separate channel, which works but only if users actually do it (they usually don't).
That's why Proton complemented their Signal-like manual address verification with Key Transparency (KT):
Depending on your threat model, you might want to make sure you're emailing the person you intend to by verifying their public key. […] Proton Mail's new key verification feature automatically checks your contacts' public keys, so you can be sure you're emailing the people you intend.
Proton's design is described in their whitepaper, based on a master's thesis by Thore Göbel supervised by ETH Zürich's Applied Crypto Group (you may know some of their greatest hits like zkae.io or breakingthe3ma.app). The protocol on paper is probably fine, the bug I found is in how it was implemented.
Proton maintains a list of all public keys across their apps, and a trusted external auditor could confirm all users see the same list every epoch (though I'm not aware of any external entities that currently do this). On top of that, clients regularly "self-audit" their own email address, verifying Proton lists the correct public key for them. The application does this non-interactively on startup, so it's more user-friendly than manual fingerprint comparison.
$ whoami
The KT log stores a Signed Key List (SKL) for each user, indexed by email address. The self-audit is supposed to confirm that the SKL at your address really contains your public keys. The problem is that the email address used in the self-audit is whatever the server says it is. The client fetches it fresh from the (untrusted) backend every time. Remember the threat model treats Proton's servers as untrusted, so the moment users audit the wrong address, the protocol's guarantees collapse.
To Proton's credit, their client implementations are open source, so we can trace the whole thing. Say my email is pascal@proton.me. My client runs a self-audit, and to figure out which address to verify, it politely asks the server via addressThunk. Proton could return the wrong address, say malicious@proton.me, and my client takes it at face value. This address is then used to verify my public key in the KT log with verifyProofs.ts.
Now Proton just needs to arrange the log accordingly. They place my genuine SKL (with my real public keys) at the position for malicious@proton.me, and a forged SKL containing the attacker's key at the position for pascal@proton.me. My self-audit looks up malicious@proton.me, finds my real keys, and passes happily in the background. Meanwhile Julia emails me, her client looks up pascal@proton.me, gets the attacker's key, encrypts to it, and Proton decrypts, reads, and re-encrypts to my real key. The flow looks like this:
An adversary could hide this attack further by using a malicious address that looks more similar to the real one (like pаscаl@proton.me with a cyrillic 'а') to fool even users who regularly check which addresses were audited. Or even simpler, the adversary could also return an empty list of audit addresses to skip the self-audit altogether.
Proton's KT is still in beta and not enabled by default. There is now an IETF draft standardizing Key Transparency, and maybe Proton is waiting on that rather than building out their draft further. I would encourage them to actively contribute to the standard. Like the whitepaper by Proton the IETF draft also assumes that users know their own label. Unless the email is cryptographically bound to the SKL, the client just needs to stop forgetting its own E-Mail (which is admittedly not trivial as users can add and remove addresses on other devices).
// TODO: Key Transparency
What's worse than a badly implemented key transparency scheme? No key transparency! Proton's password manager, ProtonPass, lets you share a password vault with another user end-to-end encrypted, which is exactly the kind of operation KT exists to protect. You want to be sure you're sharing your passwords with the actual recipient and not a MITM. The ProtonPass Security Model said as much for a long time:
each Proton user has one or more address keys for each email address associated with their account. This address key is a public key linked to a verifiable identity and published in Proton's Key Transparency system, ensuring they can't be maliciously modified by an attacker.
And the whitepaper:
With KT, whenever a Proton client looks up a key, e.g., when sending an email or when sharing a password vault, it checks that the key it received from the key server is logged in the KT system.
In practice, this has been a TODO in the code for three years and counting. From Proton's own source:
KT verification happens silently for now (with some logs), some UI will be needed later on to communicate the state
Proton removed the claim from their official ProtonPass security model at my request. However, there was no notice to users, no changelog entry, no acknowledgement that the guarantee was never real, the sentence just silently disappeared. Quietly downgrading the security model your users signed up for is, in my opinion, not great. As of writing, KT verification in ProtonPass still isn't active, so every password vault you share today requires trust in the honesty of Proton's backend. Proton is in good company here. The same Applied Crypto Group that supervised Proton's KT thesis showed in zkae.io that Bitwarden, LastPass, Dashlane, and 1Password also do not authenticate public keys when sharing credentials.
Not OPAQUE
Proton also implemented their own version of a password-authenticated key exchange (PAKE). They need to authenticate users to the backend without ever revealing the password, as the password doubles as a protection for the user's encryption keys. Several protocols could do this. Proton went with Secure Remote Password (SRP), one of the older options on offer. Matthew Green put it bluntly:
I don't like it. It's also not obviously broken. But it's inefficient and you should use OPAQUE.
To be fair, OPAQUE came out after Proton did their first implementation of SRP, so this post's title is a bit of a cheap shot. Their SRP implementation had other issues regardless. The modExp used vanilla % which is not constant-time, opening Proton up to attacks like PARASITE which could leak hashed passwords to network level attackers. They also forgot to add a % in one of their client checks which is off spec. SRP is a well-specified, decades-old protocol. If a battle-tested spec with reference implementations is hard to get exactly right, designing a protocol from scratch is even riskier.
Proton's response on the sidechannel was:
When it comes to SRP and PARASITE, in particular, cache-timing attacks are beyond the scope of our web app's threat model.
I'm not too worried here. Only the bcrypt hash leaks, and brute-forcing it would take a long time. Also, they've since ported their seven-year-old SRP to Rust, where in srp/core.rs they now have the missing modulo operation and a bigger commitment to constant-time operations.
Unsigned Clients
All of this is irrelevant when using Proton through your web browser. A webapp cannot meaningfully deliver end-to-end encryption. Whoever ships you the app can also ship you the backdoor, and decide to do so on every single page load. Nadim Kobeissi criticized Proton on exactly this point, and Proton's response was essentially that the threat model is fine and if you don't like it just use the native apps.
The mobile apps don't support address verification and key transparency yet, these are actions that can only be done through either the web browser or the electron desktop clients. Desktop apps are supposed to be better because their code can be signed, and a signed malicious client would constitute proof of vendor misconduct. They also stay on your machine instead of being re-fetched every page load, so the user gets to decide when to apply an update. Proton's Desktop apps do neither, they download new executables without asking the user and run no signature check on the downloaded binary.
Proton's apps ship with certificate pinning which hard-codes the expected server certificate inside the app. The issue is that pinning only applies to the main app. The update flow runs in a separate Electron process via squirrel-update-win.ts with no pinning at all.
I built a proof of concept on Windows using ProtonPass, the same likely applies to other Proton desktop apps. The attacker needs to break TLS first or have access to the update server, so this isn't your coffee-shop Wi-Fi attacker. I set up a Windows VM with my own root CA installed in the OS truststore (simulating a corporate MITM box, or compromised update server), and ran mitmproxy against it. Certificate Pinning blocked me from touching the main app's traffic. If I tried to intercept everything the app crashed early and never reached the update step. Luckily the update traffic offered a different supported cipher count in the TLS handshake, so my proxy could let the pinned traffic pass untouched and intercept only the updater's requests. I served my own unsigned executable in place of the real update. Next time ProtonPass restarts my tampered binary is executed, at which point it's game over. It has arbitrary code execution on your machine and it is the password manager, so it sees your master password and with it, every credential in your vault.
Bitwarden had the same issue, their Windows desktop uses electron-updater, which only enforces Authenticode verification when the bundled app-update.yml includes a publisherName, which Bitwarden's didn't. They added it for windows after my report and in their case they at least ask the user first before updating.
What creeps me out most with Proton is the silent auto-update. Don't install code on my machine without my consent. I prefer my system integrity over the latest CSS tweak on the autofill button. Beyond that, the Electron update ecosystem is a mess and most options don't support proper signature verification. For GNU/Linux it's especially grim, with no signature support anywhere I've looked (I use Debian, btw). And when the update channel is run by the vendor themselves, it opens the door to targeted attacks like the Notepad++ compromise earlier this year, where the Lotus Blossom APT served a malicious build to only a small set of targets. With Notepad++ the user at least had to click "update". With ProtonPass, the binary just shows up.
Worm-style supply chain attacks like Shai Hulud or a recent attack involving Bitwarden CLI are pushed to everyone at once, which is loud, and loud things get caught quickly. Vendor-controlled channels like Proton uses make selective, quiet attacks possible instead.
On Windows, the cleanest option is publishing through the Microsoft Store, where apps ship as MSIX packages, get re-signed by Microsoft, and update through the Store. On GNU/Linux, a Proton-run apt repository (like they have for ProtonVPN) might be a start, but I'm not a fan of installing custom repos on my system, since your security ends up at the level of the weakest vendor in your sources list and it's hosted on a vendor-controlled channel. Getting into Debian proper isn't straightforward either, so the best option is probably becoming a Verified publisher on Flathub. Unverified community Flatpaks for Mail, Pass, and VPN already exist; verifying them would take an afternoon. macOS is the awkward one, since Proton is currently suing Apple over the App Store's commission structure, so the Mac App Store is off the table. If in-place auto-update is the only realistic path there, at least prompt the user before installing.
I still use Proton
Email isn't a secure messenger and never will be. For that, use Signal. Even ProtonMail says as much in their threat model:
If you are attempting to leak state secrets […] or going up against a powerful state adversary, email may not be the most secure medium for communications.
Apart from your IP address, Proton will also disclose personal information like your payment information to authorities, as required by Swiss law. In 2025 alone they received 9'301 legal orders for Proton Mail and complied with 8'313 of them according to their own transparency report. Swiss politicians are currently considering a stricter law that Proton's CEO said would be almost identical to the one in Russia, and that Proton would leave the country if it passed. The latest public revision requires companies to decrypt data, but only if they hold the decryption keys, and end-to-end encryption between end customers is explicitly excluded. Most of Proton's data would fall outside this scope, and while I'm not a lawyer, it sounds like they wouldn't be forced to selectively plant a backdoor in a web or Electron client even if they technically had the means to do so. Their marketing line "privacy isn't a promise, it's mathematically ensured" is still not true. It is a promise. One they can legally fulfill for now, but it breaks down if they're breached and an adversary gains access to the backend.
I reported all findings to Proton's security team and provided them with a draft of this post before publishing. They were responsive and professional throughout, and paid me a small bounty that covers my subscription for the next 2.5 years. None of this review would have been possible if their clients weren't open source, and I think they genuinely try to deliver the best privacy they can.
I will keep using Proton as my email provider. Self-hosting email is technically possible but practically pointless due to deliverability issues, so I'm happy to outsource it to Proton in exchange for my messages not being flagged as spam. Using my own domain means I can switch providers any time, and I don't expect email to be a secure messenger anyway. My inbox is mostly newsletters, login codes, and spam. None of that uses PGP, so it reaches Proton unencrypted regardless. The same goes for ProtonVPN, which isn't easy to self-host and where I trust their zero-logs claim. For everything else, I'd rather keep things on my own hardware. My local KeePassXC database does everything I need without biweekly non-consensual auto-updates.