Why Bitchat Is a Bad Idea: (My Audit Found Critical Zero-Days)
As a security researcher I’m often drawn to projects that make bold promises. The bitchat messenger caught my attention for this very reason. (TOO INSECURE TO BE TRUE, RIGHT?)…. well It advertises itself as a solution for secure private off-grid communication, highlighting its use of the well regarded Noise Protocol Framework as a cornerstone of its security. On the surface it’s an admirable goal (does sound promising) as in a world of centralized services the idea of a peer-to-peer secure messenger is compelling.
This led me to conduct a Complete White box Penetration Testing of the application. I wanted to see if the engineering reality lived up to the public-facing promises. I went through the entire codebase line by line (literally) and spent hours and long nights (not without caffeine ;) to understand how the system was built.
What I discovered was deeply concerning (at least to me as a security researcher). So the application is plagued by critical security vulnerabilities, Including Two zero-days i found that completely undermine its claims of privacy and security. The use of strong cryptography is rendered ineffective by a series of fundamental flaws in the application’s design and implementation.
This Blog is a detailed technical analysis of my Vulnerability Disclosure Report which is a extensive document with all proof of work with evidence and that i have shared with the team along all of my other Critical SWE findings and this blog is to give you all a crux of security flaws that still exists so It is intended for other security professionals, developers and anyone interested in the practical challenges of building secure systems. We will go through the most critical vulnerabilities one by one examining code the flawed logic and the real-world impact of these mistakes.
The Foundational Flaw :Identity Without Proof (Zero-Day: BC-ZDA-01)
CWE-345: Insufficient Verification of Data Authenticity, CWE-347: Improper Verification of Cryptographic Signature
The most severe and impactful flaw in bitchat is not in its encryption but in what comes before it. Secure communication rests on a bedrock of trusted identity. Before you can secure a channel to someone you must have a reliable way of knowing who they are. Bitchat’s architecture fails at this first, most crucial step. This single architectural oversight invalidates the entire security model of the application.
Deconstructing the announce Packet
At the heart of the bitchat network is the “announce” packet. This is how users broadcast their presence and their chosen nickname to other peers. The code that creates this packet is in the sendBroadcastAnnounce function within the BluetoothMeshService.swift file. Let’s look at the code itself:
Swift code:
// Source: bitchat/Services/BluetoothMeshService.swift
let announcePacket = BitchatPacket(
type: MessageType.announce.rawValue,
ttl: 3,
senderID: myPeerID,
payload: Data(vm.nickname.utf8)
)
self?.broadcastPacket(announcePacket) // The packet is sent unsigned.Let’s break down what this packet contains and more importantly what it actually lacks.
- senderID: This is an ephemeral, temporary ID for the user on the network.
- payload: This contains the user’s chosen nickname in plaintext.
What’s Missing: The packet is missing any form of cryptographic proof of identity. There is no public key, no certificate and most critically, no digital signature. (HEAR ME OUT)…..
A digital signature is the standard, accepted mechanism for proving identity in a distributed system. The process should have been for the application to generate a long-term cryptographic key pair for each user. The user’s public key would represent their identity. Any message claiming to be from them, especially a critical identity announcement must be signed by their private key. Other clients can then use the sender’s public key to verify the signature. This confirms that the message was created by the person who holds the private key and that it has not been altered in transit.
Bitchat does not do this. It sends an unauthenticated, unsigned message and expects all clients to blindly trust it. The receiving code, located in case .announce (lines 2007-2195 of the same file) blindly trusts this postcard. It takes the nickname and the sender ID and updates its internal peerNicknames mapping, overwriting any previous entry for that nickname without any validation.
The Inevitable Exploit: Man-in-the-Middle by Design
This architectural flaw makes a devastating Man-in-the-Middle (MitM) attack not just a theoretical possibility, but a practical and trivial exercise. Here is a step-by-step walkthrough of how the attack unfolds:
- A Legitimate User Joins: A user named Bob starts bitchat. His device broadcasts an announce packet across the mesh network containing his nickname, “Bob” and his temporary peerID.
- The Attacker Observes: An attacker, Mallory, is also on the network. She sees this unsigned packet and now knows Bob’s nickname.
- The Impersonation: Mallory immediately crafts and broadcasts her own announce packet. In this packet, she uses the exact same nickname, “Bob” but inserts her own peerID.
- Network-Wide Deception: Every bitchat client on the network receives Mallory’s packet. Because the application logic simply trusts the last announcement received all clients including that of another user, Alice update their internal mapping of users. They now all believe that the user “Bob” is located at Mallory’s network ID.
- The “Secure” Connection to the Attacker: Alice decides to send a private message to Bob. Her app looks up “Bob” in its user list and finds Mallory’s peerID. Alice’s app then proceeds to initiate a perfect, mathematically sound, and formally verified Noise Protocol handshake with Mallory’s device.
The end result is a complete compromise of communication. Alice has a secure, end-to-end encrypted channel but it’s a channel to the wrong person. Mallory can now read all of Alice’s messages in plaintext, modify them, block them and send her own messages back while impersonating Bob.
The application’s core promise of secure communication is broken. The advanced encryption is like having an unbreakable vault door installed in a tent. The lock kinda sounds strong but it protects nothing because the surrounding architecture is fundamentally insecure.
The Critical Implementation Flaw : Master Key Exfiltration via Insecure Keychain Storage (Zero-Day: BC-ZDA-02)
CWE-922: Insecure Storage of Sensitive Information
If the identity system is the architectural failure the second zero-day I discovered is a critical implementation flaw that is just as damaging. The application stores its most sensitive secrets the user’s master private keys with dangerously permissive settings, making them trivial to steal.
A Closer Look at iOS Keychain and a Critical Mistake
The iOS Keychain is a specialized, hardware-backed database designed for securely storing secrets like passwords and cryptographic keys. Its security is not automatic; it relies on the developer choosing the correct protection class for the data being stored. This is where bitchat makes a critical error.
In the KeychainManager.swift file, all cryptographic keys are saved with a single, fatally flawed attribute:
Swift code:
// Source: bitchat/Services/KeychainManager.swift, Line 106
var query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: key,
kSecValueData as String: data,
kSecAttrService as String: service,
// VULNERABILITY: This attribute is too permissive.
kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlocked
]The kSecAttrAccessibleWhenUnlocked protection class means that once a user unlocks their device a single time, the data associated with this keychain item can be accessed by any process, including background processes without any further authentication prompts from the user. This access persists until the device is rebooted.
This choice is catastrophic because bitchat is explicitly designed to run in the background. Its project.yml and project.pbxproj files request background execution modes for Bluetooth communication (bluetooth-central and bluetooth-peripheral). This is necessary for the app to function as a node in the mesh network.
This creates a perfect storm:
- The app needs to run in the background to work as intended.
- The app’s most valuable secrets (the master private keys) are stored using a protection class that explicitly allows any background process to access them on an unlocked device.
The Attack Vector: Remote, Scalable Key Theft
This vulnerability moves the threat model beyond simple physical device theft. It enables a remote and scalable attack. An adversary could distribute a seemingly benign application a game, a weather app, a utility that contains a malicious component.
Here is how the attack would work:
- The user installs the malicious app. The malware lies dormant.
- The user unlocks their phone to check messages or use another app.
- The malware’s background process activates. It can now programmatically query the device’s keychain.
- It would specifically search for items stored with the insecure kSecAttrAccessibleWhenUnlocked flag, potentially identifying bitchat’s keys by looking for its service name.
- Once found, the malware can read the master private key data directly from the keychain and exfiltrate it to an attacker-controlled server.
This is a direct violation of secure storage principles. A single piece of malware could be used to harvest the master private keys from thousands of bitchat users. Once an attacker has a user’s master private key, the compromise is total and permanent. They can impersonate that user forever, decrypt their messages and sign messages on their behalf.
The fix is critical IMO. The keychain accessibility level must be changed. For keys that might be needed in the background the correct and more secure option is kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly. The ThisDeviceOnly suffix provides the additional crucial protection of preventing the keys from being included in iCloud Keychain syncs or unencrypted device backups, closing off other potential data leakage vectors.
More CWE’s I Found
Beyond these two critical zero-days my analysis revealed a consistent pattern of carelessness in the implementation of other cryptographic primitives and protocols. These issues further demonstrate a lack of the rigor required to build a secure application.
BC-002: Insufficiently Random Peer ID Generation (CWE-331)
The application claims to use ephemeral peer IDs for enhanced privacy to prevent tracking. However, my review of the generateNewPeerID function in BluetoothMeshService.swift Lines 810–823 (generateNewPeerID) shows that these IDs are not as random as they should be. The code correctly uses a cryptographically secure source of randomness, SecRandomCopyBytes, to generate 8 bytes of data. However, it then overwrites the last 4 bytes with a value derived directly from the system’s current timestamp.
Swift code:
Evidence
private func generateNewPeerID() -> String {
// Generate 8 random bytes (64 bits) for strong collision resistance
var randomBytes = [UInt8](repeating: 0, count: 8)
_ = SecRandomCopyBytes(kSecRandomDefault, 8, &randomBytes)
// ...
// Add timestamp entropy to ensure uniqueness
let timestampMs = UInt64(Date().timeIntervalSince1970 * 1000)
let timestamp = UInt32(timestampMs & 0xFFFFFFFF)
randomBytes[4] = UInt8((timestamp >> 24) & 0xFF)
// ...
return randomBytes.map { String(format: "%02x", $0) }.joined()
}Description & Impact
The ephemeral peerID is now 64 bits, a significant improvement over the previous 32-bit version. However, the implementation now mixes randomness from SecRandomCopyBytes with a predictable timestamp. The lower 4 bytes of the “random” data are derived directly from the system clock. While this helps ensure uniqueness, it makes a portion of the peer ID predictable. An attacker who can estimate a target’s system clock can reduce the search space needed to brute-force a collision.
Let me explain in easy words look at the comment in the code, “Add timestamp entropy,” reveals a fundamental misunderstanding of cryptography. A predictable value like a timestamp reduces the total entropy of the value; it does not add to it. An attacker who can observe when a user connects to the network can make a reasonable guess about the timestamp, thereby significantly reducing the search space needed to predict a portion of the user’s “random” peer ID. This makes it easier to track users across sessions, directly undermining the feature’s stated privacy goal. The correct implementation is to use the full unmodified output from the cryptographically secure random source.
BC-010: Lack of Replay Protection in Handshake Coordinator (CWE-294) prone to Handshake Replay Attack
The Noise Protocol itself is robust against replay attacks after a secure session has been established. However the protocol framework leaves the protection of the initial handshake messages up to the application developer. Bitchat’s attempt to provide this protection is critically flawed.
In NoiseHandshakeCoordinator.swift, the code attempts to prevent replays by keeping a history of recently processed handshake messages. The vulnerability lies in how this history is managed:
Swift code:
// Source: bitchat/Noise/NoiseHandshakeCoordinator.swift, Lines 203-217
if processedHandshakeMessages.count >= messageHistoryLimit {
processedHandshakeMessages.removeAll() // The entire history is cleared.
}The history buffer is bounded to only 100 messages. When the limit is reached, the entire history is cleared. An attacker can easily exploit this by “flushing” the buffer: they can simply send 100 junk handshake messages to a target, and then replay a legitimate, previously captured handshake message. Because the victim’s history has been cleared, the replayed message will be accepted as new and valid. This can be used to disrupt connections and force devices to perform expensive and useless cryptographic computations, leading to a denial-of-service and significant battery drain.
BC-004 & BC-011: Deceptive Documentation and Predictable Behavior
Finally, I found two other issues that speak to a lack of overall diligence:
- Deceptive Documentation: The project’s WHITEPAPER.md and PRIVACY_POLICY.md both claim that password-protected rooms use Argon2id for key derivation. Argon2id is the modern, memory-hard function and the current industry best practice. However, the codebase still uses the much older and weaker PBKDF2-SHA256. This is more than a simple bug; it is an issue of transparency and trust. Users and auditors rely on documentation to understand the security of an application, and providing inaccurate information completely undermines the project’s credibility.
- Predictable Behavior: To prevent a race condition where two peers try to initiate a handshake simultaneously, the application uses a deterministic method: the peer with the lexicographically smaller peerID always becomes the initiator. While this solves the race condition, it introduces predictable behavior into the protocol. An observer can determine which peer will initiate the handshake simply by observing their ephemeral peerIDs. In a high-security context, any predictable behavior can potentially be used by an advanced adversary to gain an advantage.
A Project in Need of a Fundamental Redesign
My White-box Penetration testing of the bitchat codebase reveals a project with a dangerous disconnect between its stated goals and its reality. There are several other critical CWE’s that i have found and mentioned in my Vulnerability Disclosure Report with proof of work but this blog is to give you all a crux of security flaws that still exists as It promises privacy and security through advanced cryptography but fails on the most basic principles of secure system design. The vulnerabilities are not isolated mistakes but symptoms of a systemic failure at every level.
Well, based on my extensive research I believe bitchat in its current state is dangerously insecure….And I cannot recommend its use for any communication that requires even a moderate amount of privacy or integrity. The flaws are not minor bugs that can be patched; they are deep-seated architectural problems that require a fundamental redesign and rewrite from the ground up.
No doubt building secure software is difficult. It requires a security-first mindset throughout the entire development lifecycle….I am publishing this detailed analysis not to condemn but to educate and highlight. This project is a valuable, if harsh case study for the entire developer community on why getting the fundamentals right is non-negotiable.
And I'm looking forward to work with their team to conduct further tests….