Pitfalls of rolling your own E2EE protocol

- 5 min. - Linus Zvp

An analysis of an e2ee chat app that used a non-cryptographically secure RNG and offered no way to verify keys.

Table of Contents

#1 Intro

In a recent HN thread titled “My small revenge on Apple”, Javier Anton talked about their app “Collaborative Groups”, which the website claims is end-to-end encrypted. There is no source available, so all the below is based on a short reverse engineering session of the Android app. This is not meant as an attack on the author. Instead, this should highlight some of the pitfalls of rolling your own encryption protocol. The author has released a blog post of their own regarding these issues, which I will be referencing throughout.

The part of the protocol seen in this short session works like this: When two parties want to chat, the app generates a random encryption key (ChatKey) and encrypts that with the RSA key of the other party. This way, the server can’t get hold of the chat key and thus can’t read your messages (in theory).

#2 Identified issues

The following sections detail the identified issues.

#2.1 RSA key size soon insufficient

The RSA key generation happens on the device, which is good, and uses SecureRandom which is also good. The key size is 2048 bits, which the BSI recommends to use only until the end of 2022.


       
java
// com.groups.network.m20.i0 // compiled from ChatUtils // in function: k RSAKeyPairGenerator k2Var = new RSAKeyPairGenerator(); k2Var.mo10576c( new RSAKeyGenerationParameters( new BigInteger("10001", 16), new SecureRandom(), 2048, 80 ) );

Status: Fixed
The developer says the RSA key size has been updated to 3072 bits.

#2.2 Usage of a non-cryptographically secure RNG

The ChatKey is used to encrypt the chat messages and is generated a bit differently:

ChatKey references, among them a call to generateRand16ByteString(), used for initialisation

      
java
// com.groups.network.m20.kh // compiled from: Utils // function renamed from: V public String generateRand16ByteString() { Random random = new Random(); StringBuffer stringBuffer = new StringBuffer(); while (stringBuffer.length() < 16) { stringBuffer.append(Integer.toHexString(random.nextInt())); } return stringBuffer.toString().substring(0, 16).toUpperCase(); }

It looks like the ChatKeys are generated using java.util.Random(). The app uses a library named Codename One to provide java.util.Random(). The documentation for that says the following:

public Random()

Creates a new random number generator. Its seed is initialized to a value based on the current time: public Random() { this(System.currentTimeMillis()); }

On Android, the standard Random is not entirely based on current system time but its use for security-sensitive applications is discouraged nonetheless and SecureRandom is recommended instead (Note: it’s unknown whether the iOS version is affected by the same issues). However, it does not seem like Codename One uses that. The documentation says it’s purely time-based.

Even if it were random, though, successive outputs of a random number generator (RNG) that is not cryptographically secure typically reveal its internal state. That’s why one just doesn’t use them for cryptographic key generation. In this case, the RNG is reinitialized for every key generation run. Depending on the RNG it uses (again, non-cryptographically secure RNGs do not provide guarantees here), assuming a few starting bytes might allow you to determine what later outputs have to be, reducing the key’s strength significantly because there are only so many possible starting sequences. If the RNG’s seed wasn’t weak already.

Status: Fixed
The app’s author says Random has been replaced with SecureRandom.

#2.3 No key verification

Additionally, it does not appear possible to verify keys. A chat was started with a random stranger and no method was found to verify either the RSA key or the ChatKey. This means that you have to trust the server to give you the right RSA key; the server could just give you a different key and intercept the connection and the average user could never tell.

Status: Fixed
This was confirmed by the app’s author and has since been implemented.

#2.4 No message authentication

It seems that no Message Authentication Code (MAC) is used to prove the integrity of chat messages, which can enable e.g. padding oracle attacks, due to CBC’s partial malleability. To make it short: please use an algorithm that provides authentication (see Authenticated Encryption with Associated Data (AEAD)), like AES-GCM-SIV. And then make use of the additional associated data (AAD) to prevent replay attacks.

Javier mentioned that he noticed some e2ee error messages after my initial post. While I had nothing to do with that, my best guess would be that someone was trying out padding oracle attacks.

I noticed some E2E error messages coming from the trace which means that you (I assume it was you) have been tinkering with it/trying to break it.

Status: Fixed
The app’s symmetric encryption algorithm has since been switched to XChaCha20-Poly1305, which is also an AEAD algorithm. The AAD is also said to be used to prevent replay attacks, but I don’t know which mechanism is used.

#2.5 Mediocre forward secrecy

For forward secrecy the device key usually stays the same, while the key messages are encrypted with will change. In the Signal protocol, they change for each message.

Status: Mostly fixed
The app’s author said that ChatKeys are rotated once a month by default and a shorter duration can be manually set.

#2.6 All users’ names, surnames and emails exposed

As seen below, it’s possible to search the whole user directory for either

  • first name,
  • last name or
  • email.
Contact search results with many entries

This wasn’t rate-limited. The app’s author says the actual email search is done in the backend via a “startswith” search. Additionally, clients are required to authenticate via a token. But even so, verified email addresses could easily be harvested by e.g. instrumenting the app with frida or MITMing the connection of one’s own device (see Burp Suite or Zed Attack Proxy) to obtain the token.

Status: Mostly fixed
A rate limit of 50 email searches per day has since been implemented.

#3 Comment on risk assessment

In the blog post Javier Anton mostly focuses on “active” Machine-In-The-Middle (MITM) attacks and only mentions the PRNG issues as an aside and only after he updated the blog post. That is, in my opinion, the wrong way around.

There are two ways to pull off the active MITM attack Javier describes:

  1. MITM the connection between client and server (this is what he focuses on)
    • this might include having to get a TLS certificate from somewhere
    • can be prevented by employing certificate pinning
  2. hack into the app’s servers (not mentioned at all)
    • then there’s no need to get any TLS certificates

#3.1 “Active” MITM

Once an attacker pulled off the above attack, they could MITM the initial RSA key trust on first use (TOFU) process or re-initiate it for both parties and from then on capture, modify and possibly forge new messages.

#3.2 “Passive” MITM

But that’s not the only way if the ChatKey is predictable and rarely, if ever, changes. Once an attacker gets onto the app’s servers they can just decrypt every message (and modify and re-encrypt it) using the time-based ChatKeys. If the keys had been generated using a cryptographically secure pseudorandom number generator (CSPRNG), that would not be possible and only in that case the “active” MITM would be the only way to capture (and/or modify) the message contents.

By allowing users to verify RSA keys and by using a CSPRNG for key generation it won’t be possible to decrypt or even re-encrypt messages in the future, even by an active MITM. But only if the participants verified fingerprints out-of-band and if the app warns when RSA keys change.

Status: Not fixed
Certificate pinning won’t be implemented in the foreseeable future.

#4 Recommendations

: Implemented :️ Partially implemented : Not implemented

  • Use SecureRandom for cryptographic key generation
  • Let participants verify RSA key fingerprints out of bounds
  • Use AEAD, like AES-GCM-SIV
  • Prevent replay attacks by making use of AEAD’s AAD
  • Fix RSA key length
  • Do not reveal the names of all users or allow the enumeration of email addresses
  • Use short-lived session keys for proper forward secrecy (rotated once a month by default)
  • Implement certificate pinning

#5 TL;DR

The app’s author fixed almost all the issues within a week, which is quite exceptional.

  1. Symmetric encryption key generation was time-based
  2. A non-cryptographically secure key generator was used
  3. RSA key length was only considered secure until end of 2022 and protects everything
  4. You trusted the server because you couldn’t verify the encryption keys in person (or at least out-of-band, like via PGP or Signal)
    • This kind of defeats the point of e2ee
  5. The symmetric ChatKeys are changed once a month by default, resulting in mediocre forward secrecy
  6. Encrypted data wasn’t authenticated and thus potentially vulnerable to a padding oracle attack
  7. Replay attacks were possible
  8. The entire user directory was visible to every user

If you’re going to advertise e2ee in something other people use, then it should be sound. If it hadn’t been advertised that would’ve been fine. If it had been marked as experimental that would’ve been fine, too. But as it stood it was not OK.

I hope others can learn from both the app author’s mistakes and his exceptional response.

While some guidance was provided to Javier, the updated app has not been reviewed or tested. This should not be interpreted as an audit or the like.

#6 Updates

(2023-09) NOTE: keylength.com was updated to the new BSI recommendations sometime after this blog was written. On 2020-04-24 the BSI extended the conformance of RSA keys with >= 2000 bits until the end of 2023.