HexDroid +AGM

Markdown boxlabs 11 Views Size: 6.69 KB Posted on: May 22, 26 @ 9:55 AM

HexDroid +AGM wire format

Version 1. AES-256-GCM end-to-end encryption for IRC messages.

This document is the authoritative reference for the +AGM scheme. Any client that wants to interoperate with HexDroid AGM-encrypted channels needs to implement this exactly.

Goal

Encrypted message content over IRC's PRIVMSG/NOTICE primitives, with: Authentication (a tampered message is rejected, not silently corrupted) Replay protection across channels (a ciphertext for #a won't decrypt in #b) Per-message nonces (no IV reuse across messages with the same key) Pre-shared key (no key exchange in v1; users share keys out of band)

This is not intended to compete with Signal or any other E2EE apps. It's a pragmatic upgrade path from FiSH/Blowfish for IRC users who want better authenticated encryption without changing the IRC protocol.

Wire format

+AGM <base64(version || nonce || ciphertext || tag)>

Where:

Field Size Description
version 1 byte Format version. 0x01 for this spec.
nonce 12 bytes Random per message, from a CSPRNG.
ciphertext N bytes AES-256-GCM of the UTF-8-encoded plaintext.
tag 16 bytes GCM authentication tag.

The base64 alphabet is the standard RFC 4648 alphabet. Padding (=) is optional, both padded and unpadded encodings must be accepted on receive. Senders MAY emit either; the reference implementations emit unpadded.

A space separates the +AGM literal from the base64 blob. Receivers MUST require exactly one space and MUST reject any other separator.

Cipher parameters

Algorithm: AES-256-GCM (AES/GCM/NoPadding in Java/Kotlin terms) Key length: 32 bytes (256 bits) Nonce length: 12 bytes (96 bits, GCM standard) Tag length: 16 bytes (128 bits)

Additional Authenticated Data (AAD)

The lowercase UTF-8 encoding of the target name (channel or nick) is the AAD:

aad = target.lower().encode('utf-8')

For channel messages, target is the channel name (e.g. #secret).

For query (private) messages, target is: The recipient's nick when encrypting (sender's view). The sender's nick when decrypting (receiver's view).

This asymmetry binds the ciphertext to the conversation rather than to either party in isolation, so a ciphertext from user1 to user2 can't be re-used by an attacker to impersonate user2 to user1 (the AAD would differ).

Key material

Keys are 32 bytes of cryptographically random data. Implementations MUST NOT derive keys from user passphrases without an explicit, salted KDF. the v1 scheme assumes high-entropy random key material from the start.

Key distribution is out of scope for this spec. The reference implementations support: Manual copy/paste (base64-encoded key) Share-sheet handoff (e.g. via Signal, SMS)

Safety numbers (fingerprints)

For out-of-band verification users compute a fingerprint:

sha256(scheme_byte || raw_key)[:5]

scheme_byte is 0x00 for AGM. The 40-bit truncation is base32-encoded with the Crockford alphabet (no 0/1/I/O) into 8 characters, with a hyphen between the first and second halves: K4XR-T9BS.

Both endpoints display this fingerprint. Users compare them out of band (over Signal, in person, etc.) to detect MITM at setup time.

Multi-line and chunking

A single AGM payload corresponds to one IRC line. Messages longer than the IRC line-length limit MUST be split before encryption, with each chunk encoded as its own +AGM line with its own nonce. Receivers reassemble at the plaintext layer if they want to; the protocol does not specify a multi-line batch mechanism.

The IRC line-length budget after base64 overhead is approximately:

plaintext_max ≈ (max_payload_bytes − 5 − 4) × 3/4 − 13
              ≈ 354 bytes (UTF-8) for the typical 400-byte PRIVMSG budget

(5 bytes for +AGM, ~4 bytes for base64 rounding, 3/4 for base64 inflation, 13 for version+nonce header.)

CTCP framing

CTCP messages (the \x01CMD args\x01 envelope) are encrypted as follows:

  • The CTCP framing bytes (\x01, the command name, the space, the trailing \x01) stay cleartext.
  • The argument string after the command is encrypted as a +AGM payload.

So /me waves arriving at the wire as PRIVMSG #foo :\x01ACTION waves\x01 becomes PRIVMSG #foo :\x01ACTION +AGM <base64>\x01.

This is so non-AGM clients still see a well-formed CTCP and can render it as "* nick" with garbled-but-readable text rather than producing a malformed CTCP error. The CTCP command name remains a fingerprint to attackers (you can see "this person sent an ACTION") but the content is hidden.

CTCP queries with no arguments (\x01VERSION\x01) pass through untouched since there is no content to protect.

Failure handling

Failure mode Receiver behaviour
Wrong key (tag mismatch) Display the raw +AGM ... line with a tamper hint
Wrong scheme version Same as above
Malformed base64 Same as above
Truncated payload Same as above
Cross-channel replay (AAD mismatch) Same as above (tag fails)
No key configured for target Pass through, render the +AGM ... line verbatim

Receivers MUST NOT decrypt under a key from a different target as a fallback (e.g. trying the #foo key on a message that arrived in #bar). That would break the cross-channel replay protection.

Threat model

AGM v1 protects:

  • Message content confidentiality against passive eavesdroppers (IRC ops, ISPs, bouncer operators if the IRC connection is TLS-terminated at the client).
  • Integrity (a tampered message is rejected, not silently mangled).
  • Cross-channel replay.

A v2 scheme (+AGE) is planned to add per-conversation X25519 key exchange and a double-ratchet for forward secrecy / post-compromise security. v1 intentionally ships first because it's a strict improvement over FiSH without protocol complexity.

Reference implementations

HexDroid: app/src/main/java/com/boxlabs/hexdroid/crypto/AesGcmCipher.kt HexChat plugin: aes-client-plugins/hexchat/hexdroid_agm.py in the HexDroid repo.

Raw Paste

Comments 0
Login to post a comment.
  • No comments yet. Be the first.
Login to post a comment. Login or Register
We use cookies. To comply with GDPR in the EU and the UK we have to show you these.

We use cookies and similar technologies to keep this website functional (including spam protection via Google reCAPTCHA or Cloudflare Turnstile), and — with your consent — to measure usage and show ads. See Privacy.