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.

Two key models. +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.

SchemeWire prefixUse 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.
Blowfish is legacy. The 64-bit block size and passphrase-as-key design have known weaknesses, and the original FiSH key-exchange (DH-1080) has been broken for years. HexDroid includes Blowfish purely for interoperability. prefer +AGM whenever both sides support it.
About +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.
Works in mixed channels. You can turn +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.
The complete +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

  1. Open the channel or private conversation.
  2. Tap the menu (โ‹ฎ) and choose Secure Chat.
  3. Leave the cipher set to AES-256-GCM and tap Generate key.
  4. 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

  1. Open the same channel/conversation and the same Encryption dialog.
  2. Paste the key into the import field (the Paste button pulls from the clipboard) and tap Import.
  3. Confirm the safety number shown on both devices is identical.
Once both sides hold the key, a ๐Ÿ”’ appears on every encrypted message and a lock badge shows in the message input. Anyone in the channel without the key sees only +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.

  1. Open the channel or private conversation.
  2. Tap the menu (โ‹ฎ) and choose Secure Chat.
  3. Set the cipher to +AGE (automatic) and turn it on.
  4. Start chatting. A shield badge appears on every +AGE message, 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.

Compare the safety number in the Encryption dialog with your contact over a trusted channel to upgrade them from trust-on-first-use to verified. See Safety numbers below.

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.

  1. Open the Encryption dialog and switch the cipher to Blowfish.
  2. Type the agreed passphrase (4โ€“56 characters) and tap Set.
  3. On the HexChat side, in the same channel, run /setkey <passphrase> (fishlim).
Choose a strong passphrase. Blowfish uses the passphrase bytes directly as the key with no stretching, so short passphrases are easy to brute-force. Use at least 12 characters of high-entropy text. HexDroid warns you if the passphrase is shorter than 8.

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.

Both client scripts and the full wire-format specifications +AGM and +AGE are published in the HexDroid repository, so any client author can interoperate.

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.

The safety number is a verification aid, not the key itself. Sharing it publicly is harmless, it cannot be reversed into the key.

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 +AGM message fails its integrity check and is shown as a decryption failure rather than fake content
  • Replay of a captured +AGM message. 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. +AGE closes 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. +AGE mitigates 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 hygiene: rotate keys periodically and whenever you suspect a key may have leaked. Use Regenerate (AES-GCM) or set a new passphrase (Blowfish), then re-share with your contacts.

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

SymptomCause & fix
You see +AGM <base64> instead of textNo 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 indicatorThe keys differ between sender and receiver, or the message was tampered with. Re-share the key and compare safety numbers.
Safety numbers don't matchYou don't have identical keys. Do not send anything sensitive. re-share the key over a trusted channel.
Works on one phone, not anotherEach device needs the key imported separately; keys are per device and never travel through backups.
Desktop user can't read your +AGMHexChat 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.