A Response to Recent Claims About Session's Security Architecture
January 16, 2025 / Kee Jefferys
By Kee Jefferys, Session Co-Founder, and Jason Rhinelander, Chief Software Architect, Session Technology Foundation.
We were recently made aware of a blog published by a security researcher which makes a number of claims about Session and supposed flaws in Session’s design and implementation. We, as well as other Session contributors, have now had time to read through the blog and investigate the claims and wanted to give a detailed response on each point raised by the author.
TL;DR: The supposed security issues raised by the researcher are in all cases incorrect or misleading, their claims are based on either a misreading of Session’s code or a misinterpretation of the underlying cryptography involved.
The article makes three key claims regarding Session’s security, and we will provide a detailed response to each of these claims in what follows. Additional gripes that do not have any practical impact on Session users are also addressed, along with other issues raised by the author.
Security Claims
Claim 1: Session Ed25519 keys are generated with insufficient entropy and provide only 64 bits of security
At a high level, we believe this claim is incorrect, but let us walk through the key generation process in Session and explain why.
In Session Ed25519 keys are generated by using a source of entropy to produce a 16 Byte (128 bit) random value and then concatenating those 16 bytes (128 bits) with another 16 bytes (128 bits) of 0’s, this concatenated value, now a total of 32 Bytes (256 bits) is then passed as a seed to libsodium to generate a Ed25519 keypair.
Libsodium implements standard Ed25519, which includes a step to hash the seed value with SHA512 before it is used to generate the keypair; this hashing step ensures any correlations in the seed value are destroyed before being used to derive the Ed25519 private key.
The author claims that this step is irrelevant, and that by knowing that the last 128 bits in the preimage of the hash were initialized to zeros, you can use a modified version of Pollard’s rho with SHA512 and clamping to find the corresponding secret key for a public key in 2^64 queries, reducing security from 128 bits to 64 bits.
The problem with the claim is that Pollard’s rho operates directly on group elements derived from scalars instead of seed values. Even if the seed values have their entropy constrained, the mapping from seed to scalar via SHA512 and clamping makes it infeasible to limit the search space in a way that avoids invalid seeds.
Functionally, this means that the modified Pollard’s rho attack the author proposes provides no reduction in the security level of the keys.
If the described attack is possible, it should be fairly easy to validate this claim by publishing a PoC showing that this attack is feasible using a smaller amount of entropy to limit computation time. Session developers have been unable to reproduce the author's claimed reduction in security.
Session’s generation of Ed25519 keys using 128 bits of entropy was explicitly identified in Quarkslab’s audit of Session, and Session developers had similar discussions with the Quarkslab team. Ultimately, they classified this finding as “low” because although the approach was non-standard, there was no practical nor theoretical method found to exploit this non standard approach.
The reason Session performs Ed25519 key generation this way, with 128-bits of entropy instead of 256-bits, isn't just for fun. It's so that Session can use 13-word mnemonic seed phrases instead of 25 word mnemonic seed phrases (called Recovery Passwords in Session). These shorter mnemonic seed phrases are easier for users to write down and save.
Batch attacks
The author also mentions the possibility of batch attacks. However, it's unclear how you could gather enough public keys to meaningfully reduce the security of Session’s keys. Firstly, the number of Session Account IDs is theoretically constrained by the number of Session users, which is likely to grow, but even the largest messaging applications (e.g. WhatsApp) only have around ~3 billion users. Complicating this, there is no simple way to gather the public keys of all users—Session Account IDs are shared out of band, and there is no central location where all Account IDs can be scraped.
Claim 2: The validation process for Ed25519 signatures is “in band”
The author has misinterpreted Session code, and has missed various validation steps which are performed on the senders message and identity in other code blocks. Taken as a whole, the message and sender validation process in Session provides no way to spoof the origin or contents of a message.
From a first principles basis, Session is designed so that any user can message you by knowing a single piece of information, your Account ID. There's no public directory for Account IDs. Instead, they have to be shared and confirmed out of band. This was an explicit design choice, because it means Session doesn’t have the same trust on first use (ToFU) issues that Signal or WhatsApp have where, a user's phone number or username needs to be first be resolved to a public key via a central server who can MiTM attack if you don't confirm safety numbers.
The purpose of this signature check in Session is simply to validate that the claimed sender address did indeed sign the message you received, and it achieves that goal: this signature is the place where the sender’s identity is proven to eliminate forgeries.
The public key being validated in this check is actually the sender’s Ed25519 public key. There is an additional check to ensure that this ‘outer’ Ed25519 pubkey matches the ‘inner’ X25519 pubkey that you derive from the ‘outer’ Ed25519 pubkey, since a Session Account ID is actually the X25519 public key derived from the senders Ed25519 key, which is also recorded in the encoded message content.
In combination, these checks ensure that the Session Account ID that messaged you cannot be spoofed or faked. This is the only property being achieved here, as message encryption is handled separately from this process.
The author has suggested that instead of the above, we should have called libsodium’s crypto_box_seal function to encrypt for the Session Account ID. They are correct, and in fact, Session does exactly that, at the step when it is encrypting. The Ed25519 pubkey the author is writing about, which is inside the encrypted payload, however, has absolutely nothing to do with encryption: it is, rather, a signature providing proof that the person who claims to have written the message is actually the creator of the message.
Claim 3: Session reuses public keys as private keys for symmetric onion requests
This claim is plainly incorrect, because the author has misinterpreted the code.
The author mentions the encrypt function and links to the wrong overload of that function. Instead the one being called is the later overload, of the same name, which takes the public key but then also uses an ephemeral locally generated private key to compute a shared secret. This shared secret, computable only by the request creator and the Service Node, is then passed into the function that the author is linking to.
The code for both encrypt functions, which are side-by-side in the code, can be viewed here
Gripes
Session mnemonic decoding isn’t done in constant-time
Although this finding is correct, it doesn’t have any practical impact. There's no way to remotely force a Session client to run this code and measure the response time, since this code is only run when the user is shown their mnemonic phrase for the first time when generating a new Session account, or when they view their seed phrase manually via the Settings menu.
Performing this operation in non-constant-time only presents a risk if the device Session is running on is compromised via a side channel attack. However, if the device is compromised, then all bets are off and it's likely that the attacker can perform far more damaging attacks by reading messages from the local database or exfiltrating encryption keys directly. Session’s threat model, along with every other end-to-end encrypted messenger's threat model, operates on the assumption that your device isn't compromised.
Session uses SecureRandom on Android in an unsafe way
As the author notes, this issue only affects ancient versions of Android— Android versions 4.4 (Kitkat) and below—and has long since been patched by the Android team. The oldest version of Android that Session has ever supported (in Session’s 1.0.0 release) was Android version 5 (Lollipop). As of the latest Session Android release (1.20.8) the minimum supported Android version is version 8 (Oreo).
Although this is not a security issue, we do agree that Session should be updated to follow the best practices on Android, which is to use new SecureRandom().
Other Issues
As well as the above issues raised by the author, the author raises a few other issues they have with Session, which we also wanted to cover.
Session’s removal of PFS
A detailed blog post on why Session removed PFS (Perfect Forward Secrecy), and what that means for users can be found here. In essence, the Signal protocol is not the best choice for every architecture and design, and it is important to understand which security properties the Signal protocol does and does not provide, and how similar properties can be provided via other routes. As the blog post outlines, the Session Protocol is far better suited to decentralised infrastructure, and various elements of Session’s design minimised the risks caused by the removal of PFS.
Ability to force Android clients to run an Argon2 KDF
This code is only run when looking up ONS usernames. It is not possible to force other Session clients to perform this Argon2 KDF at will, since ONS lookups are only ever triggered by explicit user action. This KDF is done to maintain backwards compatibility with old format ONS records; new ONS records do not use Argon2.
Thinly-veiled accusations of Session being a government-run honeypot
Not only is this not true, Session also makes every possible effort to remove risk of government interference in three key ways: developers who work on Session have no privileged access to the Session Network; nodes are run by community operators all around the world; and the protocol and apps are completely open source. If the author had reached out to Session developers before publishing their blog post, they would have been able to clarify the reasoning behind specific design decisions and how those design decisions impact security and privacy.
How cars became the worst product category for privacy
January 12, 2025 / Session
Three reasons to choose a decentralised private messaging app
December 25, 2024 / Session
How to stay safe on Session
December 18, 2024 / Session
The Privacy Risks of Digital IDs: What You Need to Know
November 26, 2024 / Session
Session User Survey: What Drives You to Join Online Communities?
October 21, 2024 / Wesley Sukh
Celebrating Global Encryption Day
October 20, 2024 / Session