Stegano: hiding messages in images, in pure Python
A pure Python steganography module.
At a glance
- What is it?
- Stegano is a steganography module with a deliberately narrow promise: it hides messages, and it does not encrypt them. The library offers a least significant bit technique, a reversible variant that restores the original image exactly, a scatterer built on the sieve of Eratosthenes, and a pair of steganalysis tools, all reachable from a command line or as a Python import.
- Who is it for?
- Stegano fits a teaching context, a researcher who needs to understand or break a specific hiding technique, and a developer who needs a message embedded in an image for a legitimate watermark or channel. It does not fit anybody who thinks a hidden message is a secret one, because the readme is clear that the hiding provides obscurity rather than confidentiality and that encryption is a separate layer.
- Can I use it commercially?
- Yes, with conditions. GPL-3.0 is a copyleft licence: if you distribute software that includes it, you must release that software's source code under the same licence. Running it internally without distributing it does not trigger that obligation.
- Is it still maintained?
- Yes. The repository last received commits 16 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 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
What the module does and does not do
Stegano is described as a pure Python steganography module, and the second paragraph of the readme is the most important thing in it.
Steganography is the art and science of writing hidden messages in such a way that nobody but the sender and the intended recipient suspects the message exists. And then the consequence, stated in the same paragraph: the functions the library provides only hide messages, without encryption. Steganography is often used with cryptography.
That sentence is a design boundary and a warning at the same time, and it is the kind of thing a library says when the author has been asked about it more than once. The distinction is real. A hidden message is concealed, not protected: anyone who knows the technique and has the image can extract the payload, and a library that hid that fact would be doing its users a disservice. Putting the encryption layer somewhere else, where it is chosen deliberately, is the responsible arrangement.
So the module is a set of hiding techniques with a clean interface, and the security property it offers is obscurity rather than confidentiality.
The practical shape is small. The package exposes the techniques as submodules, so you import the one you want, and each has a hide operation that returns an image and a reveal operation that takes an image and returns the message. There is also a reversible variant, which the readme introduces with the term for the property it provides: the original cover can be restored exactly after the message is extracted.
That reversibility is the feature that distinguishes this library from most, and it is worth understanding what it buys. A normal hiding technique changes the cover irreversibly, so a second message needs a second cover, and the evidence of a first message stays in the file forever. A reversible technique changes the cover in a way that can be undone, so the same image can carry a sequence of messages and end up byte for byte as it started.
The project is GPL 3 licensed, the documentation is published, the last push was on 2026-10-07, and there are three releases in 2026 including a major one.
The least significant bit technique, from both sides
The library's headline technique is the least significant bit one, and the library example shows the whole cycle in four lines.
>>> from stegano import lsb
>>> secret = lsb.hide("./tests/sample-files/Lenna.png", "Hello World")
>>> secret.save("./Lenna-secret.png")
>>>
>>> clear_message = lsb.reveal("./Lenna-secret.png")The shape is worth noting. The hide operation takes a path and a message and returns an image object, which you then save wherever you want. The reveal operation takes a path and returns the message. The file format and the file naming are the caller's business, which is a good design for a technique library, because the same hiding code has to work for a file on disk, an image already in memory, and an image being uploaded.
And the technique itself is the classic one. The least significant bit of each colour channel is adjusted to carry a bit of the message, and the message is spread across the pixels in a defined order. A human looking at the result sees a slightly noisier picture, and a program looking at it can read the bits back out.
The vulnerability of the technique is well known and is the reason the readme's later sections exist. Because the pixels are changed in a predictable order, a statistical detector can look for the tell-tale pattern of a least significant bit encoding, and the number of changed pixels relative to the message length is itself a signal.
The readme's answer to that is the next feature, and it is an elegant one.
The message can be scattered through the picture according to a set described by the sieve of Eratosthenes, and other sets are available, and you can supply your own generators. The readme's comment on the effect is precise: this will make a steganalysis more complicated.
That is the right framing. The sieve is a well known sequence with no obvious structure, so the positions that carry the message stop looking like a regular pattern, and the simple statistical test stops working as well. It is not security, and the readme does not claim it is, but it is the kind of improvement that raises the bar a little for an attacker who has only implemented the naive detector.
The reversible technique, and what it costs
The second technique is the one the readme calls reversible, and the example shows the three operations.
>>> from stegano import rdh
>>> secret = rdh.hide("./tests/sample-files/Lenna-grayscale.png", "Hello World")
>>> secret.save("./Lenna-secret.png")
>>>
>>> clear_message = rdh.reveal("./Lenna-secret.png")
>>> cover = rdh.recover("./Lenna-secret.png") # restores the original imageSo the same file can be revealed and recovered, and the recovered cover is identical to the original. That is a different contract from the least significant bit technique, and it is the more interesting one.
The mechanism is histogram shifting. Rather than adjusting individual pixels, the technique adjusts the distribution of pixel values in a way that encodes the message, and because the adjustment is structured rather than per-pixel, it can be undone by shifting back. The cost of that structure is that the technique is much more sensitive to the content of the image, and the readme's example uses a grayscale file, which is a strong hint about the constraint.
The consequence for a user is that reversibility comes with a narrower set of cover images. The readme's own example chooses a grayscale sample for this technique and a colour one for the other, and if you follow that pattern you will find that the reversible technique is the one that fails first on a photograph with lots of colour variety.
There is also a third technique in the package, exposed under a three letter abbreviation, and the package manifest lists five command line entry points: one for the least significant bit technique, one for the reversible one, one for the third technique, and two for the analysis tools, one for parity based detection and one for statistics.
So the package is not just a hiding library. It ships the tools you need to check your own work, and the naming makes that clear: hiding and detection are the same kind of thing here.
The command line, one tool per technique
The command line interface is worth a paragraph on its own, because it is what makes the library usable by somebody who does not write Python.
Each technique gets its own executable, named after the technique, and the installation section explains why there is a separate route for the command line tools: if you only want the tool, you install it with the tool that installs scripts from Python packages into separate virtual environments, so that they are available system wide but shielded from your system and from each other.
The hide and reveal cycle is a pair of commands with a file, a message, and an output file.
$ stegano-lsb hide -i ./tests/sample-files/Lenna.png -m "Secret Message" -o Lena1.png
$ stegano-lsb reveal -i Lena1.png
Secret MessageThe reversible variant adds the recover step, and the comment in the command is the contract.
$ stegano-rdh hide -i ./tests/sample-files/Lenna-grayscale.png -m "Secret Message" -o stego.png
$ stegano-rdh reveal -i stego.png
Secret Message
$ stegano-rdh recover -i stego.png -o cover.png # cover.png == the original imageAnd the sieve option is a flag on the hide command, with the message supplied in single quotes because it is likely to contain characters a shell would interpret.
$ stegano-lsb hide -i ./tests/sample-files/Lenna.png -m 'Secret Message' --generator eratosthenes -o Lena2.pngSo the difference between the three documented ways of hiding a message is entirely in the flags, and the difference between the libraries and the tools is entirely in whether you import a module or run a command. That is a good structure for a library with several techniques, because the user does not have to learn a different invocation for each one.
The command line tools are also the reason the package declares a console environment classifier in its metadata, along with the intended audiences of developers and of scientific research, which is a fair description of who ends up here.
Steganalysis, and the two detection tools
The package manifest lists two analysis entry points, one for parity based detection and one for statistics, and the readme's title for the project lists steganalysis as one of the topics, so detection is a first class part of the project rather than an afterthought.
That matters, because the most common question about a steganography library is the one a sceptic asks: can this be detected? A library that only hides gives you no way to find out, and a library that ships detectors lets you check your own output with the same tools an attacker would use.
The parity based tool is the classic statistical test, and shipping it inside the same package as the hiding code is a deliberate choice about honesty. The statistics tool gives you the numbers behind that judgement, so you can see not only whether a file looks like it carries a least significant bit payload but how strongly it looks like it.
The name of the second tool is the one to note: the analysis entry point is called parity, which is the term for the technique the detector looks for. Naming the detection after the thing it detects, rather than after the file format or the confidence level, is the clearest possible statement that the project knows what it is doing and is not hiding from the counter-analysis.
So the picture the project presents is complete: a set of hiding techniques, a way to scatter the payload, a reversible technique with its recovery step, a detector, and a statistics tool. The author of a steganography library who does not include the detector is either inexperienced or has something to hide, and this one has included it for years, since the copyright notice on the code goes back to 2010.
That is also the explanation for the age of the project. The core technique and the detector have been in place for more than a decade, and the recent work has been about the packaging: a modern build backend, a current release, and the addition of the reversible technique with its own command line tool.
The package, the dependencies, and how it is maintained
The manifest is short and modern, and it says what the library is made of.
The build uses the packaging library's own backend at version two, and the metadata declares the project as a pure Python steganography module, licensed under the GPL version three or later, and requiring Python 3.10 or later but below 4.0.
The four runtime dependencies are the interesting part. The imaging library is the obvious one, and the metadata and the image library are the two that do the actual work. The console colouring library is there for the command line tools, which is a small dependency that tells you the tools are meant to be readable rather than machine parsed. And the computer vision library is the one that is not obvious, and it is there because the detection tools need to analyse images statistically.
That is a library with a computer vision dependency, and the reason is the analysis half of the project. A hiding library could be written with nothing but the imaging library; this one needs to load an image and reason about its pixel statistics, and that is a different kind of computation.
The project is configured for the packaging tool's newer generation, and the release process is tied to it. The development dependencies are the static type checker and the linter, and the readme tells you how to run both: the tests are discovered with the standard library test runner, and the type checker is run over the package directory.
The readme's contribution note is short and practical: contributions are welcome, and if you want to contribute it is highly recommended to install it in a virtual environment with the packaging tool. That is the same advice the installation section gives for users, and it is the advice that keeps a project's development environment reproducible for everybody who contributes to it.
The repository layout matches a small, well organised library. There is the package directory, the tests, the documentation with its own configuration, a change log, a contributors file, the licence text, an editor configuration, a pre-commit configuration, and a lock file for the packaging tool. And there is a benchmark script at the root, which is a file that is not part of the package and not part of the tests, and which exists because the author wanted to know how the techniques perform rather than assume it.
That last file is the one worth mentioning as a practice, because a steganography library that runs benchmarks will tell you how many images a technique can process per second, and that number is what determines whether the technique is usable on a real workload.
Editorial conclusion
Stegano fits a teaching context, a researcher who needs to understand or break a specific hiding technique, and a developer who needs a message embedded in an image for a legitimate watermark or channel. It does not fit anybody who thinks a hidden message is a secret one, because the readme is clear that the hiding provides obscurity rather than confidentiality and that encryption is a separate layer. Check first which technique fits the data you are embedding, since the readme's example for the reversible one uses a grayscale image, and run the parity and statistics tools against your own output.
Frequently asked questions
What does the Stegano library do?
It hides messages in images using several techniques, and it does not encrypt them. The readme is explicit that the functions only hide messages, that this is a form of security through obscurity, and that steganography is often used with cryptography as a separate layer.
What is the difference between the least significant bit and the reversible techniques?
The least significant bit technique adjusts the low bit of colour channels to carry the message and cannot be undone. The reversible technique, based on shifting the histogram, lets you recover the original cover exactly after extracting the message, but it is the more sensitive of the two and the readme's own example uses a grayscale image with it.
How do I scatter a Stegano message so it is harder to detect?
Pass the generator option to the hide command, for example the sieve of Eratosthenes, and the message is scattered through the picture following that set instead of a regular order. Other sets are available and you can supply your own generator, and the readme notes that this makes steganalysis more complicated.
Does Stegano include tools for detecting hidden messages?
Yes. The package ships two analysis entry points, one for parity based detection and one for statistics, and they are exposed as command line tools in the same way as the hiding techniques. That lets you check your own output with the same tests an attacker would use.
How do I install Stegano for command line use only?
With the tool that installs scripts from Python packages into separate virtual environments, so the commands are available system wide while being shielded from your system and from each other. The full library install is done with the packaging tool, and the readme recommends a virtual environment for contributors too.
Official sources
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.
[](https://hysenlabs.com/projects/cedricbonhomme-stegano)Was this useful?