EncryptMaster — Stego Embed Image (LSB/AES-GCM)
Hide one image inside another — compressed, encrypted, and invisible
- cover_image
- secret_image
- image
- report
Stego Embed Image is the heavyweight sibling of Stego Embed Text: instead of a message, it hides an entire image inside another image. The cover looks completely normal, the secret image is compressed, encrypted, and buried in the least-significant bits, and only Stego Extract Image plus your passphrase can pull it back out.
This is the node for "look at this innocent photo" moments - embedding a watermark, a preview, a secret version of a render inside a public one, or just smuggling a picture through a channel where an obvious image file would raise questions. It's the closest thing in the pack to the classic "image-in-image" steganography you've seen in spy-movie explainers, except the hidden image is also AES-256-GCM encrypted, so even extracting it reveals nothing without the passphrase.
How it works
The secret image is handled smarter than a naive LSB implementation. It's compressed first - that's the trick that makes whole-image stego feasible - then encrypted, then embedded:
- The secret is encoded as PNG (lossless) or JPEG (
jpeg_quality, default 85). Compression shrinks it dramatically: a 1216×1600 secret at JPEG q85 is roughly 200–400 KB versus megabytes of raw pixels. - The compressed bytes are encrypted with AES-256-GCM (scrypt key derivation), prefixed with a 50-byte header storing the secret's dimensions and encoding type, and flattened to bits.
- Those bits overwrite the cover's least-significant bits at
bits_per_channel(1 or 2).
With resize_secret_to_cover on (default), the secret is scaled to the cover's dimensions first - which is almost always what you want, since a same-size secret gives the embedder its best fit.
Inputs and outputs that matter
cover_image(IMAGE) - the innocent carrier.secret_image(IMAGE) - the image to hide.passphrase(STRING, masked) - required; exact match at extraction.bits_per_channel- 1 or 2. For whole images, you'll usually want 2.secret_encode-png(lossless, bigger) orjpeg(much smaller, slight quality loss). Default is png, but jpeg is the practical choice for hiding whole images - the README calls it "recommended."jpeg_quality(INT, 50–95, default 85) - quality of the JPEG-encoded secret.associated_data(STRING, optional) - authenticated tag; must match at extraction.
Outputs are image (the carrier with the secret inside) and a report (STRING) telling you what was embedded and at what bit depth.
Capacity - the thing to actually care about
LSB capacity is width × height × 3 × bits_per_channel bits, and ciphertext is incompressible, so the budget is real. The README's numbers, from experience: a 1216×1600 cover at 1 bpc holds ~712 KB; at 2 bpc ~1.42 MB. A JPEG q85 version of that same-size secret lands around 200–400 KB, which fits comfortably at 2 bpc. Lossless PNG of a full image usually will not fit at 1 bpc - that's the classic first-time failure.
Good news: unlike the text embedder, this node tells you when it fails. If the payload is too big it returns the unmodified cover with a clear [StegoImage ERROR] Capacity too small... message in the report, with tips (use jpeg, bigger cover, or 2 bpc).
Installing it
Part of the ComfyUI EncryptMaster pack. Try ComfyUI Manager (search "ComfyUI EncryptMaster"), or:
cd ComfyUI/custom_nodes
git clone https://github.com/orion4d/Comfyui_EncryptMaster
Then the real dependencies (no requirements.txt in the repo - this is the list that installs):
pip install cryptography pillow numpy
Restart ComfyUI. No models, no GPU.
The rules that never change
Save the output as PNG or TIFF - JPEG recompression destroys the embedded bits. And remember that "looks invisible" is the point, not a side effect: at 2 bpc, flat gradient areas of the cover can show faint banding if you zoom in. If the cover needs to look perfect under scrutiny, 1 bpc is the safer bet and you pay for it with capacity. One light aside: this is the node you reach for when a whole secret image beats a text note - for short messages, the text stego pair is cheaper and simpler.
Inputs (8)
| Name | Type | Default | Description |
|---|---|---|---|
| cover_image | IMAGE | — | |
| secret_image | IMAGE | — | |
| passphrase | STRING | — | |
| bits_per_channel | COMBO | 1 | 2 options: 1, 2 |
| secret_encode | COMBO | png | 2 options: png, jpeg |
| jpeg_qualityopt | INT | 8550–95 | — |
| associated_dataopt | STRING | — | |
| resize_secret_to_coveropt | BOOLEAN | true | — |
Outputs (2)
| Name | Type | Description |
|---|---|---|
| image | IMAGE | — |
| report | STRING | — |