# Proton's crypto is not Transparent and not OPAQUE

Proton pitches itself as a privacy-respecting alternative to Google and friends. Their [app suite](https://web.archive.org/web/20260522105556/https://protonapps.com/) (Mail, Password Manager, VPN, etc.) uses cryptography such that, in [their words](https://web.archive.org/web/20260522105556/https://proton.me/):

> 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"](https://sslmate.com/resources/certificate_authority_failures). End-to-end encrypted messengers like [Signal](https://signal.org) 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](https://web.archive.org/web/20260522105556/https://proton.me/support/address-verification) with [Key Transparency (KT)](https://web.archive.org/web/20260522105556/https://proton.me/support/key-transparency):
> 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](https://web.archive.org/web/20260522105556/https://proton.me/files/proton_keytransparency_whitepaper.pdf), based on a [master's thesis](https://ethz.ch/content/dam/ethz/special-interest/infk/inst-infsec/appliedcrypto/education/theses/masters-thesis_thore-goebel_proton-key-transparency.pdf) by [Thore Göbel](https://thore.io/) supervised by ETH Zürich's [Applied Crypto Group](https://appliedcrypto.ethz.ch/) (you may know some of their greatest hits like [zkae.io](https://zkae.io) or [breakingthe3ma.app](https://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](https://github.com/ProtonMail/kt-auditor) 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`](https://github.com/ProtonMail/WebClients/blob/main/packages/account/addresses/index.ts#L93-L104). 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`](https://github.com/ProtonMail/WebClients/blob/e090f06d782a64c83dcd87c730426681aa7a9b2b/packages/key-transparency/lib/verification/verifyProofs.ts#L205).

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:

![Because the client trusts the server to tell it its own address, the self-audit verifies the wrong row, letting Proton serve a forged key for the real address.](img/kt_attack_flowchart.svg)

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](https://datatracker.ietf.org/doc/draft-ietf-keytrans-architecture/) 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](https://web.archive.org/web/20260131175710/https://proton.me/blog/proton-pass-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](https://web.archive.org/web/20260522105556/https://proton.me/files/proton_keytransparency_whitepaper.pdf):

> *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](https://github.com/ProtonMail/protoncore_android/blob/1b87f94ebfdfaf5e67145e8668efc52dbb931e0b/key/data/src/main/kotlin/me/proton/core/key/data/repository/PublicAddressRepositoryImpl.kt#L83-L86):

> *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](https://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](https://en.wikipedia.org/wiki/Password-authenticated_key_agreement)). 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)](http://srp.stanford.edu/design.html), one of the older options on offer. Matthew Green put it [bluntly](https://blog.cryptographyengineering.com/should-you-use-srp/):

> 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`](https://github.com/ProtonMail/pmcrypto/blob/76329565070320b73009988287fda03becea3a9d/lib/bigInteger.ts#L28-L48) used vanilla `%` which is not constant-time, opening Proton up to attacks like [PARASITE](https://eprint.iacr.org/2021/553.pdf) which could leak hashed passwords to network level attackers. They also forgot to add a `%` in one of their client [checks](https://github.com/ProtonMail/WebClients/blob/f2c9feb089723aa17a8744af309dd1306ff442b9/packages/srp/lib/srp.ts#L150) which is off [spec](http://srp.stanford.edu/design.html). 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](https://github.com/ProtonMail/WebClients/commit/588d1cc2a80e1e451ace9f0d7d4e9a13a99ffcc7) their seven-year-old SRP to Rust, where in [srp/core.rs](https://github.com/ProtonMail/proton-crypto-rs/blob/a8d2795d341fb6d44e93bc66d742ef7745dab422/proton-srp/src/srp/core.rs#L118-L123) 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](https://eprint.iacr.org/2018/1121.pdf) Proton on exactly this point, and Proton's [response](https://web.archive.org/web/20260522105556/https://proton.me/blog/cryptographic-architecture-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](https://github.com/ProtonMail/WebClients/blob/0113e3d8d248562156fbdb0fb0cc624cb10fbcfb/applications/inbox-desktop/src/update/update.ts#L156) and run no signature check on the downloaded binary.

Proton's apps ship with [certificate pinning](https://github.com/ProtonMail/WebClients/blob/decf966bf4605f85b3bc7fc39c1b0ff7a489e985/applications/pass-desktop/src/tls.ts#L10-L16) 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`](https://github.com/electron/electron/blob/ccaab437cc56866ba6a60b5b1e1139b800dbccc9/lib/browser/api/auto-updater/squirrel-update-win.ts#L27-L32) with no pinning at all.

![Pinning protects the app. The updater runs in a separate Electron process with none.](img/update_attack_flowchart.svg)

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](https://github.com/mitmproxy/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.

![After the auto-update, my tampered binary got linked to the ProtonPass icon on the user's desktop. At first it looks exactly like the real app, a user would happily type their master password into it and leak it to an adversary. To prove I have full control I then show the attack flowchart inside the app and pop a calculator to clarify this has arbitrary code execution on the machine.](img/supply_chain_demo.webm)

Bitwarden had the same issue, their Windows desktop uses [electron-updater](https://github.com/electron-userland/electron-builder/blob/ed422f36540a93e9bd2a19bc7a5e729bf2b033ea/packages/electron-updater/src/NsisUpdater.ts#L113-L114), which only enforces Authenticode verification when the bundled `app-update.yml` includes a `publisherName`, which Bitwarden's didn't. They [added](https://github.com/bitwarden/clients/pull/19591) 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](https://blog.doyensec.com/2026/02/16/electron-safe-updater.html) and most options don't support proper signature verification. For GNU[/](https://www.gnu.org/gnu/incorrect-quotation.html)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](https://cvereports.com/reports/CVE-2025-15556). 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](https://snyk.io/blog/tanstack-npm-packages-compromised/) or a recent attack involving [Bitwarden CLI](https://web.archive.org/web/20260522105556/community.bitwarden.com/t/bitwarden-statement-on-checkmarx-supply-chain-incident/96127) are pushed to everyone at once, which is loud, and loud things get caught [quickly](https://community.bitwarden.com/t/bitwarden-statement-on-checkmarx-supply-chain-incident/96127/12). 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](https://learn.microsoft.com/en-us/windows/apps/dev-tools/winapp-cli/guides/electron-packaging) packages, get re-signed by Microsoft, and update through the Store. On GNU[/](https://www.gnu.org/gnu/incorrect-quotation.html)Linux, a Proton-run apt repository (like they have for [ProtonVPN](https://repo.protonvpn.com/debian/)) 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](https://documentation.ubuntu.com/server/explanation/software/third-party-repository-usage/) and it's hosted on a vendor-controlled channel. Getting into Debian proper isn't [straightforward](https://github.com/ProtonVPN/proton-vpn-gtk-app/issues/69) either, so the best option is probably becoming a [Verified publisher](https://docs.flathub.org/docs/for-app-authors/verification) on Flathub. Unverified community Flatpaks for [Mail](https://flathub.org/en/apps/me.proton.Mail), [Pass](https://flathub.org/en/apps/me.proton.Pass), and [VPN](https://flathub.org/en/apps/com.protonvpn.www) already exist; verifying them would take an afternoon. macOS is the awkward one, since Proton is currently [suing Apple](https://web.archive.org/web/20260522105556/https://proton.me/blog/apple-lawsuit) 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](https://web.archive.org/web/20260522105556/www.latacora.com/blog/2020/02/19/stop-using-encrypted-email/). For that, [use Signal](https://signal.org). Even ProtonMail says as much in their [threat model](https://web.archive.org/web/20260522105556/https://proton.me/blog/protonmail-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](https://web.archive.org/web/20260522105556/www.404media.co/proton-mail-helped-fbi-unmask-anonymous-stop-cop-city-protestor/) personal information like your payment information to authorities, as required by Swiss [law](https://lawbrary.ch/law/art/BÜPF-v2022.05-en-art-27/). 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](https://web.archive.org/web/20260522105556/https://proton.me/legal/transparency). Swiss politicians are currently considering a [stricter law](https://www.republik.ch/2026/05/07/geheimer-ueberwachungsentwurf-der-bundesrat-hat-nichts-gelernt) that Proton's CEO [said](https://www.rts.ch/info/suisse/2025/article/proton-menace-de-quitter-la-suisse-face-aux-nouvelles-regles-de-surveillance-28883036.html) would be almost identical to the one in Russia, and that Proton would leave the country if it passed. The [latest public revision](https://cdn.repub.ch/s3/republik-assets/repos/republik/article-neue-ueberwachungsverordnung-enthuellt-bundesrat-hat-nichts-gelernt/files/d05f2cda-dd4f-47da-a38c-714f776eaf5d/1\)-a\)-e-vuepf_v19-27.02.2026-aenderungen-vs-vnl.pdf) 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](https://keepassxc.org/) database does everything I need without [biweekly](https://web.archive.org/web/20260522105556/proton.me/download/PassDesktop/win32/x64/version.json) non-consensual auto-updates.

---

> License: CC BY 4.0. To view a copy of this license, visit <https://creativecommons.org/licenses/by/4.0/>
