Library / SDK
ragibson/Steganography avatar
ragibson/Steganography

stego-lsb: least significant bit steganography for images, WAV files and bytes

Least Significant Bit Steganography for bitmap images (.bmp and .png), WAV sound files, and byte sequences. Simple LSB Steganalysis (LSB extraction) for bitmap images.

665 stars104 forksPythonMIT

At a glance

What is it?
The stego-lsb package hides data in the least significant bits of bitmap images, WAV sample data and raw byte sequences, and ships a simple extraction routine to detect it. It fits a security student, a CTF participant or a developer who needs to understand what LSB embedding looks like on real files, and it says plainly that the technique is not encryption.
Who is it for?
stego-lsb fits a learner who wants to see least significant bit embedding work end to end on real files, or a CTF participant who needs a quick hide and extract cycle with a command line interface. It is the wrong choice for anything that needs confidentiality, since the payload is not encrypted and the carrier is not authenticated, and it is the wrong choice for a production pipeline that needs a maintained, audited steganography implementation.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 2 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What LSB embedding actually does to your file

The package description is precise about the scope: least significant bit steganography for bitmap images in .bmp and .png, WAV sound files, and byte sequences, plus a simple LSB steganalysis routine for bitmap images.

The mechanism for images is described in the README's LSBSteg section. For each colour channel, the R, G and B of each pixel, the least significant bits of the colour value are overwritten with data from the file being hidden. The number of bits per channel is configurable, with a default of two. Alongside the payload, the file size of the input is also hidden in the first few colour channels of the image, which is what makes recovery possible without being told the size out of band.

For audio, the approach is the same idea on a different substrate. WavSteg uses least significant bit steganography to hide a file in the samples of a .wav file, and for each sample in the audio file the least significant bits are overwritten with the data from the file.

That is the whole technique, and understanding it is the point. The change is imperceptible because the least significant bit of an 8-bit colour value changes a pixel by at most one, and at the lowest bit of a 16-bit audio sample it is below the threshold most playback chains resolve. But nothing is encrypted, nothing is authenticated, and a carrier that has been JPEG-compressed, resampled or re-encoded will lose the embedded data.

The audience is therefore a security student or a CTF participant, and the README points to the author's own introductory article, Steganography: Hiding Data Inside Data, for readers unfamiliar with the field.

The byte-level primitives and why they are the interesting part

Before the file formats there is a byte-level layer, and the README describes it as providing the ability to quickly interleave the bytes of a payload directly in the least significant bits of a carrier byte sequence. It contains four primary functions.

lsb_interleave_bytes(carrier, payload, num_lsb, truncate=False) does the embedding, and lsb_deinterleave_bytes(carrier, num_bits, num_lsb) takes the data back out, taking the number of bits to recover as a parameter. The other two are list variants, lsb_interleave_list and lsb_deinterleave_list, which run the same operations on a list of uint8 values.

The design point is that embedding is a bit-interleaving operation, not a file-format operation. The image and WAV tools are built on top of it, and the truncate flag controls what happens when the payload exceeds the carrier's capacity.

Running bit_manipulation.py, calling its test() function directly, or running stegolsb test produces a table of encode and decode rates for a 1.0 MB payload in a 10.0 MB carrier, across one to eight LSBs. The README notes the encode rate rises with the number of LSBs used, and that at eight LSBs the decode rate is much higher than the encode rate. The exact numbers in that table are the project's own measurements on its own machine, and they are interesting mostly as a shape: the low-LSB rows are encode-bound, and the high-LSB rows are decode-bound.

For a reader, the takeaway is that this layer is a clean, reusable primitive and the file tools are thin wrappers around it.

Hiding and recovering in a WAV file, and the size contract

The WavSteg command line is compact, with flags for the operation, the input, the secret file, the output and the number of bits. The default for the LSB count is two.

Hiding looks like this:

bash
    $ stegolsb wavsteg -h -i sound.wav -s file.txt -o sound_steg.wav -n 1
    # OR
    $ stegolsb wavsteg -r -i sound_steg.wav -o output.txt -n 1 -b 1000

and the README explains that hiding uses -h, -i, -s, -o and -n, and that the command also outputs how many bytes have been used out of a theoretical maximum. If you attempt to hide too much data, WavSteg prints the minimum number of LSBs required to hide your data, which is a helpful failure rather than a truncated file.

The worked example shows the output shape, with a line reporting how many bytes can be hidden at the chosen depth, a line with the number of bytes hidden, and a line for the output file being written.

Recovery is the part with a contract worth reading carefully. The recover operation uses -r, -i, -o, -n and -b, and the README is explicit that this requires the size in bytes of the hidden data to be accurate, or the result may be too short or contain extraneous data. The WAV path does not hide a size tag the way the image path does, so the size has to come from outside.

That asymmetry between the two tools is the single most important operational difference. The image tool writes the file size into the first few colour channels, so recovery needs only the image and the LSB count. The WAV tool needs the byte count as well.

The image tool hides the size for you, and can tell you the capacity first

LSBSteg has the extra affordance that makes it usable without a side channel. For each colour channel in each pixel of the .bmp or .png image, the least significant bits of the colour value are overwritten with the data from the file, and the file size of the input file is also hidden in the first few colour channels of the image, so that recovering the data is easier.

The command line adds an analyze mode that reports capacity before you commit to anything, printing the image resolution, how many bytes can be hidden at the chosen depth, the size of the input file, and the size of the file size tag. The analyze flag is the first thing to run on an unknown carrier image, because it tells you whether the file will fit at the LSB depth you intend to use.

Hiding then writes the steganographed image, and the README's example output shows a file read, a byte count of what was hidden, and the image being overwritten.

There is one more parameter that matters. The compression option takes a value from 1, for best speed, to 9, for smallest file size, with a default of 1. So the payload is compressed before embedding, which is why the analyze output compares a compressible payload size against the hidden capacity.

The whole flow, then, is: analyze to check capacity, hide with a chosen depth and compression level, and recover with the same depth. The recovery step for images needs the LSB count and nothing else, because the size tag travels inside the image.

StegDetect, and the limits of a simple extraction test

The package description mentions simple LSB steganalysis, LSB extraction, for bitmap images, and the table of contents lists StegDetect as one of the four subcommands alongside WavSteg and LSBSteg.

The README text available here is cut off before the StegDetect section, so the exact command line and the criteria it uses to decide that an image carries hidden data are not documented in the part of the page I could read. That is worth flagging rather than glossing over, because the difference between extraction and detection matters. Extraction is a mechanical operation: pull the low bits out and see what falls out. Detection is a judgement: decide whether this carrier holds something.

A simple LSB extraction tool answers the first question, and a tool that reports whether an image looks like it carries data answers the second. The first is trivial and deterministic. The second is a statistical judgement, because a natural image also has low bits that look random, and the classic baseline for a naive LSB detector is a chi-squared style test on the bit distribution.

So the reasonable expectation is that StegDetect is closer to the first kind than the second, and that it is a way to attempt recovery from an image rather than a forensic verdict on one. For a CTF challenge, where the goal is to pull a payload out of a carrier, that is exactly what you want. For an investigation, where the question is whether a given image was tampered with, the same routine is a weak instrument and the README gives no basis for treating it as more.

The package name, stego-lsb, is on PyPI, and the CLI is installed as a single stegolsb command.

Packaging, typing and the maintenance picture

The project metadata is the part that tells you how much care has gone into the distribution. The build system is setuptools with a requirement of 75.3.2 or newer, the package name on PyPI is stego-lsb at version 1.7.2, and it requires Python 3.9 or newer.

The dependencies are Click for the command line, Pillow for the image work, and numpy with version markers that reflect the numpy 2.x transition. The metadata pins numpy for Python 3.9 specifically to below 2.1.0, pins numpy below 1.25.0 for Python 3.8, and leaves Python 3.10 and later on a plain numpy requirement. That is a careful set of constraints for a package whose image code is a thin layer over Pillow and whose byte code is a thin layer over numpy.

The typing story is also better than most small packages manage. The repository root has a py.typed marker, the package data section ships it, there is a mypy.ini, and the tool.setuptools.package-data entry explicitly includes the marker. That means type checkers will use the package's annotations rather than treating it as untyped.

The CLI entry point is registered as stegolsb pointing at stego_lsb.cli:main, and the package discovery includes only the stego_lsb package, so the module and its console script are the whole public surface.

On maintenance, the last push was on 2026-10-11 and the repository is not archived. The release history shows v1.7.0 on 2025-09-27, v1.7.1 on 2025-10-10 and v1.7.2 on 2026-10-11, so a steady patch cadence with a year-long gap at one point. The repository also has an AUTHORS.md, a tests directory and a .github directory.

The licence is MIT, with a LICENSE.md at the root and the same identifier recorded in the project metadata.

Where stego-lsb is the wrong tool

The first wrong-tool case is anything that needs confidentiality, and this is the point most worth making plainly. Least significant bit embedding is not encryption. The payload sits in the carrier in the clear, recoverable by anyone who knows the technique and the bit depth, which is why the steganalysis half of the package exists. If a secret needs to be protected, this is the wrong mechanism, and no configuration of the LSB count changes that.

The second is a carrier you do not control the format of. The embedding writes into the pixel data or the sample data directly, so the carrier must remain in its original encoding. A PNG that has been re-saved, a WAV that has been resampled or transcoded, and an image that has been through a lossy format all lose the payload. A real steganography deployment faces this constantly, because the point of hiding data in a photo is that people post photos.

The third is a need for a keyed or authenticated method. There is no scrambling of the embedded bits beyond the interleaving, no key, and no integrity check on the recovered payload, so a wrong LSB count or a wrong byte count gives you bytes rather than an error. The README says this about the WAV path, that the size in bytes of the hidden data must be accurate or the result may be too short or contain extraneous data, and the same applies to the image path with a wrong depth.

The fourth is a production service that needs an audited implementation. This is a small library with a command line interface, a test directory and typed packaging, which is the right shape for a tool in a lab or a CTF. It is not the right shape for a system where the steganography method is a product requirement, because there is no statement of the threat model, no key management, and no algorithm agility.

The alternative to weigh is a dedicated steganography library that treats the method as a researched field, with keyed embedding, a defined threat model and handling for the transformations that break naive LSB. The difference in approach is that this package gives you a clean, readable implementation of the classic technique, while those give you a design you can defend to an auditor.

Editorial conclusion

stego-lsb fits a learner who wants to see least significant bit embedding work end to end on real files, or a CTF participant who needs a quick hide and extract cycle with a command line interface. It is the wrong choice for anything that needs confidentiality, since the payload is not encrypted and the carrier is not authenticated, and it is the wrong choice for a production pipeline that needs a maintained, audited steganography implementation. Before adopting it, read the packaging metadata for the Python version and numpy constraints, and test the extraction routine on a file you embedded yourself before drawing any conclusion about a file you did not.

Frequently asked questions

What can stego-lsb hide data in?

It hides data in the least significant bits of bitmap images in .bmp and .png format, in the sample data of WAV sound files, and in arbitrary byte sequences, and it also provides a simple LSB extraction routine for bitmap images.

How do I install stego-lsb?

It is on PyPI as stego-lsb and installs with pip install stego-lsb, or you can clone the repository and run python3 setup.py install, and afterwards use the stegolsb command or import from stego_lsb.

Why does recovering data from a WAV file need the byte count?

The README states that recovering requires the size in bytes of the hidden data to be accurate, otherwise the result may be too short or contain extraneous data, whereas the image tool hides the file size in the first few colour channels.

What does the analyze option in the stegolsb image tool tell me?

It prints the image resolution, how many bytes can be hidden at the chosen LSB depth, the size of the input file, and the size of the file size tag, so you can check capacity before embedding.

Official sources

  1. Issues
  2. License: MIT
  3. ragibson/Steganography on GitHub
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/ragibson-steganography.svg)](https://hysenlabs.com/projects/ragibson-steganography)

Was this useful?