StegoToolkit

LSB Embedder

Hide text or files in an image using Least Significant Bit steganography. Sequential or PRNG pixel order, 1–4 bit depth, optional AES-256-GCM password. Reports the measured PSNR and saves a lossless PNG.

Try the demo instantly

Loads a 200×200 gradient PNG — no upload needed

Drop image file or click to browse

PNG, BMP, WebP, JPEG — max 50 MB (10 MB on mobile). TIFF opens only in browsers that can decode it (Safari)

Processed in your browser

Your image is decoded in your browser, in a Web Worker, and is not sent to a StegoToolkit server. Basic usage analytics record coarse facts such as file type and size range, never the image or your payload. Output is always a lossless PNG.

How to Hide Data in an Image Using LSB (4 steps)

  1. Upload a cover image — PNG, BMP, WebP or JPEG (up to 50 MB, 10 MB on phones). The tool shows how many bytes it can hold. The result is always saved as a lossless PNG.
  2. Type your secret message or choose a file. Standard and Forensic modes add PRNG pixel order, 2–4 bit depth, channel choice and (Forensic) Alpha and deflate compression.
  3. Optional: enter a password to encrypt the payload with AES-256-GCM before it is embedded. PRNG order also takes its seed from this password.
  4. Click 'Hide in Image' — processing runs in a Web Worker in your browser. Download the PNG and send it as a file; any step that re-encodes the picture (JPEG conversion, resizing) destroys the data.

LSB Steganography — Modes & Methods

ModeTechniqueEffectBest For
Sequential (Basic)Overwrites the lowest bit(s) of the chosen channels pixel by pixel, left to right, top to bottom, after a 20-byte headerFills the first rows completely before touching the rest; the order other LSB tools readFiles that travel losslessly, CTF challenges, tools that read raster order
PRNGSame bits, but pixel positions come from a seeded shuffle (Mulberry32, seed = first 4 bytes of SHA-256 of your password)Spreads the changes over the whole image; without a password the seed is 0 and anyone can reproduce itHiding less than a full image's worth of data; use with a password
Blue ChannelSequential order, Blue channel onlyOne third of the capacity; Blue carries 11% of perceived brightness (BT.601 weights)Smooth images where the changes should be least visible
1-bit depthChanges each used colour value by at most ±1Smallest change: 52.4 dB PSNR at full capacity on the photo crops we measuredDefault for every mode
2–4 bit depthChanges each used colour value by at most ±3, ±7 or ±1533–45 dB PSNR at full capacity on the same crops; larger steps can show in smooth areasLarge payloads in a small image

What LSB Hiding Actually Changes

A decoded image is a grid of pixels, and each pixel is four numbers from 0 to 255: red, green, blue and alpha. Least-significant-bit (LSB) steganography overwrites the lowest bit (or the lowest 2, 3 or 4 bits) of some of those numbers with the bits of your message. At 1 bit a colour value moves by at most 1 out of 255. The tool decodes your image in a Web Worker, changes values in memory, and saves the result as a PNG. PNG is lossless, so every changed value stays exactly as written until something re-encodes the picture.

Nothing here is encryption: without a password the message is stored as plain bytes, and the Extractor (or any tool that knows the layout) reads it back. With a password the payload is encrypted first, but the 20-byte LSBSTEG header that precedes it is not, so the image still shows that a payload of a given size exists.

A Bit-Level Example You Can Check by Hand

Hide the letter A (byte 65, bits 01000001) with Sequential, 1-bit, R + G + B, into three pixels whose colours are (200, 100, 50), (201, 101, 51) and (202, 102, 52). The eight bits go into the lowest bit of eight consecutive values: R, G, B of pixel 1, R, G, B of pixel 2, then R, G of pixel 3. Result: pixel 1 becomes (200, 101, 50), pixel 2 becomes (200, 100, 50) and pixel 3 becomes (202, 103, 52). Five of the 8 values changed (100→101, 201→200, 101→100, 51→50, 102→103), each by exactly 1; the other three already held the right bit, and the Blue value of pixel 3 was not touched. Reading the lowest bit of those eight values in order gives 0, 1, 0, 0, 0, 0, 0, 1 — the byte 65 again. (The real tool starts at pixel 55 and writes the header first; this example starts at pixel 1 to keep the numbers short.)

Variant, Bit Depth and Channels

Quick mode always uses Sequential, 1 bit, on R, G and B. Standard adds PRNG order, a Blue-only variant, 2-bit depth and channel selection. Forensic adds 3 and 4 bits, the Alpha channel and deflate compression.

Sequential writes the payload into pixels 54, 55, 56 … in raster order (pixels 0–53 hold the header). PRNG picks pixel positions with a partial Fisher–Yates shuffle driven by Mulberry32, a small non-cryptographic generator, seeded with the first 4 bytes of SHA-256 of your password. That is a 32-bit seed (about 4.3 billion values), so it spreads the changes but is not a secret: the AES encryption is what protects the content. With no password the seed is 0.

Each extra bit of depth doubles the capacity and the largest possible change to a colour value (1-bit ±1, 2-bit ±3, 3-bit ±7, 4-bit ±15). The channel notes in the interface show each channel's share of perceived brightness using the standard BT.601 weights (R 30%, G 59%, B 11%); they are a guide to which changes the eye notices most, not a measure of detectability.

Capacity: What Fits in an Image

Capacity is (width × height − 54 header pixels) × channels × bit depth ÷ 8, rounded down to whole bytes. The 20-byte header sits in its own 54 pixels, so it is not taken off again. A password costs 48 bytes (4-byte length + 16-byte salt + 12-byte IV + 16-byte authentication tag). The capacity bar and the encoder use the same function, and the encoder refuses a payload that does not fit with a 'Payload too large' message instead of writing part of it.

Deflate (Forensic) is applied before encryption. The capacity bar counts the uncompressed size; the encoder checks the compressed size, which for short or already-compressed data can be slightly larger than the original. The payload's file name is not stored: when you hide a file, the Extractor guesses its type from its first bytes.

What Gets Written

First a 20-byte header in pixels 0–53, always 1 bit per channel on R, G, B in order: the magic 'LSTG', a version byte, the variant, a channel bitmask, the bit depth, an encryption flag, a reserved byte, a compression flag, a reserved byte, the stored payload length (4 bytes, little endian) and a CRC-32 of the first 16 bytes. Then the stored payload — your bytes, deflated if you chose that, then AES-wrapped if you set a password — bit by bit, most significant bit first, using the chosen depth and channels. Pixels beyond the payload are not touched.

The Extractor reads the header first and uses the recorded settings when its CRC matches and the stored length fits the image. Images made by other tools have no such header, which is why the Extractor also has a ranked scan.

How Strong Is the Encryption?

If you set a password, the payload is encrypted before it is hidden, in your browser, with the Web Crypto API. The cipher is AES-256 in GCM mode. The 256-bit key is derived from your password with PBKDF2 using HMAC-SHA-256 and 100,000 iterations. Every encryption generates a fresh random 16-byte salt and a fresh random 12-byte IV with the browser's cryptographically secure random generator (crypto.getRandomValues), so encrypting the same message twice gives different bytes. The stored layout is a 4-byte length, the salt, the IV, then the ciphertext followed by a 16-byte authentication tag — 48 bytes added in total. The tag means a wrong password, or any change to the ciphertext, makes decryption fail instead of returning garbage.

These are standard, widely used primitives, but this implementation has not had an independent audit, and using them does not make the system unbreakable. Their limits matter. Anyone who finds the hidden data can copy the ciphertext and try passwords against it offline, so your password is the real defence: use a long random passphrase, not a word or a date. The key derivation uses 100,000 PBKDF2-HMAC-SHA-256 iterations. This is below OWASP's commonly cited 600,000-iteration recommendation for password storage. That recommendation is not a direct requirement for this file-encryption format, but the lower count makes a strong, unique password especially important. The iteration count is fixed in the code and is not written into the file, so raising it requires a new, versioned format so that files already made can still be opened.

Encryption hides what the data says, not that it exists, and it does not hide the length: AES-GCM ciphertext is the same length as the plaintext, and the encrypted layout adds a fixed 48 bytes of overhead: its own 4-byte length field, the salt, the IV and the authentication tag, separate from any length or framing the tool puts around it. No padding is added. There is no key file and no way to recover a forgotten password. We have not measured key-derivation time on phones or older devices, so very large batches or slow devices may take noticeably longer per file. The tool is intended for educational and personal use; do not rely on it as the only protection for sensitive information, or against a well-resourced adversary.

Formats, Transparency and Size Limits

PNG, BMP, WebP and JPEG covers are accepted, up to 50 MB (10 MB on phones). A JPEG or lossy-WebP cover is fine: it is decoded to pixels first, and the output is a PNG — only converting the result back to JPEG destroys the data. TIFF opens only in browsers that can decode it. Animated GIF is not supported.

A browser canvas cannot keep exact colour values under transparent or partly transparent pixels, so a cover with transparency is placed on a white background before embedding (the tool tells you when it did). The Alpha channel option (Forensic) changes pixel transparency itself; browsers may then alter the colours of the pixels it touched. After encoding, the result screen re-reads the PNG in your browser and warns if any embedded byte changed. In our check, Chromium and WebKit kept every byte of a test image with alpha 254 on half its pixels, while Firefox altered 307 of 2,048 such pixels — so avoid Alpha unless you verify the output.

Detectability

Overwriting low bits leaves statistical traces that tests such as chi-square, RS analysis and sample pairs can find, especially when most of the image is used. In our tests with this site's chi-square statistic (the Steganography Analyzer's whole-image test) on 8 crops of one photograph, a random payload filling 5%, 25% and 60% of the capacity gave a p-value of 0.000 (PRNG with a password reached at most 0.04 at 60%, over 4 runs of the 8 crops) — the same as the unmodified photographs — and a full image gave 0.85–1.00 for Sequential and 0.25–1.00 for PRNG (4 runs × 8 crops; the lowest value seen in each run was 0.25, 0.51, 0.64 and 0.85). The PRNG figures vary from run to run because the password-encrypted payload uses a random salt and IV, so the bits written differ each time; Sequential without a password is repeatable. That is one statistic on one photograph: it does not show that partly filled images escape other tests (such as the progressive chi-square test or RS analysis), and a text payload is less random than the random bytes used here.

Using less of the capacity and a textured photograph helps; PRNG order avoids filling the top rows completely. None of this guarantees that a detector will miss the data. The tool reports measured figures (PSNR, share of pixels changed) rather than a 'stealth score', because no single score can be tied to a measurement.

Capacity Examples (computed by the tool's own function)

Bytes the stored stream may occupy, R + G + B channels unless stated. 'With password' is the largest plain payload once the 48-byte encryption wrapper is added. The exact boundary (largest payload accepted, one byte more refused) is tested for every variant × depth × channel set × password state at 96 × 96 pixels.

ImageBit depthChannelsNo passwordWith password
100 × 1001R, G, B3,729 B3,681 B
800 × 6001R, G, B179,979 B179,931 B
800 × 6001Blue only59,993 B59,945 B
800 × 6002R, G, B359,959 B359,911 B
800 × 6004R, G, B719,919 B719,871 B
1920 × 10801R, G, B777,579 B777,531 B
1920 × 10801R, G, B, A1,036,773 B1,036,725 B

Measured Image Quality (PSNR)

PSNR (decibels over all RGBA bytes, peak 255) compares the original and the changed pixels; higher means a smaller change. Mean of 8 crops of 800 × 600 cut from one photograph (a desktop-wallpaper landscape), random payloads, Sequential order. PRNG gave the same figures to within 0.01 dB (for example 58.41 dB Sequential and 58.42 dB PRNG at 25% fill in one run): a random payload flips about half of the values it touches wherever it is written, and the two orders flipped slightly different counts (180,050 and 179,773 values on one crop). One photograph is an example, not a promise for every image; PSNR also says nothing about whether a detection tool would notice.

Setting5% full25% full60% full100% full
1-bit, R + G + B65.4 dB58.4 dB54.6 dB52.4 dB

PSNR by Bit Depth at Full Capacity

Same 8 photograph crops, Sequential, R + G + B, payload filling the capacity.

Bit depthLargest change per valuePSNR
1±152.4 dB
2±345.4 dB
3±739.1 dB
4±1533.2 dB

Does It Survive a Conversion? (our test)

A 600-byte message, 1-bit Sequential R + G + B, hidden in each of the 8 photograph crops; the PNG was converted with the macOS system image tool (sips) and the Extractor's decoder was run. Other encoders, resizing and individual websites or apps were not tested.

ConversionMessage recovered
PNG → PNG8 of 8
PNG → BMP → PNG8 of 8
PNG → TIFF → PNG8 of 8
PNG → JPEG quality 100 → PNG0 of 8
PNG → JPEG quality 95 → PNG0 of 8
PNG → JPEG quality 75 → PNG0 of 8

Examples

Input: A 100 × 100 PNG, the 40-character text 'Meet me at the old bridge at 9pm sharp!!', Sequential, 1-bit RGB, no password

Result: Accepted (capacity 3,729 B). The Extractor returned all 40 bytes unchanged.

Meaning: The 40 bytes use 320 colour values of the 29,838 available, so at most 320 values change by 1; the header adds 160.

Input: An 800 × 600 PNG and a binary file of exactly 179,979 bytes, 1-bit RGB, no password

Result: Accepted and read back byte for byte; 179,980 bytes is refused with a 'Payload too large' message.

Meaning: The limit is the number the capacity bar shows. The same exact-limit test passes for every variant, bit depth and channel set.

Input: The same image, a 179,931-byte file and a password

Result: Accepted; one more byte is refused. The password adds 48 bytes to what is stored.

Meaning: Encryption costs a fixed 48 bytes, not a percentage. The header stays readable, so the image still reveals that an encrypted payload of this size exists.

Input: A 1-byte secret with 3-bit depth

Result: Read back as the same byte.

Meaning: 8 bits do not divide evenly into 3-bit groups; the last group is zero-padded and the length in the header says where the data ends.

How the LSB Embedder Works

1. Decode the cover

The browser decodes your image to raw RGBA pixels in a Web Worker (a cover with transparency is placed on white first). Nothing is uploaded.

2. Prepare the payload

Your bytes are optionally deflated, then optionally encrypted with AES-256-GCM. The result is the stored stream.

3. Check it fits

The stored length is compared with (pixels − 54) × channels × depth ÷ 8. If it is larger the encoder stops with a 'Payload too large' message.

4. Write header and payload

The 20-byte header goes into pixels 0–53 (1 bit per R, G, B). The payload bits follow in raster order, or at pixel positions from the seeded shuffle for PRNG.

5. Measure and save

The tool computes PSNR and how many pixels changed, saves a PNG, re-reads that PNG in your browser and checks every byte matches.

What the results mean

ResultWhat it meansWhat to do
PSNR above about 50 dBA very small change (1-bit at modest or full fill on the photos we measured)Fine for most uses; it says nothing about whether a detection tool would find the data
PSNR 30–40 dBA larger change, typical of 3–4 bit depth at high fillCompare the Original and Stego previews; use a larger cover or a shorter payload
Pixels modified above 20%A large part of the image was rewrittenStatistical detection gets easier; use a larger cover or PRNG order
Self-check: every byte matchedThis browser read back exactly what was embeddedThe PNG is good; other programs and websites were not tested
Self-check: this browser changed N bytesThe browser altered pixel values while writing the PNG, usually under transparency or AlphaUse an opaque cover without Alpha, or another browser, and extract the result to confirm
Refused: Payload too largeThe stored data is larger than the image holds at these settingsUse a larger image, higher bit depth, more channels, or a smaller payload

Limitations

  • The output is always a PNG. Converting it to JPEG destroyed the message in every one of our tests (quality 100, 95 and 75); PNG, BMP and TIFF copies kept it. Other encoders, resizing and individual websites or apps were not tested.
  • LSB hiding is not encryption: the header is readable and an unencrypted payload can be read by any tool that knows the layout (see Detectability for what statistics can find).
  • The file name of a hidden file is not stored; the Extractor guesses the type from the file's first bytes and names the download accordingly.
  • Transparency: covers with transparent areas are flattened onto white; the Alpha channel option can make browsers alter colours of the pixels it touches (Firefox did in our check).
  • PRNG order is not a security feature (see Variant, Bit Depth and Channels).
  • PSNR and the chi-square figures on this page were measured on 8 crops of a single photograph; other images give other numbers.
  • TIFF covers open only in browsers that can decode TIFF; animated images are not supported.

Privacy

Your image, payload and password are processed in your browser, in a Web Worker, and are not sent to a StegoToolkit server by the tool's own code. The only things the tool keeps in your browser are your chosen mode (localStorage) and a flag that you dismissed the mobile tip (sessionStorage). Site analytics, if you accept them, may record coarse facts such as the bit depth or compression option you chose, the file type, a size range and timing — never the image, payload, password or file name. The hosting provider's ordinary request logs still record your visit.

Which Tool Next?

Frequently Asked Questions

Which images can I use, and why is the output a PNG?

PNG, BMP, WebP or JPEG covers, up to 50 MB (10 MB on phones); TIFF only where the browser can decode it. The cover is decoded to pixels, the bits are written into the pixel values, and the pixels are saved as a PNG because PNG is lossless. A JPEG cover is fine for that reason — what destroys the data is converting the result back to JPEG, which lost the message in every test we ran (quality 100, 95 and 75).

How many bytes of LSB data fit in my image?

(width × height − 54) × channels × bit depth ÷ 8 bytes, rounded down. For a 1920 × 1080 image with 1-bit RGB that is 777,579 bytes, or 777,531 bytes if you set a password (48 bytes of encryption wrapper). The capacity bar uses the same function as the encoder, and one byte more is refused with a clear message.

What is the LSBSTEG header and why is it needed?

A 20-byte block in the first 54 pixels (1 bit per R, G, B): magic 'LSTG', version, variant, channel bitmask, bit depth, encryption flag, compression flag, stored length and a CRC-32. It lets the Extractor use your exact settings without guessing. It is not encrypted, so it also shows that a payload of that size exists.

What does PRNG mode do?

It keeps the same bits but chooses the pixel positions with a seeded shuffle (Mulberry32) instead of walking left to right. The seed is the first 4 bytes of the SHA-256 of your password, so the Extractor needs the same password; with no password the seed is 0 and anyone can reproduce the order. It is a 32-bit, non-cryptographic seed: it spreads the changes, but only the AES encryption protects the content.

What does the password do in LSB hiding (encryption and PRNG order)?

It encrypts the payload with AES-256-GCM (key from PBKDF2-HMAC-SHA-256, 100,000 iterations) before embedding and adds 48 bytes. A wrong password makes extraction fail with a 'Wrong password' message — it does not produce noise, and the unencrypted header still reveals that an encrypted payload is present. In PRNG mode the same password also seeds the pixel order.

Will the stego image survive sharing online?

It survives anything that keeps the PNG's pixels unchanged: a file copy, an email attachment, cloud storage downloads. It does not survive JPEG conversion (0 of 8 recovered, macOS sips at quality 100, 95 and 75) or, in principle, anything that resizes or re-encodes the picture. We have not tested individual websites or apps, so send it as a file and check by extracting it.

Is the picture visibly changed?

At 1-bit depth each used colour value moves by at most 1 out of 255; on the photo crops we measured the PSNR was 52.4 dB at full capacity and 65.4 dB at 5%. At 4 bits it was 33.2 dB, which can show in smooth areas. The result screen shows both previews and the measured PSNR for your image. 'Hard to see' is not 'undetectable': statistical tests can still find heavily filled images.

Is my image or payload uploaded to any server?

The tool's own code does not upload your image, payload or password: encoding runs in a Web Worker in your browser. Accepted analytics may record coarse option choices, file type, size range and timing, but never the image, payload, password or file name, and your host's ordinary request logs still see the page visit.

Related Tools