StegoToolkit

GIF Steganography Hider

Hide text or files inside static or animated GIFs using palette LSB, multi-frame distribution, or metadata injection. Optional AES-256 password encryption.

Optional AES-256
Animated GIF

Try the demo instantly

Loads a small animated GIF — no upload needed

Drop a GIF here or click to browse

Static or animated GIF · Max 50 MB

Processed in your browser

Your GIF is read and processed in this browser tab and is not sent to a StegoToolkit server. Coarse usage facts (such as file size range and the options chosen) may be counted if you accept analytics; the file, message and password are never included.

How to Hide a Message in a GIF (5 steps)

  1. Upload a GIF (static or animated, up to 50 MB)
  2. Type the secret text or choose a file to hide
  3. Choose a method: Palette LSB, Multi-Frame Distribution or Metadata (Extension Block)
  4. Optionally add a password, compression or Reed-Solomon parity, then check the capacity bar
  5. Click Hide, download the GIF, and give the Extractor the same method if you chose a non-default one

GIF Steganography Methods Comparison

MethodTechniqueQualityBest For
Palette LSBChanges the lowest bit of each pixel's colour index, so a pixel points to a neighbouring palette entryPixel colours change; the size depends on how different neighbouring palette entries areStatic GIFs and short messages
Multi-Frame DistributionSame lowest-bit change, spread evenly across the frames of an animated GIF (the header sits in frame 1)Fewer changed pixels per frame; animated GIFs onlyAnimated GIFs and larger messages
Metadata (Extension Block)Adds an Application Extension block named STEGANOG before the end of the file; no pixel changesPixels are untouched; up to 2,000 bytes; any tool that drops unknown blocks removes itShort notes in files that stay as they are

How a GIF Can Carry a Hidden Message

A GIF does not store a colour for every pixel. It stores a colour table (the palette, at most 256 entries) and then, for each pixel, a number that says which table entry to use. That number is the colour index. This tool's two pixel methods change the lowest bit of those index numbers: an index of 6 becomes 7, or 7 becomes 6, so the pixel points at the neighbouring table entry. If the two entries are similar colours, the change is hard to see. If they are not, it shows.

The third method does not touch pixels. A GIF file is a sequence of blocks, and the format allows Application Extension blocks that readers ignore if they do not recognise them. The Metadata method adds one such block, named STEGANOG, carrying your message. Players show the same picture, but the file is bigger and the block is easy to find in a hex editor.

The output is always a .gif. After the file is rewritten, each frame's delay, disposal method and transparency setting are copied through byte for byte (tested). That is not the same as the picture looking identical: Palette LSB and Multi-Frame change pixel colours, and on a transparent GIF they can change which pixels are transparent (see below). Only the Metadata method leaves every pixel as it was.

Which Method Should You Choose?

Use Palette LSB for a static GIF or a short message. It is the default. Use Multi-Frame Distribution when the GIF is animated and the message is large, because each frame then carries only a share of the bits. On a GIF with a single frame, Multi-Frame has no frames to spread across and offers no capacity, so pick Palette LSB there.

Use Metadata when you want the picture to stay byte-for-byte identical and the message is under 2,000 bytes. It is the most fragile in transit: any optimiser, converter or uploader that rewrites the file may drop the block. It also does not work on GIF87a files, which have no extension blocks, and the tool disables it for them.

Capacity: What Fits in a GIF

For Palette LSB and Multi-Frame the stored stream can be at most floor(total pixels ÷ 8) − 20 bytes, where total pixels counts every frame (Multi-Frame also needs a first frame of at least 96 pixels, because the whole header is written into it; below that its capacity is 0). The stream is a 12-byte header (6 bytes in plausible-deniability mode), then your data. Your data starts with one filename byte, so the largest plain-text message is 13 bytes smaller than the stream capacity. A password adds 48 bytes; Reed-Solomon adds a 16-byte header plus 10, 20 or 30 parity bytes per 223-byte block; Deflate compression can shrink the data, but by how much depends on the data, so the capacity bar only estimates it.

These limits are this tool's own, not limits of the GIF format: they follow from where this encoder puts the bits (one bit per pixel index) and from the header it writes. The bar and the Hide button use the same function the encoder enforces, including the header, the filename byte, a password, compression and Reed-Solomon. A payload that exceeds the capacity is refused with a message rather than cut short, and the boundary is tested: the largest payload the bar allows is written and read back exactly, and one byte more is refused, for every method with and without a password and Reed-Solomon. The encoder's own check is the authoritative one; the formulas here describe it. Sizes on this page are in bytes.

How Much Does the Picture Change?

It depends on the message length and on the palette. We measured PSNR (a standard distortion figure; higher means closer to the original) on 480 × 320 GIFs built from photo-like images with Pillow's palette quantiser. They are not real photographs, so treat the numbers as a guide: a 29-byte message gave roughly 55 to 60 dB, a 1 KB message about 36 to 41 dB, and a message filling half the capacity about 28 to 32 dB, with around a quarter of the pixels changing colour. The mean RGB distance between palette entries 2k and 2k+1 on those test files was about 10 to 17 units.

So "hard to see" is true for short messages and not for messages near capacity. After encoding the tool shows the measured PSNR, the number of pixels that changed and the frames modified for your actual file; trust those over any general statement.

Transparent GIFs

Palette LSB and Multi-Frame do not look at the transparency setting. If a frame marks one palette index as transparent, flipping the lowest bit of a pixel's index can move it onto or off that index, which turns a transparent pixel opaque or the other way round. In a test, hiding 300 bytes in a GIF with a transparent index made 638 transparent pixels opaque. The Hider shows a warning when it finds a transparent GIF. Use the Metadata method for such files, or check the result view.

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.

Reed-Solomon Error Correction

RS-10, RS-20 and RS-30 add parity bytes so that up to 5, 10 or 15 changed bytes per 223-byte block can be repaired. This helps when a few bytes are damaged. It does not help when a file is re-encoded, resized or optimised, because that changes most pixels at once. The Extractor recognises Reed-Solomon data by its header, so you do not need to set the level when decoding.

Plausible Deniability: Two Different Options

The "Plausible deniability" switch in the settings leaves the STEGO signature bytes out of the stream, so Auto-Detect cannot recognise the file by that signature. It requires a password, the Extractor needs the exact method chosen by hand, and it does not hide statistical changes from analysis.

The separate Forensic-mode section "Plausible Deniability: Decoy + Real Payload" builds a different thing: an outer decoy message in an Application Extension block and your real payload, AES-256-GCM encrypted, inside the pixels by Palette LSB. Without the password only the decoy is recoverable. Both layers are headerless, so this tool's Auto-Detect does not identify either one. That does not make the data undetectable by other tools or manual inspection; the decoy block is a visible Application Extension block, and statistical tests can flag the pixel changes.

Batch Watermarking

Forensic mode can embed a different recipient identifier in a copy of the GIF for each name in a list, and produce a ZIP of the copies plus a separate watermark_keys.json. It is a way to trace which copy leaked. It is not tamper-proof: anyone who re-saves a copy through an editor or optimiser removes the pixel changes.

Capacity Examples (computed from the tool's own formulas)

Stream capacity is the most the encoder can store at that setting. Largest message is the biggest plain-text payload that fits after the 12-byte header and the 1 filename byte; a password takes a further 48 bytes. Figures come from the same functions the encoder enforces.

Carrier and methodStream capacityLargest message, no passwordLargest message, with password
100 × 100, 1 frame, Palette LSB1,230 B1,217 B1,169 B
320 × 240, 1 frame, Palette LSB9,580 B9,567 B9,519 B
480 × 320, 1 frame, Palette LSB19,180 B19,167 B19,119 B
480 × 320, 24 frames, Palette LSB or Multi-Frame460,780 B460,767 B460,719 B
Any static GIF, Multi-Frame0 Bnonenone
Any GIF89a, Metadata (Extension Block)2,000 B1,987 B1,939 B
Any GIF87a, Metadata (Extension Block)0 Bnonenone

Measured Visual Change (480 × 320, photo-like test GIFs)

Test GIFs were made with Pillow's palette quantiser from synthetic photo-like images, not real photographs. Message sizes are payload bytes. Pixel counts follow from the method: about half of the written bits change a pixel index (12-byte header and filename byte included), so the count grows with the message. Your own result view reports the figures for your file.

MessagePSNRPixels that changed colour
29 Babout 55 to 60 dBabout 0.1% (about 170 of 153,600 pixels)
1 KBabout 36 to 41 dBabout 3% (about 4,100 pixels)
Half of the capacityabout 28 to 32 dBabout 25%

Examples

Input: 40-character text 0123456789012345678901234567890123456789, Palette LSB, no password, no compression, no Reed-Solomon

Result: 53 bytes are written: 40 characters + 1 filename byte + a 12-byte header

Meaning: It fits in any GIF with at least 53 × 8 + 160 = 584 pixels. The Extractor returns the same 40 characters.

Input: The same 40 characters with a password

Result: 101 bytes are written: 40 + 1 + 48 (AES-256-GCM overhead) + 12

Meaning: A password makes the stream 48 bytes bigger, so near-capacity messages that fit without one may not fit with one.

Input: 320 × 240 static GIF, a message 9,568 bytes long

Result: Refused: the largest message is 9,567 bytes

Meaning: The tool never cuts a message short. Use a bigger GIF, shorten the message, or turn on compression if the text is repetitive.

Input: Static GIF with Multi-Frame selected

Result: Capacity 0 B

Meaning: Multi-Frame needs several frames to spread across. Choose Palette LSB for a one-frame GIF.

Input: GIF87a file with Metadata selected

Result: Metadata is disabled

Meaning: GIF87a has no extension blocks. Palette LSB still works on it.

How the Hider Works

1. Parse

The GIF is read frame by frame, including each frame's palette and its LZW-compressed index data. Files over 50 MB are rejected.

2. Prepare the payload

Your data gets a one-byte filename frame, is optionally compressed with Deflate, optionally encrypted with AES-256-GCM, optionally wrapped in Reed-Solomon parity, and given a STEGO header (method, flags and length).

3. Embed

Palette LSB writes the bits into the lowest bit of consecutive pixel indices. Multi-Frame writes the header into frame 1 and spreads the rest evenly across the chosen frames. Metadata adds an Application Extension block before the end of the file.

4. Re-compress and rebuild

Changed frames are LZW-compressed again and the file is rebuilt. Frame delays, disposal methods and transparency settings are copied through unchanged.

5. Measure and verify

The tool compares the output with your original (PSNR, pixels changed, frames modified), and can decode its own output to confirm the message reads back.

What the results mean

ResultWhat it meansWhat to do
PSNR: 50 dB or moreThe output differs from the original by a very small amountNo action. Typical for short messages
PSNR: 40 to 50 dBA small but measurable differenceFine for most uses; compare the frames if it matters
PSNR: under 40 dBThe change is large enough to notice on some imagesShorten the message, use a larger or more detailed GIF, or use Metadata
Pixels that changed colourHow many pixels now point at a different palette entryMany changed pixels means a larger message for this carrier
Transparent pixels changedA transparent GIF where some pixels flipped between transparent and opaqueUse the Metadata method for this file
Refused: exceeds capacityThe stream (header + data + any password or parity) is larger than the GIF can holdUse a bigger GIF, shorten the message or remove the password or parity

Limitations

  • Re-saving, resizing, converting or optimising the GIF can destroy a pixel-method message, and Metadata survives only if the tool keeps unknown extension blocks. We have not tested individual websites or apps, so send the .gif file itself and run the Extractor on what was received.
  • GIF stores 256 colours at most and the lowest-bit change moves a pixel to a neighbouring palette entry, not by one brightness step. How visible that is depends on the palette; the figures on this page come from synthetic photo-like GIFs, not real photographs.
  • Palette LSB and Multi-Frame can change which pixels of a transparent GIF are transparent (see "Transparent GIFs" above); the Hider warns and Metadata avoids it.
  • The Metadata method holds at most 2,000 bytes here, is easy to find with a hex editor or a tool that lists extension blocks, and does not work on GIF87a.
  • Multi-Frame needs an animated GIF whose first frame has at least 96 pixels, and a custom frame selection or plausible-deniability message cannot be found by Auto-Detect; the Extractor needs the exact same choices.
  • This is steganography, not secrecy: hiding is not a substitute for a password, and statistical tools can flag LSB changes. A password is optional, and without one the message is readable by anyone with the Extractor.
  • Maximum file size is 50 MB. On phones the tool warns above 20 MB because processing can be slow.

Privacy

Your GIF and message are processed in your browser, in a Web Worker, and are not sent to a StegoToolkit upload server: the GIF code makes no network requests, and the encoded file is created on your device and downloaded from it. Page views are counted by Vercel Analytics, which is cookieless. Tool events (the options you choose such as the method or error-correction level, the file's type and a size range, and how long processing took) are sent to Google Analytics only if the site has it configured and you accepted analytics cookies. The code does not pass the GIF, the message, a password or the filename to analytics. If you accept cookies, the site may also load advertising and other analytics scripts that are outside this tool's code. Your display mode preference is kept in this browser's localStorage and the mobile tip banner's dismissal in sessionStorage.

Which Tool Next?

Frequently Asked Questions

What is GIF steganography?

It hides data inside a GIF so the file still opens as a normal picture. GIFs store a colour table and a colour index per pixel, so this tool changes the lowest bit of those indices (Palette LSB and Multi-Frame) or adds an extension block (Metadata).

How much can I hide in a GIF?

It depends on the pixel count and the method; the capacity section above has the formula and a table. For example, a 320 × 240 single-frame GIF holds a 9,567-byte message with Palette LSB, and Metadata holds 2,000 bytes. The capacity bar shows the exact limit for your file.

Can I hide data in an animated GIF?

Yes. Multi-Frame Distribution spreads the bits across frames, and Palette LSB also works on every frame in order. Total pixels add up across all frames, which is why animated GIFs hold much more than static ones.

Will the picture look different?

For a short message usually not noticeably; for a message near capacity yes. On our test GIFs a 29-byte message gave about 55 to 60 dB PSNR and half capacity about 28 to 32 dB with around a quarter of the pixels changing. The tool reports the measured figure for your file. The Metadata method changes no pixels.

Does it keep the animation timing and transparency?

Frame delays, disposal methods and the transparency flag are copied through unchanged, but the pixel methods can still flip individual pixels between transparent and opaque on a transparent GIF. See "Transparent GIFs" above; the Hider warns when it finds one.

Is it encrypted?

Only if you add a password: then the data is encrypted with AES-256-GCM before it is hidden. Without a password anyone with the Extractor can read the message. Encryption hides the content, not the existence of a hidden message.

Does the hidden message survive sharing?

A pixel-method message survives only if the receiver gets the exact same file. Converting, resizing, optimising or re-saving the GIF can destroy it, and some services do that. We have not tested individual services, so send the .gif as a file and check it with the Extractor.

What is the difference between GIF and PNG steganography?

A typical truecolour PNG stores a colour per pixel, so an LSB change moves a colour channel by one step. A GIF always stores palette indices, so the same lowest-bit change moves a pixel to a neighbouring palette entry, which can be a larger colour jump. PNG can also be indexed-colour, supports metadata chunks, and APNG adds animation; the Palette Steganography tools handle indexed PNG. This tool works on GIF only.

Related Tools