Soatok posted: " (If you aren't interested in the background information, feel free to skip to the meat of this post. If you're in a hurry, there's a summary of results at the end.) Around this time last year, I was writing Going Bark: A Furry's Gude to End-to-End"
(If you aren't interested in the background information, feel free to skip to the meat of this post. If you're in a hurry, there's a summary of results at the end.)
Around this time last year, I was writing Going Bark: A Furry's Gude to End-to-End Encryption and the accompanying TypeScript implementation of the Extended 3-Way Diffie-Hellman authenticated key exchange (Rawr X3DH). In that blog post, I had said:
The goal of [writing] this is to increase the amount of end-to-end encryption deployed on the Internet that the service operator cannot decrypt (even if compelled by court order) and make E2EE normalized. The goal is NOT to compete with highly specialized and peer-reviewed privacy technology.
I'm stating all this up-front because I want to re-emphasize that end-to-end encryption is important, and I don't want to discourage the development of E2EE. Don't let a critical post about someone else's product discourage you from encrypting your users' data.
Partly as a result of being closed source for so long, but mostly the Threema team's history of over-emphasizing the legal jurisdiction they operate from (Switzerland) in their claims about privacy and security, most of the cryptography experts I know had quietly put Threema in the "clown-shoes cryptography" bucket and moved on with their lives. After all, if your end-to-end encryption is well implemented and your engineers prioritized privacy with metadata collection, jurisdiction shouldn't matter.
What changed for me was, recently, a well-meaning Twitter user mentioned Threema in response to a discussion about Signal.
In response, I had casually glanced through their source code and pointed out a few obvious WTFs in the Twitter thread. I had planned on following up by conducting a thorough analysis of their code and reporting my findings to them privately (which is called coordinated disclosure, not "responsible disclosure").
Signal requires users to disclose personally identifiable information. Threema, on the other hand, can be used anonymously: Users don't have to provide their phone number or email address. The fact that Signal, being a US-based IT service provider, is subject to the CLOUD Act only makes this privacy deficit worse.
Threema - Messenger Comparison
Thus, in spite of their deliberate misinformation, Threema has been disqualified from any such courtesy. They will see this blog post, and its contents, once it's public and not a moment sooner.
How Are Threema's Claims FUD?
The quote paragraph is deceptive, and was apparently designed to make their prospective customers distrustful of Signal.
The CLOUD Act isn't black magic; it can only force Signal to turn over the data they actually possess. Which is, as demonstrated by a consistent paper trail of court records, almost nothing.
As usual, we couldn't provide any of that. It's impossible to turn over data that we never had access to in the first place. Signal doesn't have access to your messages; your chat list; your groups; your contacts; your stickers; your profile name or avatar; or even the GIFs you search for. As a result, our response to the subpoena will look familiar. It's the same set of "Account and Subscriber Information" that we can provide: Unix timestamps for when each account was created and the date that each account last connected to the Signal service.
That's it.
The Signal blog
Additionally, their claim that "Threema [...] can be used anonymously" is, at best, a significant stretch. At worst, they're lying by omission.
Bear in mind, over 1 million of their installed base is through the Google Play Store. This means that, in practice, almost nobody actually takes advantage of this "possibility" of anonymity.
Additionally, their own whitepaper discusses the collection of users' phone number and email addresses. Specifically, they store hashes (really HMAC with a static, publicly known key for domain separation, but they treat it as a hash) of identifiers (mobile numbers, email addresses) on their server.
When it comes to security (and especially cryptography), the defaults matter a lot. That's why well-written cryptographic tools prioritize both correctness and misuse resistance over shipping new features. The default configuration is the only configuration for all but your most savvy users, because it's the path of least resistance. Trying to take credit for the mere existence of an alternate route that few people take is dishonest, and anyone in the cryptography space should know better.
The only correct criticism of Signal contained within their "comparison" is that, as of this writing, they still require a phone number to bootstrap an account. The phone number requirement makes it difficult for people to have multiple independent, compartmentalized identities; i.e. preventing your professional identity from intersecting with your personal identity--which is especially not great for LGBTQIA+ folks that aren't out to their families (usually for valid safety reasons).
This obviously sucks, but fails to justify the other claims Threema made.
With all that out of the way, let's look at Threema's cryptography protocol and some of their software implementations.
This first discussion is about weaknesses in Threema's cryptography protocol, irrespective of the underlying implementation.
At its core, Threema uses similar cryptography to Tox. This means that they're using some version of NaCl (the library that libsodium is based on) in every implementation.
This would normally be a boring (n.b. obviously secure) choice, but the security bar for private messaging apps is very high--especially for a Signal competitor.
It isn't sufficient for a secure messenger competing with Signal to just use NaCl. You also have to build well-designed protocols atop the APIs that NaCl gives you.
No Forward Secrecy
The goal of end-to-end encryption is to protect users from the service provider. Any security property guaranteed by transport-layer cryptography (i.e. HTTPS) are therefore irrelevant, since the cryptography is terminated on the server rather than your peer's device.
The Threema team claims to provide forward secrecy, but only on the network connetion.
Forward secrecy: Threema provides forward secrecy on the network connection (not on the end-to-end layer).
This is how Threema admits a weakness in their construction. "We offer it at a different [but irrelevant] layer."
Their whitepaper acknowledges this deficit.
As I've demonstrated previously, it's not difficult to implement Signal's X3DH AKE (which offers forward secrecy) using libsodium. Most of what I've done there can be done with NaCl (basically use SHA512 instead of BLAKE2b and you're golden).
The X3DH handshake is essentially multiple Curve25519 ECDH handshakes (one long-term, one short-term e.g. biweekly, one totally ephemeral), which are mixed together using a secure key derivation function (i.e. HKDF).
To the state of the art for secure messaging that Threema claims, Forward Secrecy is table stakes. Threema's end-to-end encryption completely lacks this property (and transport-layer doesn't count). No amount of hand-waving on their part can make this not a weakness in Threema.
The specification for X3DH has been public for 5 years. My proof-of-concept TypeScript implementation that builds atop libsodium is nearly a year old. If the Threema team wanted to fix this, it would not be hard for them to do so. Building a usable messaging app is much harder than building X3DH on top of a well-studied Curve25519 implementation.
Threema IDs Aren't Scalable
Threema IDs are 8-digit alphanumeric unique identifiers, chosen randomly, that serve as a pseudonymous mapping to an asymmetric keypair.
This means there are possible Threema IDs (2.8 trillion). This is approximately , so we can say there's about a 41-bit keyspace.
That may seem like a large number (more than 100,000 the human population), but they're chosen randomly. Which means: The birthday problem rears its ugly head.
Threema will begin to experience collisions (with 50% probability) after (roughly 1.7 million) Threema IDs have been reserved.
At first, this won't be a problem: If you collide with someone else's Threema ID, you just have to generate another one. That's just an extra round-trip for the Threema server to say "LOL NO try again". In fact, given the Google Play Store estimates for the number of Threema installs, they're probably in excess of the birthday bound today.
Quick aside: One known problem with Threema IDs is that users don't know they're supposed to back them up, so when they switch phones, they lose access to their old IDs and secret keys. Aside from the obvious social engineering risk that emerges from habitually tolerating new Threema IDs for all contacts ("I lost my old Threema ID, again, so blindly trust that it's me and not a scammer"), there's a bigger problem.
Since Threema IDs are used by each app to identify peers, it's not possible for Threema to recycle expired IDs. In order to do so, Threema would need to be able to inspect the social graph to determine which Threema IDs can be freed, which would be a huge privacy violation and go against their marketing.
So what happens if someone maliciously reserve-then-discards billions of Threema IDs?
Due to the pigeonhole principle, this will eventually lead to an address space exhaustion and prevent more Threema users from being onboarded. However, trouble will begin before it gets this far: At a certain point, legitimate users' attempts to register a new Threema ID will result in an unreasonable number of contentions with reserved IDs.
Neither the Threema website nor their whitepaper discusses how Threema can cope with this sort of network strain.
This problem could have been prevented if the Threema designers were more cognizant of the birthday bound.
This is, once again, attributable to the birthday bound problem that affects Threema IDs.
Related: I also find it interesting that they only hash the Curve25519 public key and not the combination of public key and Threema ID. The latter construction would frustrate batch attacks by committing both the public key (which is random and somewhat opaque to users) and the Threema ID (which users see and interact with) to a given fingerprint:
Finding a collision against a 128-bit probability space, where the input is a public key, can be leveraged against any user.
Finding a collision against a 128-bit probability space, where the input is a public key and a given Threema ID, can only be leveraged against a targeted user (i.e. that Threema ID).
Both situations still suck because of the 128-bit truncation, but Threema managed to choose the worse of two options.
No Breadcrumb for Cryptography Migrations
There is no concept of versioning anywhere in Threema's protocols or designs, which means that one day migrating to better cryptography, without introducing the risk of downgrade attacks, simply isn't possible.
The lack of any cryptography migration breadcrumb also prevents Threema from effectively mitigating security weaknesses inherent to their protocols and designs. You'll see why this is a problem when we start looking at the implementations.
Inconsistency with Cryptographic Randomness
Threema commits the same sin as most PGP implementations in misunderstanding the difference between /dev/random and /dev/urandom on Linux.
You should ignore the man page. Don't use /dev/random. The distinction between /dev/random and /dev/urandom is a Unix design wart. The man page doesn't want to admit that, so it invents a security concern that doesn't really exist. Consider the cryptographic advice in random(4) an urban legend and get on with your life.
Emphasis mine.
If you use /dev/random instead of urandom, your program will unpredictably (or, if you're an attacker, very predictably) hang when Linux gets confused about how its own RNG works. Using /dev/random will make your programs less stable, but it won't make them any more cryptographically safe.
Emphasis not mine.
This is an easy fix (/dev/random -> /dev/urandom), but it signals that the whitepaper's author lacks awareness of cryptographic best practices.
(Optional) If the user sets a passphrase, calculate the PBKDF2-SHA1 of their passphrase (with only 10,000 iterations) and XOR the master key with this output.
If the user opts to not use a passphrase, if their phone is ever seized from a government agency, it might as well be stored as plaintext.
To be charitable, maybe that kind of attack is outside of their (unpublished) threat model.
Even if a user elects to store a passphrase, the low iteration count of PBKDF2 will allow for sophisticated adversaries to be able to launch offline attacks against the encrypted key file.
The 4-byte SHA1 verification checksum of the plaintext master key gives cracking code a crib for likely successful attempts (which, for weak passphrases, will almost certainly mean "you found the right key"). This is somehow worse than a typical textbook MAC-and-Encrypt design.
The checksum-as-crib is even more powerful if you've sent the target a photo before attempting a device seizure: Just keep trying to crack the Master Key then, after each time the checksum passes, decrypt the photo until you've successfully decrypted the known plaintext.
The verification checksum saves you from wasted decryption attempts; if the KDF output doesn't produce a SHA1 hash that begins with the verification checksum, you can keep iterating until it does.
Once you've reproduced the file you sent in the first place, you also have their Curve25519 secret key, which means you can decrypt every message they've ever sent or received (especially if the Threema server operator colludes with their government).
Also, Array.equals() isn't constant-time. Threema should know this by now thanks to their Cure53 audit finding other examples of it a year ago. It's 2021, you can use MessageDigest.isEqual() for this.
Unlike the care taken with nonces for message encryption, Threema doesn't bother trying to keep track of which IVs it has seen before, even though a CBC collision will happen much sooner than an Xsalsa20 collision. It also just uses SecureRandom despite the whitepaper claiming to avoid it due to weaknesses with that class on Android.
Additionally, there's no domain separation or protection against type confusion in the methods that build atop this feature. They're just AES-CBC-encrypted blobs that are decrypted and trusted to be the correct file format. So you can freely swap ciphertexts around and they'll just get accepted in incorrect places.
Tangent: The Pure-Java NaCl implementation they use when JNI isn't available also uses SecureRandom. If you're going to include a narrative in your Cryptography Whitepaper, maybe check that you're consistently adhering to it?
Cache-Timing Leaks with Hex-Encoding (JNaCl)
This isn't a meaningfully practical risk, but it's still disappointing to see in their pure-Java NaCl implementation. Briefly:
Because this implementation uses table lookups, whenever a secret (plaintext or key) is goes through one of the JNaCl hexadecimal functions, it will leak the contents of the secret through cache-timing.
/** * Convert a password string to a NaCl key. This is done by getting a * SHA512 hash and returning the first 32 bytes. */ private pwToKey(password: string): Uint8Array { const bytes = this.stringToBytes(password); const hash = nacl.hash(bytes); return hash.slice(0, nacl.secretbox.keyLength); }
Once again, just because you're using NaCl, doesn't mean you're using it well.
Also, this is another cache-timing leak in most JavaScript engines and the entire method that contains it could have been replaced by Uint8Array.from(password, 'utf-8').
Threema can't claim they were avoiding the UInt8Array.from() method there because of compatibility concerns (e.g. with IE11) because they use it here.
Summary of Results
In the cryptography employed by Threema, I was able to quickly identify 5 issues, of which 2 directly negatively impact the security of their product (Threema IDs Aren't Scalable can lead to address exhaustion and Denial-of-Service; Peer Fingerprints Aren't Collision-Resistant allows moderately-funded adversaries to bypass fingerprint detection for a discount).
Additionally, the total lack of forward secrecy invalidates the Threema marketing claims of being more private or secure than Signal.
In the Android app, I was able to identify 3 issues, of which 2 directly negatively impact the security of their product (Weak Encryption With Master Key (LocalCrypto) provides a very weak obfuscation or somewhat weak KDF (with a checksum) that, either way, makes leaking the key easier than it should be; File Encryption Uses Unauthenticated CBC Mode introduces all of the problems of CBC mode and unauthenticated encryption).
Finally, I only identified 1 security issue in the web client (Insecure Password-Based Key Derivation) before I saw the maintenance notice in the README on GitHub and decided it's not worth my time to dive any deeper.
I did not study the iOS app at all. Who knows what dragons there be?
There were a few other issues that I thought existed, and later realized was false. For example: At first glance, it looked like they weren't making sure received messages didn't collide with an existing nonce (n.b. only on messages being sent)--which, since the same key is used in both directions, would be catastrophic. It turns out, they do store the nonces on received messages, so a very obvious attack isn't possible.
The fact that Threema's developers built atop NaCl probably prevented them from implementing higher-severity issues in their product. Given that Threema Web finding, I can't help but ponder if they would have been better served by libsodium instead of NaCl.
Threema has been publicly audited (twice!) by vendors that they hired to perform code audits, and yet so many amateur cryptography mistakes persist in their designs and implementations years later. From their most recent audit:
Cure53's conclusion doesn't jive with my observations. I don't know if that says something about them, or something about me, or even something much more meta and existential about the nature of cryptographic work.
Recommendations for Threema Users
Basically, I don't recommend Threema.
Nothing I shared here is a game over vulnerability, but my findings certainly debunk the claims made by Threema's marketing copy.
When in doubt, just use Signal. It's free, open source, private, and secure.
The reason you hear less about Signal on blogs like this is because, when people like me reviews their code, we don't find these sorts of problems. I've tried to find problems before.
If you want a federated, desktop-first experience with your end-to-end encryption without a phone number, you've got options: Wire, Wickr, and Matrix. If you want mobile support too, Open Privacy is developing Cwtch.
Disclosure Timeline
This is all zero-day. I did not notify Threema ahead of time with these findings.
If you're going to engage in dishonest behavior, I'm going to treat you the same way I treat othercharlatans. Especially when your dishonesty will deceive users into trusting an inferior product with their most sensitive and intimate conversations.
No comments:
Post a Comment
Note: Only a member of this blog may post a comment.