Post-Quantum K4li-Chat
k4li chat
Postquantum encrypted chat over ntfy. No accounts, no server, no database.
Every message is protected by ML-KEM-768 key exchange and AES-256-GCM authenticated encryption.
The ntfy server only ever relays ciphertext.
Install
npm install -g k4li-chat-cli
Then run:
k4li-chat
Usage
On first launch you are asked four questions.
- Server:
https://ntfy.shorhttps://server.k4li.ch(Recomended)
- Room: the topic name, like
my-team
- Username: how others see you
- Room passphrase: a shared secret, press Enter to skip
Share the passphrase with the people in your room out of band.
Everyone who enters the same passphrase joins the same authenticated room.
Commands
| Command | What it does |
| --- | --- |
| /who | list peers with an open channel and their key id |
| /msg NAME TEXT | send a private message |
| /send FILE | send a file to everyone |
| /send @NAME FILE | send a file to one peer |
| /refresh | re announce and rebuild channels |
| /clean | clear the terminal |
| /help | show the menu |
Anything else you type is sent to the room as a message.
Security
Real postquantum cryptography
Key exchange uses ML-KEM-768, the NIST FIPS 203 standard formerly known as CRYSTALS Kyber.
This is genuine lattice based key encapsulation, not a simulation and not a classical algorithm wearing a postquantum label.
Each session generates a fresh keypair at startup. Keys are never written to disk.
Authenticated encryption
Message bodies are sealed with AES-256-GCM using a random 96 bit IV per message.
The sender peer id is bound into the authentication tag as additional authenticated data, so a ciphertext cannot be replayed under a different identity.
A separate key is derived for each direction of each conversation, so the key you use to send is never the key you use to receive.
Secret room keys
Room keys are derived with HKDF SHA-256 from your passphrase, salted per room.
Two rooms with the same passphrase produce entirely different keys, and the encryption key and the authentication key are separate values.
There is no hardcoded key anywhere in the source.
Signed presence
Every announcement carries an HMAC SHA-256 tag binding the room, peer id, public key, username, and timestamp.
A peer without the passphrase cannot forge one, and the server cannot rewrite a username or substitute a different key.
Announcements older than 60 seconds are refused, which keeps a captured announcement from being replayed later.
Impersonation detection
If a second key claims a name that is already in the room, everyone is warned immediately. Names cannot be silently taken over.
Peer verification
/who prints a short key id for every peer. Compare those over another channel to confirm you are talking to the person you think you are.
Files
Files are encrypted before they leave your machine and split into chunks small enough for ntfy to carry. Only the intended recipients can reconstruct them.
How it works
- You join a room and announce your ML-KEM public key.
- Every peer that sees your key encapsulates a shared secret to it and sends you the ciphertext.
- You decapsulate it, both sides derive the same directional keys with HKDF, and AES-256-GCM takes over from there.
- The ntfy server transports opaque ciphertext and never holds a key that can read your messages.
Development
npm install
npm start # run the cli
npm test # offline protocol and crypto checks
npm run e2e # live checks against a real ntfy server
The e2e suite creates a throwaway topic, exercises discovery, channels, messages, private messages, file transfer, and confirms that a peer with the wrong pas
sphrase is refused.
Requirements
Node.js 20.18.1 or newer.
License
MIT