Expand description
Building a container around a payload, instead of hiding a payload inside one.
§Who this is for
Someone with no usable photograph. A laptop with no comfortable way to move pictures across from a phone, or a camera that only ever emits JPEG or HEIC — both lossy, both leaving the 8x8 grid the validation layer refuses, and neither repaired by converting the file to PNG. Until this mode existed that user had no way through at all.
§Why generating is more secure than embedding, not less
Embedding modifies an image, and a modification is a thing a detector can hunt for; the argument is only ever about how well it is hidden. Generating allows something an embedder cannot do: draw each sample from the cover distribution conditioned on its least significant bit being the ciphertext bit it must carry. Rejection sampling does that in about two draws, and then, for a uniform carrier bit,
sum over b of P(sample = v | LSB = b) P(b) = P(sample = v)exactly, provided the least significant bit of the unconditioned distribution is a fair coin. The container that carries a message and the container that carries nothing are draws from one distribution. There are not two hypotheses to separate, so there is no statistic to find and no future detector that changes the answer. The naive construction — overwrite the least significant bit rather than draw conditioned on it — forfeits exactly this, and is measurably worse than doing nothing; the reasoning is with the sampler that refuses to use it.
Three consequences follow, and the code below depends on all of them:
- The 0.02 bpp cap does not apply here. It exists because a photograph’s distribution is unknown to the sender. Here the sender is the distribution, and every sample carries a bit: 1.45 MB in a 2000x2000 container against roughly 7 KB for the embedding path.
- The HILL cost map and the trellis take no part. There is no cost to minimise when every position is equally free.
- “One image + one password = one message” holds by construction. Each generation is a new container with a different perceptual hash, and therefore a different salt, key and nonce. For the embedding path that rule is a matter of the user’s discipline; here it cannot be broken.
§What this mode does not do
It hides which, not whether. The two hypotheses it equalises are “generated around a message” and “generated around nothing”. It says nothing about “generated” versus “photographed”: the container looks like a synthetic texture, and someone looking at a folder of them sees images nobody has an obvious reason to keep. Against “which of these hundred carries the message?” it is a complete answer. Against “why do you have this folder?” it is no answer at all.
§Untying the circularity
The key comes from the perceptual hash of the container, which now depends
on the ciphertext, which depends on the key. The knot unties because the
hash reads a 32x32 thumbnail where some four thousand grain samples average
away to nothing, and layer 1 already refuses any container whose
coefficients sit within 5.0 of their median. So a draft container fixes
the hash, the hash fixes the key, and the final container is checked to
hash the same before anything is written.
The draft never touches the disk. It is built in memory and handed straight to the gates. A draft on disk would be the original cover, and the original cover not existing is precisely what this mode buys.
§The seed is key material
There is no cover to subtract, but an adversary who can reproduce the generator’s random state can regenerate the container and read the difference — and the confirmation is unmistakable, because the right state reproduces the image and a wrong one differs in millions of samples. So the generator is seeded with 32 bytes from the system CSPRNG and from nothing else: never a timestamp, never a counter, never anything derived from the password. The seed is not persisted, printed or logged anywhere.
Structs§
- Container
Dimensions - The size of the container to draw, checked against the two size gates.
- Generate
Report - What one generation produced.
- Rejection
Exhausted - The one way conditioned sampling can fail.
Enums§
- Generate
Error - Everything that can go wrong between a plaintext and a generated container.
Constants§
- DEFAULT_
CONTAINER_ SIDE - Side of the square container generated when no size is requested.
- MAX_
CONTAINER_ PIXELS - Largest pixel count a generated container may have.
- MIN_
CONTAINER_ SIDE - Smallest side a generated container may have, in pixels.
Functions§
- generate_
container - Builds a
dimensionscontainer aroundplaintextand writes it tooutput_path.