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.

Pre-shared keys. Both schemes use a key that you and your contact agree on ahead of time and share out of band (in person, over Signal, etc.). HexDroid does not perform an automated key exchange, you control exactly who has the key.

Two 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 channel name. Recommended for all new conversations.
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.

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 (Signal, in person via QR-from-another-app, etc.). 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 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.

HexChat interoperability

You can chat encrypted with desktop HexChat users in both schemes:

+AGM via plugin

Install the hexdroid_agm.py plugin (requires the Python cryptography package). It adds /AGM-GEN, /AGM-SET, /AGM-INFO and decrypts/encrypts +AGM messages transparently.

Generate on one side, /AGM-SET <target> <base64> on the other, verify safety numbers match.

+OK via fishlim

HexChat ships with the fishlim plugin 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.

The plugin and the full +AGM wire-format specification are published in the HexDroid repository, so any client author can add +AGM support.

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 ciphertext into a different channel (the channel name is bound into each +AGM message)

Does not protect

  • Identity anyone holding the shared key can read and send as if they had it; the key does not prove who you are
  • Forward secrecy if a key is later compromised, past messages encrypted with it can be read
  • 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. TThe same protection used for SASL passwords.
  • 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.
HexChat user can't read your +AGMThey need the hexdroid_agm.py plugin and the cryptography Python package, plus the same key set via /AGM-SET.