
blue_ghost
protocol specification
Overview
A complete technical specification of the cryptographic layers, wire protocol, and session lifecycle. For a plain-English summary see Home; for privacy guarantees see Privacy.
Identity Layer β HardwareβBacked Keys
Each device's long-term identity is a P-256 elliptic curve keypair generated inside Android Keystore and bound to the device's Trusted Execution Environment. The private key is non-exportable β it never touches the JVM heap, and cannot be extracted even if the OS is compromised.
- Algorithm: P-256 (secp256r1) ECDSA
- Storage: Android Keystore TEE β private key never leaves hardware
- Use: Identity verification and Safety Number derivation
- Fingerprint: Exposed as a Safety Number for out-of-band peer verification
Handshake Layer β Ephemeral ECDH
When two devices connect, they perform an ephemeral ECDH (P-256) key exchange to establish a shared secret without transmitting any private material. Both devices generate fresh ephemeral keypairs for each session β no long-term key material is reused across sessions.
Device A (initiator) Device B (responder)
ββββββββββββββββββ ββββββββββββββββββββ
Generate ephemeral keypair EKa Generate ephemeral keypair EKb
Send EKa.public βββββββββββββββΆ Receive EKa.public
Compute shared = ECDH(EKb.private, EKa.public)
βββββββββββββββ Send EKb.public
Compute shared = ECDH(EKa.private, EKb.public)
Both sides derive:
[rootKey β sendChainKey β recvChainKey] = HKDF-SHA256(shared, 96 bytes)
HKDF-SHA256 expands the raw shared secret into three 32-byte keys used to initialise the Double Ratchet. No static keys participate in the handshake, so there is no pre-key infrastructure to compromise.
Messaging Layer β Double Ratchet
Once the handshake completes, every message is encrypted with a unique key derived from two interleaved ratchets:
- Symmetric-key ratchet (KDF chain) β advances on every message, producing a fresh message key from HMAC-SHA256. Old keys are wiped immediately after use.
- DH ratchet β advances whenever either party sends after receiving. Each step generates a new ephemeral keypair and re-derives the root key from a new ECDH output, providing break-in recovery.
This provides:
- Forward secrecy β past message keys are deleted; compromising the current state reveals nothing about prior messages
- Break-in recovery β after a key compromise, future messages become secure again as soon as the DH ratchet advances
- Per-message key isolation β no two messages share a key
Encryption Layer β AES-256-GCM
Each message key from the ratchet is used once for AES-256-GCM encryption:
- Key size: 256 bits, derived fresh per message
- IV: 12-byte random nonce, generated per message
- Authentication tag: 128-bit GCM tag β detects any ciphertext tampering
- AAD: Sequence number and sender DH public key are authenticated into every message, binding the sequence counter and ratchet state to the ciphertext
Replay & Injection Protection
Every message carries a monotonically increasing sequence number that is authenticated into the GCM tag via AAD. The receiver maintains a 1024-message sliding window bitmap. Any message with a sequence number outside the window, or a number already seen within it, is rejected before decryption is attempted.
- Replay attacks are detected and dropped
- Message injection or reordering is detected via authentication failure
- Out-of-order delivery within the window is handled by caching skipped message keys
Peer Verification β Safety Number
To defend against MITM attacks during the handshake, both devices can compare a Safety Number β a human-readable fingerprint derived from both identity public keys. If the numbers match when read aloud or compared in person, the session is authentic.
The Safety Number is computed as SHA-256 over the lexicographically ordered concatenation of both identity public keys, formatted as 12 groups of 5 digits β the same approach used by Signal.
Secure Teardown β Seal Ceremony
When a session ends, all cryptographic session material is actively destroyed:
- Root key, send chain key, receive chain key β zeroed and overwritten
- All cached skipped message keys β zeroed individually
- Ephemeral DH keypairs β released (JVM GC, with zero-fill best-effort)
- Message history β cleared from memory
- Ratchet counters and replay bitmap β reset
The long-term identity key in Android Keystore is never destroyed β it persists as your stable identity across sessions, but is never used for encryption.