Overview
HexDroid supports optional end-to-end encryption (E2EE) of message content, configured per channel or per private conversation. This is separate from, and layered on top of, your TLS connection to the server.
TLS (transport)
Encrypts the link between your phone and the IRC server. The server, and any bouncer in between sees your messages in the clear. Protects against network eavesdroppers only.
E2EE (content)
Encrypts the message text itself before it leaves your device. The server, the bouncer, and other channel members without the key see only ciphertext. Only holders of the shared key can read it.
+AGM and +OK use a pre-shared key that you and your contact agree on ahead of time and share out of band (in person, over Signal, etc.) you control exactly who holds it. +AGE instead gives each identity its own keypair and performs an automated key exchange, with trust-on-first-use pinning and out-of-band fingerprint verification.Three schemes
Pick per target in the encryption dialog. The padlock glyph on each message tells you which scheme delivered it.
| Scheme | Wire prefix | Use it for |
|---|---|---|
| AES-256-GCM ๐ padlock |
+AGM |
The modern default. HexDroid-to-HexDroid, or HexDroid-to-HexChat via the companion plugin. Authenticated encryption with a fresh random nonce per message and replay-binding to the conversation. Recommended for all new conversations. |
| Ed25519 + X25519 ๐ key exchange |
+AGE |
The asymmetric scheme, built the way a modern secure messenger is. Each identity has its own signing (Ed25519) and key-agreement (X25519) keypair, and keys are exchanged automatically, with nothing to copy by hand. Channels and encrypted multiplayer .hex games share a group key with per-sender signatures, so no member can forge as another; 1:1 private messages run an X3DH-style handshake into a double ratchet for forward secrecy and post-compromise security. Identities are pinned trust-on-first-use. |
| Blowfish ๐ fish |
+OK |
Legacy FiSH compatibility, for talking to people using HexChat's fishlim plugin or other old clients. Reads both ECB and CBC FiSH formats; sends CBC. Use only when the other side can't speak +AGM. |
+AGM whenever both sides support it.+AGE. Unlike the pre-shared schemes, +AGE gives every identity its own keypair and exchanges keys automatically. In a channel, the members present agree a shared group key with no central host (the lowest-fingerprint member mints it and seals it to the others), and every line is individually signed so no one can speak as another. In a private message, the two clients run a short handshake into a double ratchet. Identities are pinned trust-on-first-use; compare the safety number out of band to upgrade a peer to verified.+AGE on even when you are the only one using it: your messages go out as ciphertext that only +AGE members can read, and anyone else sees an unreadable AGE… line. For a private message to someone whose client hasn't answered the key exchange yet, HexDroid holds your message and sends it the instant the handshake completes, so it decrypts for them properly, rather than blocking you or leaking plaintext.+AGE wire format, canonical encoding, key derivation, sealed invites, the signed channel layer, and the 1:1 handshake + double ratchet is published as age-wire-format.md in the HexDroid repository, so any client author can interoperate.Set up AES-GCM (+AGM)
For you
- Open the channel or private conversation.
- Tap the menu (โฎ) and choose Secure Chat.
- Leave the cipher set to AES-256-GCM and tap Generate key.
- Tap Reveal key, then Share to send the key to your contact through a channel you trust. The shared text includes the safety number for verification.
For other users
- Open the same channel/conversation and the same Encryption dialog.
- Paste the key into the import field (the Paste button pulls from the clipboard) and tap Import.
- Confirm the safety number shown on both devices is identical.
+AGM <base64>.Set up automatic keys (+AGE)
+AGE has nothing to copy or paste. The first time you use it, HexDroid creates your identity keypair; after that, turning it on for a channel or private message exchanges keys with the other +AGE users automatically.
- Open the channel or private conversation.
- Tap the menu (โฎ) and choose Secure Chat.
- Set the cipher to +AGE (automatic) and turn it on.
- Start chatting. A shield badge appears on every
+AGEmessage, and a lock badge shows in the message input.
In a channel
The +AGE members present agree a shared group key between themselves, with no host to nominate. You can turn +AGE on even if you are the only one using it right now: your messages go out as ciphertext, anyone without the key sees an unreadable AGE… line, and anyone who enables +AGE later is brought into the key automatically.
In a private message
The two clients run a short handshake and then a double ratchet, so each message has its own key (forward secrecy) and a compromise later can't read earlier ones. If you send a line before the other side has finished the handshake, HexDroid holds it and delivers it the moment keys are agreed, so it decrypts for them properly. If the other person turns out not to run a +AGE client at all, your message still goes out as ciphertext they can't read, HexDroid never falls back to plaintext.
Set up Blowfish (+OK / FiSH)
Blowfish uses a shared passphrase rather than a generated key, the same string both clients type. This matches how fishlim and other FiSH clients work.
- Open the Encryption dialog and switch the cipher to Blowfish.
- Type the agreed passphrase (4โ56 characters) and tap Set.
- On the HexChat side, in the same channel, run
/setkey <passphrase>(fishlim).
Desktop client interoperability
You can chat encrypted with desktop IRC users. The same keys and safety numbers work across HexDroid, HexChat and irssi:
+AGM · HexChat
Install the hexdroid_agm.py plugin (needs the Python cryptography package). Adds /AGM-GEN, /AGM-SET, /AGM-INFO and en/decrypts +AGM transparently.
Generate on one side, /AGM-SET <target> <base64> on the other, verify the safety numbers match.
Decrypted messages re-enter HexChat as ordinary messages, so highlights, notifications and tab-activity colouring all work natively.
+AGM · irssi
Load the hexdroid_agm.pl script (needs the CryptX Perl module). Same commands, lowercased: /agm-gen, /agm-set, /agm-info.
Wire-compatible with HexDroid and the HexChat plugin, so a key shared with one works with all three.
+OK · fishlim
HexChat ships fishlim for FiSH. Set the same passphrase on both sides: /setkey in HexChat, Blowfish passphrase in HexDroid.
HexDroid reads both the ECB and CBC FiSH wire formats and sends CBC.
Safety numbers
Each key has a short safety number (also called a fingerprint) such as K4XR-T9BS, derived from the key. It appears in the encryption dialog on every device that holds the key.
Compare it with your contact over a separate, trusted channel. If both devices show the same safety number, you have the same key and no one tampered with it in transit. If they differ, do not send anything sensitive: re-share the key and investigate.
What it protects, and what it doesn't
Be realistic about the guarantees. HexDroid's E2EE is a pragmatic upgrade over plaintext IRC and FiSH.
Protects against
- Passive eavesdropping by the IRC server, bouncer, or network operators
- Other channel members who don't have the key
- Message tampering, an altered
+AGMmessage fails its integrity check and is shown as a decryption failure rather than fake content - Replay of a captured
+AGMmessage. The conversation (the channel name, or the pair of participants for a private message) is bound into every message, so a ciphertext can't be replayed into a different conversation; and each client also remembers recently seen message nonces per conversation, dropping a ciphertext replayed back into the same one - Accidental plaintext leakage. If encryption ever fails for a message you send, the client drops it rather than falling back to sending in the clear
Does not protect
- Identity (pre-shared schemes) with
+AGM/+OK, anyone holding the shared key can read and send as if they had it; the key does not prove who you are.+AGEcloses this gap, every message is individually signed, so members are authenticated to one another - Forward secrecy (pre-shared schemes) for
+AGM/+OK, if a key later leaks, past messages encrypted with it can be read.+AGEmitigates this: a fresh group key per game gives forward secrecy between games, and the 1:1 ratchet adds per-message forward secrecy and post-compromise security - Metadata who is talking to whom, when, and how often remains visible
- A compromised device decrypted text and the key live in memory on each device
Key storage & backup
- Keys are stored on-device in
EncryptedSharedPreferences, wrapped by the Android Keystore. - Keys are excluded from backups. A backup/restore or reinstall will not carry your encryption keys, so you'll re-share them after moving devices. This is deliberate: it keeps keys from ever leaving the device in a portable form.
- Deleting a network profile clears all of its encryption keys.
- Changing your device lock screen can invalidate the Keystore; if that happens, affected keys become unrecoverable and the channel quietly reverts to "no key configured" rather than misbehaving (just re-import).
Troubleshooting
| Symptom | Cause & fix |
|---|---|
You see +AGM <base64> instead of text | No key configured on your side for that target, or your key doesn't match the sender's. Open the Encryption dialog, import the correct key, and confirm safety numbers match. |
| Messages show a decryption-failure indicator | The keys differ between sender and receiver, or the message was tampered with. Re-share the key and compare safety numbers. |
| Safety numbers don't match | You don't have identical keys. Do not send anything sensitive. re-share the key over a trusted channel. |
| Works on one phone, not another | Each device needs the key imported separately; keys are per device and never travel through backups. |
Desktop user can't read your +AGM | HexChat needs the hexdroid_agm.py plugin (+ Python cryptography); irssi needs hexdroid_agm.pl (+ CryptX). Both then need the same key set via /AGM-SET and a matching safety number. |
| HexChat: Failed to load module: 'NoneType' object has no attribute 'write' | HexChat's bundled Python can't find the cryptography package; its import-time warning then fails because HexChat leaves sys.stderr unset, which masks the real cause. Install cryptography into the exact Python interpreter HexChat uses, then reload the script. |