Conversation Steganography: Hiding Secrets Inside Ordinary AI Chat
A Go-based proof of concept that uses arithmetic coding, local language models, and conversation state to make encrypted messages look like harmless text.
- The project’s real innovation is camouflage through generation, not a new encryption primitive.
- Arithmetic coding turns token choice into a hiding place, so the visible chat is a byproduct of the secret payload.
- The Go and Python split favors flexibility and local model support over a tightly coupled runtime.
- Its fragility is the point: editing, paraphrasing, or platform interference can destroy the hidden channel.
Why This Looks Like Normal Chat
Most encryption advertises itself. It becomes a blob of base64, a block of noise, or a file that looks immediately suspicious. Conversation Steganography flips that instinct: the message is supposed to look boring, like the kind of AI chat nobody pauses to read twice.
That is the editorial punchline. The project is not trying to make the payload unreadable. It is trying to make the payload unremarkable, which is a different and more unsettling privacy problem.
The goal is to produce text resembling ordinary model-generated prose, with a tradeoff between payload capacity and text quality. This proof of concept has not been shown to be statistically undetectable, and its output must be copied exactly.
That limitation matters. The repo is not selling magic. It is showing a channel that works when the environment is controlled, the model is deterministic enough, and the text survives untouched.
The Trick: Bits Steer Token Choice
The core move is arithmetic coding. An encrypted bitstream does not get pasted into the output after generation. Instead, it nudges the model’s next-token choice so that each token corresponds to a slice of probability mass.
That is why the repo leans on local models and deterministic settings. If the sender and receiver do not see the same probability landscape, the path collapses. The system is only as stable as the model behavior it can reproduce.
Why Go Talks to Python
The codebase splits the job cleanly. Go owns the CLI, orchestration, state, and protocol flow. Python handles model inference through Hugging Face or MLX, which keeps the project close to the LLM ecosystem without dragging the whole tool into Python.
Go CLI and state
-> spawn Python model process
-> request next-token probabilities
-> select token with arithmetic coder
-> append to conversation chain
-> persist local state for decoding later
That tradeoff is smart for a proof of concept. The project stays portable, can support different runtimes, and avoids hardwiring itself to one inference stack. The cost is obvious: subprocess boundaries and model determinism become part of the product surface.
The Message Is Chained, Not Just Hidden
The repository does more than hide a single blob. It keeps a conversation chain, versions the envelope, and ties each message to prior state. That makes reordering, dropping, or replaying messages harder, at least in the clean environment the tool assumes.
| Layer | What it does | Why it matters |
|---|---|---|
| Envelope | Wraps the payload with versioning, nonce, and index fields. | Keeps the message format explicit and auditable. |
| Chain state | Links each message to the previous one. | Makes the conversation behave like a sequence, not isolated packets. |
| Shared model path | Uses the same token probability behavior on both sides. | Lets the receiver recover the hidden bits if nothing changed. |
| Local persistence | Stores conversation state on disk. | Supports resume and decode without a network dependency. |
That design makes the tool feel like a protocol, not a one-off stunt. The visible chat is only one layer. Under it sits state, ordering, and a key schedule that tries to keep sender and receiver aligned.
Where It Breaks
This is where the project earns credibility. If the text is edited, paraphrased, autocorrected, reformatted, or silently transformed by a platform, the hidden channel can fail. That fragility is not a bug in the story. It is the story.
The goal is to produce text resembling ordinary model-generated prose, with a tradeoff between payload capacity and text quality. This proof of concept has not been shown to be statistically undetectable, and its output must be copied exactly.
The repo’s own language around naturalness slack and semantic filtering points in the same direction. The system is balancing two incompatible goals: make the output look ordinary, and make it decodable with enough precision to recover the secret.
How It Compares to Older Steganography
The clearest comparison is not with a general encryption tool. It is with older steganography systems and academic LLM research that chase the same goal from different angles.
| Project | Core method | Stealth | Robustness | Best for |
|---|---|---|---|---|
| Conversation Steganography | LLM token selection with arithmetic coding | High, because the output looks like normal chat | Fragile, especially if text changes | Local-first hidden conversations |
| StegCloak | Invisible Unicode characters | Medium, but detectable by character analysis | Moderate, unless characters are stripped | Quick hiding in existing text |
| Meteor and OD-Stega | Probability distribution optimization | Very high in research settings | Varies by method and setup | Academic exploration of linguistic steganography |
| Ordinary encryption | Ciphertext | Low, because the output looks obviously encoded | High for the payload itself | Confidentiality without camouflage |
That is the project’s niche. It sits between academic rigor and usable tooling. It is not the strongest formal system on paper, but it is close enough to a real workflow to make the idea feel immediate.
What the Project Really Shows
The broader shift is bigger than this repo. Privacy tools are moving from hiding data to hiding the existence of data. In that world, the most effective secret is not a locked container. It is a sentence nobody thinks to question.
Conversation Steganography makes that future legible in one sharp prototype. It shows that an LLM can be more than a generator. It can also be a camouflage engine, one that turns everyday language into a hiding place.





