Compare commits

..

No commits in common. "master" and "v0.11.1" have entirely different histories.

39 changed files with 904 additions and 1224 deletions

42
.builds/debian.yml Normal file
View file

@ -0,0 +1,42 @@
image: debian/bullseye
sources:
- https://git.sr.ht/~cedric/stegano
packages:
- python3
- python3-dev
- python3-pip
- python3-setuptools
- python3-venv
- rustc
- cargo
- libffi-dev
- libssl-dev
- libjpeg-dev
- libpng-dev
- libfreetype6-dev
- ffmpeg
- libsm6
- libxext6
environment:
project: stegano
tasks:
- dependencies: |
pip3 install --user poetry
export PATH="$PATH:/home/build/.local/bin"
cd ${project}
poetry install
- lint: |
export PATH="$PATH:/home/build/.local/bin"
cd ${project}
# stop the build if there are Python syntax errors or undefined names
poetry run flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
# exit-zero treats all errors as warnings.
poetry run flake8 . --count --max-complexity=10 --max-line-length=127 --statistics
- test: |
export PATH="$PATH:/home/build/.local/bin"
cd ${project}
poetry run nose2 -v --pretty-assert
- typecheck: |
export PATH="$PATH:/home/build/.local/bin"
cd ${project}
poetry run mypy .

View file

@ -9,7 +9,7 @@ jobs:
strategy: strategy:
matrix: matrix:
python-version: ["3.10", "3.11", "3.12"] python-version: [3.8, 3.9, 3.10.2]
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@v1
@ -22,14 +22,14 @@ jobs:
- name: Install dependencies - name: Install dependencies
run: | run: |
pip install poetry pip install poetry
poetry install --with dev poetry install
- name: Lint with flake8 - name: Lint with flake8
run: | run: |
# stop the build if there are Python syntax errors or undefined names # stop the build if there are Python syntax errors or undefined names
poetry run flake8 stegano --count --select=E9,F63,F7,F82 --show-source --statistics poetry run flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
poetry run flake8 stegano --count --max-complexity=15 --max-line-length=127 --statistics poetry run flake8 . --count --max-complexity=15 --max-line-length=127 --statistics
- name: Test with pytest - name: Test with pytest
run: | run: |
@ -37,6 +37,6 @@ jobs:
env: env:
testing: actions testing: actions
# - name: Type check with mypy - name: Type check with mypy
# run: | run: |
# poetry run mypy . poetry run mypy .

View file

@ -1,27 +0,0 @@
on:
release:
types:
- published
name: release
jobs:
pypi-publish:
name: Upload release to PyPI
runs-on: ubuntu-latest
environment:
name: pypi
url: https://pypi.org/p/Stegano
permissions:
id-token: write # IMPORTANT: this permission is mandatory for trusted publishing
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install Poetry
run: python -m pip install --upgrade pip poetry
- name: Build artifacts
run: poetry build
- name: Publish package distributions to PyPI
uses: pypa/gh-action-pypi-publish@release/v1

View file

@ -1,36 +0,0 @@
ci:
autoupdate_schedule: monthly
repos:
- repo: https://github.com/asottile/pyupgrade
rev: v3.3.1
hooks:
- id: pyupgrade
args: ["--py37-plus"]
- repo: https://github.com/PyCQA/isort
rev: 5.12.0
hooks:
- id: isort
- repo: https://github.com/psf/black
rev: 22.3.0
hooks:
- id: black
- repo: https://github.com/PyCQA/flake8
rev: 6.1.0
hooks:
- id: flake8
additional_dependencies:
- flake8-bugbear
- flake8-implicit-str-concat
args: ["--max-line-length=125"]
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.1.0
hooks:
- id: fix-byte-order-marker
- id: trailing-whitespace
exclude: .md
- id: end-of-file-fixer
exclude: tests/.*
- repo: https://github.com/pypa/pip-audit
rev: v2.7.3
hooks:
- id: pip-audit

View file

@ -1,22 +0,0 @@
# .readthedocs.yaml
# Read the Docs configuration file
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
# Required
version: 2
# Set the version of Python and other tools you might need
build:
os: ubuntu-22.04
tools:
python: "3.11"
# Build documentation in the docs/ directory with Sphinx
sphinx:
configuration: docs/conf.py
# We recommend specifying your dependencies to enable reproducible builds:
# https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html
python:
install:
- requirements: docs/requirements.txt

View file

@ -1,251 +1,216 @@
## Release History ## Release History
### 1.0.1 (2025-05-03)
- Improved the packaging configuration for the command line (stegano.console).
### 1.0.0 (2025-04-26)
- Updated dependencies.
- Improved the packaging configuration.
- Fixed typing errors.
### 0.11.5 (2025-02-13)
- Updated dependencies.
- Aligned pyproject.toml with the standard specification.
- Publishing to PyPI using a Trusted Publisher.
### 0.11.4 (2024-09-07)
- Added a parameter, close_file, to lsb.reveal in order to
specify if the file must be closed at the end of the processing.
### 0.11.3 (2024-01-02)
- Stegano now supports Python 3.12. Support of Python 3.8 has been removed.
### 0.11.2 (2023-05-23)
- improved typing of various functions;
- updated dependencies.
### 0.11.1 (2022-11-20) ### 0.11.1 (2022-11-20)
- Fixed a bug in the command line when no sub-command is specified. * Fixed a bug in the command line when no sub-command is specified.
### 0.11.0 (2022-11-20) ### 0.11.0 (2022-11-20)
- Reduced memory footprint and processing speed, * Reduced memory footprint and processing speed,
the modules ``lsb`` and ``lsbset`` have been merged the modules ``lsb`` and ``lsbset`` have been merged
([PR #34](https://github.com/cedricbonhomme/Stegano/pull/34)). ([PR #34](https://github.com/cedricbonhomme/Stegano/pull/34)).
### 0.10.2 (2022-01-13) ### 0.10.2 (2022-01-13)
- Stegano now uses Pillow 9.0.0 (CVE-2022-22815). * Stegano now uses Pillow 9.0.0 (CVE-2022-22815).
### 0.10.1 (2021-11-30) ### 0.10.1 (2021-11-30)
- Stegano now uses OpenCV Python 4.5.4 abd Numpy 1.21.4. * Stegano now uses OpenCV Python 4.5.4 abd Numpy 1.21.4.
### 0.10.0 (2021-11-29) ### 0.10.0 (2021-11-29)
- new: Implemented Shi-Tomashi corner generator * new: Implemented Shi-Tomashi corner generator
([PR #32](https://github.com/cedricbonhomme/Stegano/pull/32)). ([PR #32](https://github.com/cedricbonhomme/Stegano/pull/32)).
Implemented by thundersparkf (see CONTRIBUTORS.md file). Implemented by thundersparkf (see CONTRIBUTORS.md file).
### 0.9.9 (2021-07-02) ### 0.9.9 (2021-07-02)
- Stegano now uses Pillow 8.3.0. * Stegano now uses Pillow 8.3.0.
### 0.9.8 (2019-12-20) ### 0.9.8 (2019-12-20)
- Stegano is now using poetry; * Stegano is now using poetry;
- minor improvements to the command line. * minor improvements to the command line.
### 0.9.7 (2019-10-27) ### 0.9.7 (2019-10-27)
- fixed markdown of the previous release. * fixed markdown of the previous release.
### 0.9.6 (2019-10-27) ### 0.9.6 (2019-10-27)
- fixed markdown of the previous release; * fixed markdown of the previous release;
### 0.9.5 (2019-10-27) ### 0.9.5 (2019-10-27)
- updated dependencies; * updated dependencies;
- home page of the project is now: https://github.com/cedricbonhomme/Stegano * home page of the project is now: https://git.sr.ht/~cedric/Stegano
### 0.9.4 (2019-06-05) ### 0.9.4 (2019-06-05)
- new: Implemented LFSR generator (with tests and CLI) * new: Implemented LFSR generator (with tests and CLI)
([PR #27](https://github.com/cedricbonhomme/Stegano/pull/27)) ([PR #27](https://github.com/cedricbonhomme/Stegano/pull/27))
- new: Implemented Ackermann generators CLI interface * new: Implemented Ackermann generators CLI interface
([PR #26](https://github.com/cedricbonhomme/Stegano/pull/26)) ([PR #26](https://github.com/cedricbonhomme/Stegano/pull/26))
- new: The Ackermann functions are not actual generators * new: The Ackermann functions are not actual generators
([#24](https://github.com/cedricbonhomme/Stegano/issues/24)) ([#24](https://github.com/cedricbonhomme/Stegano/issues/24))
- new: add a shift parameter for the lsbmodule * new: add a shift parameter for the lsbmodule
([#25](https://github.com/cedricbonhomme/Stegano/issues/25)) ([#25](https://github.com/cedricbonhomme/Stegano/issues/25))
- fix: lsbset.hide cause .png transparent area lost * fix: lsbset.hide cause .png transparent area lost
([#23](https://github.com/cedricbonhomme/Stegano/issues/23)) ([#23](https://github.com/cedricbonhomme/Stegano/issues/23))
### 0.9.3 (2019-04-10) ### 0.9.3 (2019-04-10)
- it is now possible to either pass the location of an image or directly pass * it is now possible to either pass the location of an image or directly pass
an already opened Image.Image to the hide and reveal methods; an already opened Image.Image to the hide and reveal methods;
- code re-formatted a bit with black. * code re-formatted a bit with black.
### 0.9.2 (2019-04-04) ### 0.9.2 (2019-04-04)
- updated Pillow dependency to version 6.0.0 in order to fix a bug when opening * updated Pillow dependency to version 6.0.0 in order to fix a bug when opening
some PNG files (https://github.com/python-pillow/Pillow/issues/3557). some PNG files (https://github.com/python-pillow/Pillow/issues/3557).
### 0.9.1 (2019-03-06) ### 0.9.1 (2019-03-06)
- updated Pillow dependency in order to fix a bug when opening some PNG files. * updated Pillow dependency in order to fix a bug when opening some PNG files.
### 0.9.0 (2018-12-18) ### 0.9.0 (2018-12-18)
- added the possibility to shift the encoded bits when using the lsbset module. * added the possibility to shift the encoded bits when using the lsbset module.
### 0.8.6 (2018-11-05) ### 0.8.6 (2018-11-05)
- fixed a potential security issue related to CVE-2018-18074. * fixed a potential security issue related to CVE-2018-18074.
### 0.8.5 (2018-04-18) ### 0.8.5 (2018-04-18)
- Fixed an encoding problem which occured on Windows during the installation * Fixed an encoding problem which occured on Windows during the installation
of the module. of the module.
### 0.8.4 (2018-02-28) ### 0.8.4 (2018-02-28)
- Stegano is ready for use with pipenv and pipsi. * Stegano is ready for use with pipenv and pipsi.
### 0.8.3 (2018-02-23) ### 0.8.3 (2018-02-23)
- the recommended way to install Stegano is now to use pipenv. * the recommended way to install Stegano is now to use pipenv.
### 0.8.2 (2017-12-20) ### 0.8.2 (2017-12-20)
- Fixed a bug with the new 'encoding' function when using Stegano as a command * Fixed a bug with the new 'encoding' function when using Stegano as a command
line tool. No default value was set. Default value is UTF-8. line tool. No default value was set. Default value is UTF-8.
### 0.8.1 (2017-05-16) ### 0.8.1 (2017-05-16)
- it is now possible to specify the encoding (UTF-8 or UTF-32LE) of the message * it is now possible to specify the encoding (UTF-8 or UTF-32LE) of the message
to hide/reveal through the command line; to hide/reveal through the command line;
- the help of the command line now displays the available choices for the * the help of the command line now displays the available choices for the
arguments, if it is necessary (list of available encodings, list of available arguments, if it is necessary (list of available encodings, list of available
generators); generators);
- tests expected results lies now in a dedicated folder; * tests expected results lies now in a dedicated folder;
- a script has been added in order to get proper exit code check for mypy. * a script has been added in order to get proper exit code check for mypy.
### 0.8 (2017-05-06) ### 0.8 (2017-05-06)
- updated command line. All commands are now prefixed with *stegano-*; * updated command line. All commands are now prefixed with *stegano-*;
- improved type hints; * improved type hints;
- it is possible to load and save images from and to file objects (BytesIO); * it is possible to load and save images from and to file objects (BytesIO);
- improved checks when revealing a message with the lsbset module fails. * improved checks when revealing a message with the lsbset module fails.
### 0.7.1 (2017-05-05) ### 0.7.1 (2017-05-05)
- improved generators for the lsb-set module; * improved generators for the lsb-set module;
- improved tests for the generators; * improved tests for the generators;
- improved type hints. * improved type hints.
### 0.7 (2017-05-04) ### 0.7 (2017-05-04)
- unicode is now supported. By default UTF-8 encoding is used. UTF-32LE can also * unicode is now supported. By default UTF-8 encoding is used. UTF-32LE can also
be used to hide non-ASCII characters. UTF-8 (8 bits) is the default choice be used to hide non-ASCII characters. UTF-8 (8 bits) is the default choice
since it is possible to hide longer messages with it. since it is possible to hide longer messages with it.
- improved checks with type hints. * improved checks with type hints.
### 0.6.9 (2017-03-10) ### 0.6.9 (2017-03-10)
- introduces some type hints (PEP 484); * introduces some type hints (PEP 484);
- more tests for the generators and for the tools module; * more tests for the generators and for the tools module;
- updated descriptions of generators; * updated descriptions of generators;
- fixed a bug with a generator that has been previously renamed. * fixed a bug with a generator that has been previously renamed.
### 0.6.8 (2017-03-08) ### 0.6.8 (2017-03-08)
- bugfix: fixed #12: Error when revealing a hidden binary file in an image. * bugfix: fixed #12: Error when revealing a hidden binary file in an image.
### 0.6.7 (2017-02-21) ### 0.6.7 (2017-02-21)
- bugfix: added missing dependency in the setup.py file. * bugfix: added missing dependency in the setup.py file.
### 0.6.6 (2017-02-20) ### 0.6.6 (2017-02-20)
- improved docstrings for the desciption of the generators; * improved docstrings for the desciption of the generators;
- improved the command which displays the list of generators. * improved the command which displays the list of generators.
### 0.6.5 (2017-02-16) ### 0.6.5 (2017-02-16)
- added a command to list all available generators for the lsb-set module; * added a command to list all available generators for the lsb-set module;
- test when the data image is coming via byte stream, for the lsb module. * test when the data image is coming via byte stream, for the lsb module.
### 0.6.4 (2017-02-06) ### 0.6.4 (2017-02-06)
- a command line for the 'red' module has been added; * a command line for the 'red' module has been added;
- bugfix: fixed a bug in the lsb-set command line when the generator wasn't * bugfix: fixed a bug in the lsb-set command line when the generator wasn't
specified by the user. specified by the user.
### 0.6.3 (2017-01-29) ### 0.6.3 (2017-01-29)
- Support for transparent PNG images has been added (lsb and lsbset modules). * Support for transparent PNG images has been added (lsb and lsbset modules).
### 0.6.2 (2017-01-19) ### 0.6.2 (2017-01-19)
- bugfix: solved a bug when the image data is coming via byte streams (ByteIO), * bugfix: solved a bug when the image data is coming via byte streams (ByteIO),
for the exifHeader hiding method. for the exifHeader hiding method.
### 0.6.1 (2016-08-25) ### 0.6.1 (2016-08-25)
- reorganization of the steganalysis sub-module. * reorganization of the steganalysis sub-module.
### 0.6 (2016-08-04) ### 0.6 (2016-08-04)
- improvements of the command line of Stéganô. The use of Stéganô through the * improvements of the command line of Stéganô. The use of Stéganô through the
command line has slightly changed ('hide' and 'reveal' are now sub-parameters command line has slightly changed ('hide' and 'reveal' are now sub-parameters
of the command line). No changes if you use Stéganô as a module in your of the command line). No changes if you use Stéganô as a module in your
software. The documentation has been updated accordingly. software. The documentation has been updated accordingly.
@ -253,66 +218,66 @@
### 0.5.5 (2016-08-03) ### 0.5.5 (2016-08-03)
- bugfix: Incorrect padding size in `base642string` in tools.base642binary(). * bugfix: Incorrect padding size in `base642string` in tools.base642binary().
### 0.5.4 (2016-05-22) ### 0.5.4 (2016-05-22)
- the generator provided to the functions lsbset.hide() and lsbset.reveal() is * the generator provided to the functions lsbset.hide() and lsbset.reveal() is
now a function. This is more convenient for a user who wants to use a custom now a function. This is more convenient for a user who wants to use a custom
generator (not in the module lsbset.generators). generator (not in the module lsbset.generators).
- performance improvements for the lsb and lsbset modules. * performance improvements for the lsb and lsbset modules.
### 0.5.3 (2016-05-19) ### 0.5.3 (2016-05-19)
- reorganization of all modules. No impact for the users of Stegano. * reorganization of all modules. No impact for the users of Stegano.
### 0.5.2 (2016-05-18) ### 0.5.2 (2016-05-18)
- improvements and bug fixes for the exifHeader module; * improvements and bug fixes for the exifHeader module;
- added unit tests for the exifHeader module; * added unit tests for the exifHeader module;
- improvements of the documentation. * improvements of the documentation.
### 0.5.1 (2016-04-16) ### 0.5.1 (2016-04-16)
- minor improvements and bug fixes; * minor improvements and bug fixes;
- added unit tests for the slsb and slsbset modules. * added unit tests for the slsb and slsbset modules.
### 0.5 (2016-03-18) ### 0.5 (2016-03-18)
- management of greyscale images. * management of greyscale images.
### 0.4.6 (2016-03-12) ### 0.4.6 (2016-03-12)
- bugfix when the length of the message to hide is not divisible by 3, * bugfix when the length of the message to hide is not divisible by 3,
for the slsb and slsbset module. for the slsb and slsbset module.
### 0.4.5 (2015-12-23) ### 0.4.5 (2015-12-23)
- bugfix. * bugfix.
### 0.4.4 (2015-12-23) ### 0.4.4 (2015-12-23)
- new project home page; * new project home page;
- minor updated to the documentation. * minor updated to the documentation.
### 0.4.3 (2015-10-06) ### 0.4.3 (2015-10-06)
- bug fixes for Python 3; * bug fixes for Python 3;
- bug fixes in the scripts in *./bin*. * bug fixes in the scripts in *./bin*.
### 0.4.2 (2015-10-05) ### 0.4.2 (2015-10-05)
- first stable release on PypI. * first stable release on PypI.
### 0.4 (2012-01-02) ### 0.4 (2012-01-02)
@ -326,16 +291,16 @@ Python codes as a Python module or as a program in your scripts.
### 0.3 (2011-04-15) ### 0.3 (2011-04-15)
- you can now use Stéganô as a library in your Python program; * you can now use Stéganô as a library in your Python program;
(python setup.py install) or as a 'program' thanks to the scripts provided (python setup.py install) or as a 'program' thanks to the scripts provided
in the bin directory; in the bin directory;
- new documentation (reStructuredText) comes with Stéganô. * new documentation (reStructuredText) comes with Stéganô.
### 0.2 (2011-03-24) ### 0.2 (2011-03-24)
- this release introduces some bugfixes and a major speed improvement of the * this release introduces some bugfixes and a major speed improvement of the
*reveal* function for the LSB method. Moreover it is now possible to hide a *reveal* function for the LSB method. Moreover it is now possible to hide a
binary file (ogg, executable, etc.); binary file (ogg, executable, etc.);
- a new technique for hiding/revealing a message in a JPEG picture by using the * a new technique for hiding/revealing a message in a JPEG picture by using the
description field of the image is provided. description field of the image is provided.

View file

@ -16,6 +16,5 @@
- panni <panni@fragstore.net> - panni <panni@fragstore.net>
- Peter Justin <peter@peterjustin.me> - Peter Justin <peter@peterjustin.me>
- thundersparkf - https://github.com/thundersparkf - thundersparkf - https://github.com/thundersparkf
- Mickaël Schoentgen <mschoentgen@nuxeo.com>
And thank you to the testers! And thank you to the testers!

View file

@ -671,4 +671,4 @@ into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read Public License instead of this License. But first, please read
<http://www.gnu.org/philosophy/why-not-lgpl.html>. <http://www.gnu.org/philosophy/why-not-lgpl.html>.

View file

@ -1,8 +1,9 @@
# Stegano # Stegano
[![builds.sr.ht status](https://builds.sr.ht/~cedric/stegano.svg)](https://builds.sr.ht/~cedric/stegano)
[![Workflow](https://github.com/cedricbonhomme/Stegano/workflows/Python%20application/badge.svg?style=flat-square)](https://github.com/cedricbonhomme/Stegano/actions?query=workflow%3A%22Python+application%22) [![Workflow](https://github.com/cedricbonhomme/Stegano/workflows/Python%20application/badge.svg?style=flat-square)](https://github.com/cedricbonhomme/Stegano/actions?query=workflow%3A%22Python+application%22)
[Stegano](https://github.com/cedricbonhomme/Stegano), a pure Python Steganography [Stegano](https://sr.ht/~cedric/stegano), a pure Python Steganography
module. module.
Steganography is the art and science of writing hidden messages in such a way Steganography is the art and science of writing hidden messages in such a way
@ -11,6 +12,9 @@ existence of the message, a form of security through obscurity. Consequently,
functions provided by Stegano only hide messages, without encryption. functions provided by Stegano only hide messages, without encryption.
Steganography is often used with cryptography. Steganography is often used with cryptography.
For reporting issues, visit the tracker here:
https://todo.sr.ht/~cedric/stegano
## Installation ## Installation
@ -93,23 +97,11 @@ Contributions are welcome. If you want to contribute to Stegano I highly
recommend you to install it in a Python virtual environment with poetry. recommend you to install it in a Python virtual environment with poetry.
## Donations
If you wish and if you like Stegano, you can donate via GitHub Sponsors:
[![GitHub Sponsors](https://img.shields.io/github/sponsors/cedricbonhomme)](https://github.com/sponsors/cedricbonhomme)
or with Bitcoin to this address:
bc1q56u6sj7cvlwu58v5lemljcvkh7v2gc3tv8mj0e
Thank you !
## License ## License
This software is licensed under This software is licensed under
[GNU General Public License version 3](https://www.gnu.org/licenses/gpl-3.0.html) [GNU General Public License version 3](https://www.gnu.org/licenses/gpl-3.0.html)
Copyright (C) 2010-2025 [Cédric Bonhomme](https://www.cedricbonhomme.org) Copyright (C) 2010-2022 [Cédric Bonhomme](https://www.cedricbonhomme.org)
For more information, [the list of authors and contributors](CONTRIBUTORS.md) is available. For more information, [the list of authors and contributors](CONTRIBUTORS.md) is available.

View file

@ -1,6 +1,8 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*-
# Stegano - Stegano is a pure Python steganography module. # Stegano - Stegano is a pure Python steganography module.
# Copyright (C) 2010-2025 Cédric Bonhomme - https://www.cedricbonhomme.org # Copyright (C) 2010-2022 Cédric Bonhomme - https://www.cedricbonhomme.org
# #
# For more information : https://github.com/cedricbonhomme/Stegano # For more information : https://github.com/cedricbonhomme/Stegano
# #
@ -24,7 +26,6 @@ __revision__ = "$Date: 2019/06/04 $"
__license__ = "GPLv3" __license__ = "GPLv3"
import inspect import inspect
import crayons import crayons
try: try:
@ -33,10 +34,10 @@ try:
except Exception: except Exception:
print("Install stegano: pipx install Stegano") print("Install stegano: pipx install Stegano")
import argparse
from stegano import tools from stegano import tools
import argparse
class ValidateGenerator(argparse.Action): class ValidateGenerator(argparse.Action):
def __call__(self, parser, args, values, option_string=None): def __call__(self, parser, args, values, option_string=None):
@ -75,7 +76,7 @@ def main():
choices=tools.ENCODINGS.keys(), choices=tools.ENCODINGS.keys(),
default="UTF-8", default="UTF-8",
help="Specify the encoding of the message to hide." help="Specify the encoding of the message to hide."
" UTF-8 (default) or UTF-32LE.", + " UTF-8 (default) or UTF-32LE.",
) )
# Generator # Generator
@ -130,7 +131,7 @@ def main():
choices=tools.ENCODINGS.keys(), choices=tools.ENCODINGS.keys(),
default="UTF-8", default="UTF-8",
help="Specify the encoding of the message to reveal." help="Specify the encoding of the message to reveal."
" UTF-8 (default) or UTF-32LE.", + " UTF-8 (default) or UTF-32LE.",
) )
# Generator # Generator
@ -178,7 +179,7 @@ def main():
generator = getattr(generators, arguments.generator_function[0])() generator = getattr(generators, arguments.generator_function[0])()
except AttributeError: except AttributeError:
print(f"Unknown generator: {arguments.generator_function}") print("Unknown generator: {}".format(arguments.generator_function))
exit(1) exit(1)
if arguments.command == "hide": if arguments.command == "hide":
@ -222,6 +223,6 @@ def main():
all_generators = inspect.getmembers(generators, inspect.isfunction) all_generators = inspect.getmembers(generators, inspect.isfunction)
for generator in all_generators: for generator in all_generators:
print("Generator id:") print("Generator id:")
print(f" {crayons.green(generator[0], bold=True)}") print(" {}".format(crayons.green(generator[0], bold=True)))
print("Desciption:") print("Desciption:")
print(f" {generator[1].__doc__}") print(" {}".format(generator[1].__doc__))

View file

@ -1,6 +1,8 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*-
# Stegano - Stegano is a pure Python steganography module. # Stegano - Stegano is a pure Python steganography module.
# Copyright (C) 2010-2025 Cédric Bonhomme - https://www.cedricbonhomme.org # Copyright (C) 2010-2022 Cédric Bonhomme - https://www.cedricbonhomme.org
# #
# For more information : https://github.com/cedricbonhomme/Stegano # For more information : https://github.com/cedricbonhomme/Stegano
# #
@ -22,10 +24,10 @@ __version__ = "$Revision: 0.7 $"
__date__ = "$Date: 2016/08/25 $" __date__ = "$Date: 2016/08/25 $"
__license__ = "GPLv3" __license__ = "GPLv3"
import argparse
from PIL import Image from PIL import Image
import argparse
try: try:
from stegano.steganalysis import parity from stegano.steganalysis import parity
except Exception: except Exception:

View file

@ -1,6 +1,8 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*-
# Stegano - Stegano is a pure Python steganography module. # Stegano - Stegano is a pure Python steganography module.
# Copyright (C) 2010-2025 Cédric Bonhomme - https://www.cedricbonhomme.org # Copyright (C) 2010-2022 Cédric Bonhomme - https://www.cedricbonhomme.org
# #
# For more information : https://github.com/cedricbonhomme/Stegano # For more information : https://github.com/cedricbonhomme/Stegano
# #

View file

@ -1,6 +1,8 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*-
# Stegano - Stegano is a pure Python steganography module. # Stegano - Stegano is a pure Python steganography module.
# Copyright (C) 2010-2025 Cédric Bonhomme - https://www.cedricbonhomme.org # Copyright (C) 2010-2022 Cédric Bonhomme - https://www.cedricbonhomme.org
# #
# For more information : https://github.com/cedricbonhomme/Stegano # For more information : https://github.com/cedricbonhomme/Stegano
# #
@ -23,10 +25,10 @@ __date__ = "$Date: 2016/08/26 $"
__revision__ = "$Date: 2016/08/26 $" __revision__ = "$Date: 2016/08/26 $"
__license__ = "GPLv3" __license__ = "GPLv3"
import argparse
from PIL import Image from PIL import Image
import argparse
try: try:
from stegano.steganalysis import statistics from stegano.steganalysis import statistics
except Exception: except Exception:

View file

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
# #
# Stéganô documentation build configuration file, created by # Stéganô documentation build configuration file, created by
# sphinx-quickstart on Wed Jul 25 13:33:39 2012. # sphinx-quickstart on Wed Jul 25 13:33:39 2012.
@ -9,13 +10,17 @@
# #
# All configuration values have a default; values that are commented out # All configuration values have a default; values that are commented out
# serve to show the default. # serve to show the default.
# If extensions (or modules to document with autodoc) are in another directory, # If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the # add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here. # documentation root, use os.path.abspath to make it absolute, like shown here.
# sys.path.insert(0, os.path.abspath('.')) # sys.path.insert(0, os.path.abspath('.'))
# -- General configuration ----------------------------------------------------- # -- General configuration -----------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here. # If your documentation needs a minimal Sphinx version, state it here.
# needs_sphinx = '1.0' # needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be extensions # Add any Sphinx extension module names here, as strings. They can be extensions
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. # coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = [] extensions = []
@ -34,7 +39,7 @@ master_doc = "index"
# General information about the project. # General information about the project.
project = "Stegano" project = "Stegano"
copyright = "2010-2025, Cédric Bonhomme" copyright = "2010-2022, Cédric Bonhomme"
author = "Cédric Bonhomme <cedric@cedricbonhomme.org>" author = "Cédric Bonhomme <cedric@cedricbonhomme.org>"
# The version info for the project you're documenting, acts as replacement for # The version info for the project you're documenting, acts as replacement for
@ -165,6 +170,6 @@ latex_documents = [
latex_show_urls = True latex_show_urls = True
latex_show_pagerefs = True latex_show_pagerefs = True
ADDITIONAL_PREAMBLE = r""" ADDITIONAL_PREAMBLE = """
\setcounter{tocdepth}{3} \setcounter{tocdepth}{3}
""" """

View file

@ -6,6 +6,10 @@
Presentation Presentation
============ ============
.. image:: https://builds.sr.ht/~cedric/stegano.svg
:target: https://builds.sr.ht/~cedric/stegano
Stegano_ is a pure Python steganography_ module. Stegano_ is a pure Python steganography_ module.
Steganography is the art and science of writing hidden messages in such a way Steganography is the art and science of writing hidden messages in such a way
@ -49,7 +53,7 @@ Tutorial
steganalysis steganalysis
You can have a look at the You can have a look at the
`unit tests <https://github.com/cedricbonhomme/Stegano/tree/master/tests>`_. `unit tests <https://git.sr.ht/~cedric/stegano/tree/master/tests>`_.
License License
@ -73,7 +77,7 @@ Contact
.. _Python: https://www.python.org .. _Python: https://www.python.org
.. _Stegano: https://github.com/cedricbonhomme/Stegano .. _Stegano: https://sr.ht/~cedric/stegano
.. _`Pillow`: https://pypi.python.org/pypi/Pillow .. _`Pillow`: https://pypi.python.org/pypi/Pillow
.. _`piexif`: https://pypi.python.org/pypi/piexif .. _`piexif`: https://pypi.python.org/pypi/piexif
.. _steganography: http://en.wikipedia.org/wiki/Steganography .. _steganography: http://en.wikipedia.org/wiki/Steganography

View file

@ -12,4 +12,7 @@ If you want to retrieve the source code (with the unit tests):
.. code-block:: bash .. code-block:: bash
$ git clone https://github.com/cedricbonhomme/Stegano $ git clone https://git.sr.ht/~cedric/stegano
.. image:: https://builds.sr.ht/~cedric/stegano.svg
:target: https://builds.sr.ht/~cedric/stegano

View file

@ -2,7 +2,7 @@ Using Stegano as a Python module
================================ ================================
You can find more examples in the You can find more examples in the
`unit tests directory <https://github.com/cedricbonhomme/Stegano/tree/master/tests>`_. `unit tests directory <https://git.sr.ht/~cedric/stegano/tree/master/tests>`_.
LSB method LSB method
---------- ----------

1437
poetry.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,79 +1,66 @@
[build-system] [tool.poetry]
requires = ["poetry-core>=2.0"]
build-backend = "poetry.core.masonry.api"
[project]
name = "stegano" name = "stegano"
version = "1.0.1" version = "0.11.1"
description = "A pure Python Steganography module." description = "A pure Python Steganography module."
authors = [ authors = [
{name = "Cédric Bonhomme", email= "cedric@cedricbonhomme.org"} "Cédric Bonhomme <cedric@cedricbonhomme.org>"
] ]
license = "GPL-3.0-or-later" license = "GPL-3.0-or-later"
readme = "README.md" readme = "README.md"
keywords = ["Steganography", "Security", "Stegano"]
dynamic = ["classifiers"] homepage = "https://sr.ht/~cedric/stegano"
repository = "https://git.sr.ht/~cedric/stegano"
documentation = "https://stegano.readthedocs.io"
requires-python = ">=3.10,<4.0" keywords = ["steganography", "security", "stegano"]
dependencies = [
"pillow (>=9.5,<11.0)",
"piexif (>=1.1.3)",
"crayons (>=0.4.0)",
"opencv-python (>=4.8.1.78)"
]
[project.urls]
Homepage = "https://github.com/cedricbonhomme/Stegano"
Changelog = "https://github.com/cedricbonhomme/Stegano/blob/master/CHANGELOG.md"
Repository = "https://github.com/cedricbonhomme/Stegano"
Documentation = "https://stegano.readthedocs.io"
[project.scripts]
stegano-lsb = "stegano.console.lsb:main"
stegano-red = "stegano.console.red:main"
stegano-steganalysis-parity = "stegano.console.parity:main"
stegano-steganalysis-statistics = "stegano.console.statistics:main"
[tool.poetry]
requires-poetry = ">=2.0"
classifiers = [ classifiers = [
"Development Status :: 5 - Production/Stable", "Development Status :: 5 - Production/Stable",
"Environment :: Console", "Environment :: Console",
"Intended Audience :: Developers", "Intended Audience :: Developers",
"Intended Audience :: Science/Research", "Intended Audience :: Science/Research",
"Topic :: Security", "Topic :: Security",
"Operating System :: POSIX :: Linux", "Operating System :: OS Independent",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)" "License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)"
] ]
include = [ include = [
"README.md", "README.md",
"COPYING", "COPYING",
"CHANGELOG.md", "CHANGELOG.md",
"docs/**/*", "docs/**/*",
"bin/*"
] ]
[tool.poetry.scripts]
stegano-lsb = "bin.lsb:main"
stegano-red = "bin.red:main"
stegano-steganalysis-parity = "bin.parity:main"
stegano-steganalysis-statistics = "bin.statistics:main"
[tool.poetry.group.dev.dependencies] [tool.poetry.dependencies]
mypy = "^1.8.0" python = ">=3.8,<3.12"
flake8 = "^6.0.0" pillow = "^9.2.0"
nose2 = "^0.14.0" piexif = "^1.1.3"
Sphinx = "^6.2.1" crayons = "^0.4.0"
pre-commit = "^3.6.0" opencv-python = "^4.6.0.66"
[tool.poetry.dev-dependencies]
mypy = "^0.910"
flake8 = "^4.0.1"
nose2 = "^0.10.0"
Sphinx = "^4.2.0"
[tool.poetry.group.dev] [build-system]
optional = true requires = ["poetry>=0.12"]
build-backend = "poetry.masonry.api"
[tool.mypy] [tool.mypy]
python_version = "3.12" python_version = "3.10"
check_untyped_defs = true check_untyped_defs = true
ignore_errors = false ignore_errors = false
ignore_missing_imports = true ignore_missing_imports = true
@ -88,6 +75,3 @@ show_error_context = true
pretty = true pretty = true
exclude = "build|dist|docs|stegano.egg-info" exclude = "build|dist|docs|stegano.egg-info"
[tool.isort]
profile = "black"

View file

@ -1,5 +1,10 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*-
from . import exifHeader, lsb, red, steganalysis from . import red
from . import exifHeader
from . import lsb
from . import steganalysis
__all__ = ["red", "exifHeader", "lsb", "steganalysis"] __all__ = ["red", "exifHeader", "lsb", "steganalysis"]

View file

@ -1,4 +1,5 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*-
from .exifHeader import hide, reveal from .exifHeader import hide, reveal

View file

@ -1,8 +1,10 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*-
# Stegano - Stegano is a pure Python steganography module. # Stegano - Stegano is a pure Python steganography module.
# Copyright (C) 2010-2025 Cédric Bonhomme - https://www.cedricbonhomme.org # Copyright (C) 2010-2022 Cédric Bonhomme - https://www.cedricbonhomme.org
# #
# For more information : https://github.com/cedricbonhomme/Stegano # For more information : https://git.sr.ht/~cedric/stegano
# #
# This program is free software: you can redistribute it and/or modify # This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by # it under the terms of the GNU General Public License as published by
@ -24,7 +26,6 @@ __revision__ = "$Date: 2017/01/18 $"
__license__ = "GPLv3" __license__ = "GPLv3"
import piexif import piexif
from stegano import tools from stegano import tools
@ -36,11 +37,11 @@ def hide(
img_format=None, img_format=None,
): ):
"""Hide a message (string) in an image.""" """Hide a message (string) in an image."""
from base64 import b64encode
from zlib import compress from zlib import compress
from base64 import b64encode
if secret_file is not None: if secret_file is not None:
with open(secret_file) as f: with open(secret_file, "r") as f:
secret_message = f.read() secret_message = f.read()
try: try:

View file

@ -1,4 +1,5 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*-
from .lsb import hide, reveal from .lsb import hide, reveal

View file

@ -1,8 +1,10 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*-
# Stegano - Stegano is a pure Python steganography module. # Stegano - Stegano is a pure Python steganography module.
# Copyright (C) 2010-2025 Cédric Bonhomme - https://www.cedricbonhomme.org # Copyright (C) 2010-2022 Cédric Bonhomme - https://www.cedricbonhomme.org
# #
# For more information : https://github.com/cedricbonhomme/Stegano # For more information : https://git.sr.ht/~cedric/stegano
# #
# This program is free software: you can redistribute it and/or modify # This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by # it under the terms of the GNU General Public License as published by
@ -24,11 +26,10 @@ __revision__ = "$Date: 2021/11/29 $"
__license__ = "GPLv3" __license__ = "GPLv3"
import itertools import itertools
import math
from typing import Any, Dict, Iterator, List
import cv2 import cv2
import numpy as np import numpy as np
import math
from typing import Dict, Iterator, List, Any
def identity() -> Iterator[int]: def identity() -> Iterator[int]:
@ -72,7 +73,7 @@ def eratosthenes() -> Iterator[int]:
"""Generate the prime numbers with the sieve of Eratosthenes. """Generate the prime numbers with the sieve of Eratosthenes.
https://oeis.org/A000040 https://oeis.org/A000040
""" """
d: Dict[int, List[int]] = {} d = {} # type: Dict[int, List[int]]
for i in itertools.count(2): for i in itertools.count(2):
if i in d: if i in d:
for j in d[i]: for j in d[i]:
@ -89,7 +90,8 @@ def composite() -> Iterator[int]:
""" """
p1 = 3 p1 = 3
for p2 in eratosthenes(): for p2 in eratosthenes():
yield from range(p1 + 1, p2) for n in range(p1 + 1, p2):
yield n
p1 = p2 p1 = p2
@ -223,7 +225,7 @@ def LFSR(m: int) -> Iterator[int]:
# Add the feedback bit # Add the feedback bit
state.insert(0, feedback) state.insert(0, feedback)
# Convert the registers to an int # Convert the registers to an int
out = sum(e * (2**i) for i, e in enumerate(state)) out = sum([e * (2**i) for i, e in enumerate(state)])
yield out yield out
@ -242,7 +244,7 @@ def shi_tomashi(
gray, max_corners, quality, min_distance gray, max_corners, quality, min_distance
) )
corners_int: np.ndarray[Any, np.dtype[np.signedinteger[Any]]] = np.array( corners_int: np.ndarray[Any, np.dtype[np.signedinteger[Any]]] = np.array(
np.intp(corners) np.int0(corners)
) )
i = 0 i = 0
while True: while True:

View file

@ -1,8 +1,10 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*-
# Stegano - Stegano is a pure Python steganography module. # Stegano - Stegano is a pure Python steganography module.
# Copyright (C) 2010-2025 Cédric Bonhomme - https://www.cedricbonhomme.org # Copyright (C) 2010-2022 Cédric Bonhomme - https://www.cedricbonhomme.org
# #
# For more information : https://github.com/cedricbonhomme/Stegano # For more information : https://git.sr.ht/~cedric/stegano
# #
# This program is free software: you can redistribute it and/or modify # This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by # it under the terms of the GNU General Public License as published by
@ -25,9 +27,8 @@ __license__ = "GPLv3"
from typing import IO, Iterator, Union from typing import IO, Iterator, Union
from stegano import tools
from .generators import identity from .generators import identity
from stegano import tools
def hide( def hide(
@ -67,10 +68,9 @@ def reveal(
generator: Union[None, Iterator[int]] = None, generator: Union[None, Iterator[int]] = None,
shift: int = 0, shift: int = 0,
encoding: str = "UTF-8", encoding: str = "UTF-8",
close_file: bool = True,
): ):
"""Find a message in an image (with the LSB technique).""" """Find a message in an image (with the LSB technique)."""
revealer = tools.Revealer(encoded_image, encoding, close_file) revealer = tools.Revealer(encoded_image, encoding)
width = revealer.encoded_image.width width = revealer.encoded_image.width
if not generator: if not generator:

View file

@ -1,4 +1,5 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*-
from .red import hide, reveal from .red import hide, reveal

View file

@ -1,8 +1,10 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*-
# Stegano - Stéganô is a basic Python Steganography module. # Stegano - Stéganô is a basic Python Steganography module.
# Copyright (C) 2010-2024 Cédric Bonhomme - https://www.cedricbonhomme.org # Copyright (C) 2010-2022 Cédric Bonhomme - https://www.cedricbonhomme.org
# #
# For more information : https://github.com/cedricbonhomme/Stegano # For more information : https://git.sr.ht/~cedric/stegano
# #
# This program is free software: you can redistribute it and/or modify # This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by # it under the terms of the GNU General Public License as published by

View file

@ -1 +1,2 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*-

View file

@ -1,8 +1,10 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*-
# Stegano - Stegano is a pure Python steganography module. # Stegano - Stegano is a pure Python steganography module.
# Copyright (C) 2010-2025 Cédric Bonhomme - https://www.cedricbonhomme.org # Copyright (C) 2010-2022 Cédric Bonhomme - https://www.cedricbonhomme.org
# #
# For more information : https://github.com/cedricbonhomme/Stegano # For more information : https://git.sr.ht/~cedric/stegano
# #
# This program is free software: you can redistribute it and/or modify # This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by # it under the terms of the GNU General Public License as published by
@ -34,10 +36,7 @@ def steganalyse(img: Image.Image) -> Image.Image:
width, height = img.size width, height = img.size
for row in range(height): for row in range(height):
for col in range(width): for col in range(width):
if pixel := img.getpixel((col, row)): r, g, b = img.getpixel((col, row))[0:3]
r, g, b = pixel[0:3]
else:
raise Exception("Error during steganlysis.")
if r % 2 == 0: if r % 2 == 0:
r = 0 r = 0
else: else:

View file

@ -1,8 +1,10 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*-
# Stegano - Stegano is a pure Python steganography module. # Stegano - Stegano is a pure Python steganography module.
# Copyright (C) 2010-2025 Cédric Bonhomme - https://www.cedricbonhomme.org # Copyright (C) 2010-2022 Cédric Bonhomme - https://www.cedricbonhomme.org
# #
# For more information : https://github.com/cedricbonhomme/Stegano # For more information : https://git.sr.ht/~cedric/stegano
# #
# This program is free software: you can redistribute it and/or modify # This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by # it under the terms of the GNU General Public License as published by
@ -24,7 +26,9 @@ __revision__ = "$Date: 2021/11/01 $"
__license__ = "GPLv3" __license__ = "GPLv3"
import typing import typing
from collections import Counter, OrderedDict
from collections import Counter
from collections import OrderedDict
def steganalyse(img): def steganalyse(img):
@ -32,7 +36,7 @@ def steganalyse(img):
Steganlysis of the LSB technique. Steganlysis of the LSB technique.
""" """
width, height = img.size width, height = img.size
colours_counter: typing.Counter[int] = Counter() colours_counter = Counter() # type: typing.Counter[int]
for row in range(height): for row in range(height):
for col in range(width): for col in range(width):
r, g, b = img.getpixel((col, row)) r, g, b = img.getpixel((col, row))
@ -43,7 +47,7 @@ def steganalyse(img):
sorted(list(colours_counter.items()), key=lambda t: t[1]) sorted(list(colours_counter.items()), key=lambda t: t[1])
) )
colours: float = 0 colours = 0 # type: float
for colour in list(dict_colours.keys()): for colour in list(dict_colours.keys()):
colours += colour colours += colour
colours = colours / len(dict_colours) colours = colours / len(dict_colours)

View file

@ -1,8 +1,10 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*-
# Stegano - Stegano is a pure Python steganography module. # Stegano - Stegano is a pure Python steganography module.
# Copyright (C) 2010-2025 Cédric Bonhomme - https://www.cedricbonhomme.org # Copyright (C) 2010-2022 Cédric Bonhomme - https://www.cedricbonhomme.org
# #
# For more information : https://github.com/cedricbonhomme/Stegano # For more information : https://git.sr.ht/~cedric/stegano
# #
# This program is free software: you can redistribute it and/or modify # This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by # it under the terms of the GNU General Public License as published by
@ -26,7 +28,7 @@ __license__ = "GPLv3"
import base64 import base64
import itertools import itertools
from functools import reduce from functools import reduce
from typing import IO, List, Union from typing import IO, Iterator, List, Tuple, Union
from PIL import Image from PIL import Image
@ -74,7 +76,9 @@ def setlsb(component: int, bit: str) -> int:
return component & ~1 | int(bit) return component & ~1 | int(bit)
def n_at_a_time(items: List[int], n: int, fillvalue: str): def n_at_a_time(
items: List[int], n: int, fillvalue: str
) -> Iterator[Tuple[Union[int, str]]]:
"""Returns an iterator which groups n items at a time. """Returns an iterator which groups n items at a time.
Any final partial tuple will be padded with the fillvalue Any final partial tuple will be padded with the fillvalue
@ -130,7 +134,7 @@ class Hider:
if image.mode not in ["RGB", "RGBA"]: if image.mode not in ["RGB", "RGBA"]:
if not auto_convert_rgb: if not auto_convert_rgb:
print(f"The mode of the image is not RGB. Mode is {image.mode}") print("The mode of the image is not RGB. Mode is {}".format(image.mode))
answer = input("Convert the image to RGB ? [Y / n]\n") or "Y" answer = input("Convert the image to RGB ? [Y / n]\n") or "Y"
if answer.lower() == "n": if answer.lower() == "n":
raise Exception("Not a RGB image.") raise Exception("Not a RGB image.")
@ -150,7 +154,7 @@ class Hider:
if self._len_message_bits > npixels * 3: if self._len_message_bits > npixels * 3:
raise Exception( raise Exception(
f"The message you want to hide is too long: {message_length}" "The message you want to hide is too long: {}".format(message_length)
) )
def encode_another_pixel(self): def encode_another_pixel(self):
@ -175,19 +179,13 @@ class Hider:
class Revealer: class Revealer:
def __init__( def __init__(self, encoded_image: Union[str, IO[bytes]], encoding: str = "UTF-8"):
self,
encoded_image: Union[str, IO[bytes]],
encoding: str = "UTF-8",
close_file: bool = True,
):
self.encoded_image = open_image(encoded_image) self.encoded_image = open_image(encoded_image)
self._encoding_length = ENCODINGS[encoding] self._encoding_length = ENCODINGS[encoding]
self._buff, self._count = 0, 0 self._buff, self._count = 0, 0
self._bitab: List[str] = [] self._bitab: List[str] = []
self._limit: Union[None, int] = None self._limit: Union[None, int] = None
self.secret_message = "" self.secret_message = ""
self.close_file = close_file
def decode_pixel(self, coordinate: tuple): def decode_pixel(self, coordinate: tuple):
# pixel = [r, g, b] or [r,g,b,a] # pixel = [r, g, b] or [r,g,b,a]
@ -214,8 +212,7 @@ class Revealer:
self.secret_message = "".join(self._bitab)[ self.secret_message = "".join(self._bitab)[
len(str(self._limit)) + 1 : # noqa: E203 len(str(self._limit)) + 1 : # noqa: E203
] ]
if self.close_file: self.encoded_image.close()
self.encoded_image.close()
return True return True

View file

@ -1,8 +1,10 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*-
# Stegano - Stegano is a pure Python steganography module. # Stegano - Stegano is a pure Python steganography module.
# Copyright (C) 2010-2025 Cédric Bonhomme - https://www.cedricbonhomme.org # Copyright (C) 2010-2022 Cédric Bonhomme - https://www.cedricbonhomme.org
# #
# For more information : https://github.com/cedricbonhomme/Stegano # For more information : https://git.sr.ht/~cedric/stegano
# #
# This program is free software: you can redistribute it and/or modify # This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by # it under the terms of the GNU General Public License as published by
@ -23,9 +25,9 @@ __date__ = "$Date: 2016/05/17 $"
__revision__ = "$Date: 2017/01/18 $" __revision__ = "$Date: 2017/01/18 $"
__license__ = "GPLv3" __license__ = "GPLv3"
import io
import os import os
import unittest import unittest
import io
from stegano import exifHeader from stegano import exifHeader

View file

@ -1,8 +1,10 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*-
# Stegano - Stegano is a pure Python steganography module. # Stegano - Stegano is a pure Python steganography module.
# Copyright (C) 2010-2025 Cédric Bonhomme - https://www.cedricbonhomme.org # Copyright (C) 2010-2022 Cédric Bonhomme - https://www.cedricbonhomme.org
# #
# For more information : https://github.com/cedricbonhomme/Stegano # For more information : https://git.sr.ht/~cedric/stegano
# #
# This program is free software: you can redistribute it and/or modify # This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by # it under the terms of the GNU General Public License as published by
@ -23,9 +25,8 @@ __date__ = "$Date: 2017/03/01 $"
__revision__ = "$Date: 2017/03/01 $" __revision__ = "$Date: 2017/03/01 $"
__license__ = "GPLv3" __license__ = "GPLv3"
import itertools
import unittest import unittest
import itertools
import cv2 import cv2
import numpy as np import numpy as np
@ -70,7 +71,7 @@ class TestGenerators(unittest.TestCase):
def test_eratosthenes(self): def test_eratosthenes(self):
"""Test the Eratosthenes sieve.""" """Test the Eratosthenes sieve."""
with open("./tests/expected-results/eratosthenes") as f: with open("./tests/expected-results/eratosthenes", "r") as f:
self.assertEqual( self.assertEqual(
tuple(itertools.islice(generators.eratosthenes(), 168)), tuple(itertools.islice(generators.eratosthenes(), 168)),
tuple(int(line) for line in f), tuple(int(line) for line in f),
@ -78,7 +79,7 @@ class TestGenerators(unittest.TestCase):
def test_composite(self): def test_composite(self):
"""Test the composite sieve.""" """Test the composite sieve."""
with open("./tests/expected-results/composite") as f: with open("./tests/expected-results/composite", "r") as f:
self.assertEqual( self.assertEqual(
tuple(itertools.islice(generators.composite(), 114)), tuple(itertools.islice(generators.composite(), 114)),
tuple(int(line) for line in f), tuple(int(line) for line in f),
@ -86,7 +87,7 @@ class TestGenerators(unittest.TestCase):
def test_fermat(self): def test_fermat(self):
"""Test the Fermat generator.""" """Test the Fermat generator."""
with open("./tests/expected-results/fermat") as f: with open("./tests/expected-results/fermat", "r") as f:
self.assertEqual( self.assertEqual(
tuple(itertools.islice(generators.fermat(), 9)), tuple(itertools.islice(generators.fermat(), 9)),
tuple(int(line) for line in f), tuple(int(line) for line in f),
@ -94,7 +95,7 @@ class TestGenerators(unittest.TestCase):
def test_triangular_numbers(self): def test_triangular_numbers(self):
"""Test the Triangular numbers generator.""" """Test the Triangular numbers generator."""
with open("./tests/expected-results/triangular_numbers") as f: with open("./tests/expected-results/triangular_numbers", "r") as f:
self.assertEqual( self.assertEqual(
tuple(itertools.islice(generators.triangular_numbers(), 54)), tuple(itertools.islice(generators.triangular_numbers(), 54)),
tuple(int(line) for line in f), tuple(int(line) for line in f),
@ -102,7 +103,7 @@ class TestGenerators(unittest.TestCase):
def test_mersenne(self): def test_mersenne(self):
"""Test the Mersenne generator.""" """Test the Mersenne generator."""
with open("./tests/expected-results/mersenne") as f: with open("./tests/expected-results/mersenne", "r") as f:
self.assertEqual( self.assertEqual(
tuple(itertools.islice(generators.mersenne(), 20)), tuple(itertools.islice(generators.mersenne(), 20)),
tuple(int(line) for line in f), tuple(int(line) for line in f),
@ -110,7 +111,7 @@ class TestGenerators(unittest.TestCase):
def test_carmichael(self): def test_carmichael(self):
"""Test the Carmichael generator.""" """Test the Carmichael generator."""
with open("./tests/expected-results/carmichael") as f: with open("./tests/expected-results/carmichael", "r") as f:
self.assertEqual( self.assertEqual(
tuple(itertools.islice(generators.carmichael(), 33)), tuple(itertools.islice(generators.carmichael(), 33)),
tuple(int(line) for line in f), tuple(int(line) for line in f),
@ -118,7 +119,7 @@ class TestGenerators(unittest.TestCase):
def test_ackermann_slow(self): def test_ackermann_slow(self):
"""Test the Ackermann set.""" """Test the Ackermann set."""
with open("./tests/expected-results/ackermann") as f: with open("./tests/expected-results/ackermann", "r") as f:
self.assertEqual(generators.ackermann_slow(3, 1), int(f.readline())) self.assertEqual(generators.ackermann_slow(3, 1), int(f.readline()))
self.assertEqual(generators.ackermann_slow(3, 2), int(f.readline())) self.assertEqual(generators.ackermann_slow(3, 2), int(f.readline()))
@ -126,13 +127,13 @@ class TestGenerators(unittest.TestCase):
"""Test the Naive Ackermann generator""" """Test the Naive Ackermann generator"""
gen = generators.ackermann_naive(3) gen = generators.ackermann_naive(3)
next(gen) next(gen)
with open("./tests/expected-results/ackermann") as f: with open("./tests/expected-results/ackermann", "r") as f:
self.assertEqual(next(gen), int(f.readline())) self.assertEqual(next(gen), int(f.readline()))
self.assertEqual(next(gen), int(f.readline())) self.assertEqual(next(gen), int(f.readline()))
def test_ackermann_fast(self): def test_ackermann_fast(self):
"""Test the Ackermann set.""" """Test the Ackermann set."""
with open("./tests/expected-results/ackermann") as f: with open("./tests/expected-results/ackermann", "r") as f:
self.assertEqual(generators.ackermann_fast(3, 1), int(f.readline())) self.assertEqual(generators.ackermann_fast(3, 1), int(f.readline()))
self.assertEqual(generators.ackermann_fast(3, 2), int(f.readline())) self.assertEqual(generators.ackermann_fast(3, 2), int(f.readline()))
self.assertEqual(generators.ackermann_fast(4, 1), int(f.readline())) self.assertEqual(generators.ackermann_fast(4, 1), int(f.readline()))
@ -141,13 +142,13 @@ class TestGenerators(unittest.TestCase):
"""Test the Ackermann generator""" """Test the Ackermann generator"""
gen = generators.ackermann(3) gen = generators.ackermann(3)
next(gen) next(gen)
with open("./tests/expected-results/ackermann") as f: with open("./tests/expected-results/ackermann", "r") as f:
self.assertEqual(next(gen), int(f.readline())) self.assertEqual(next(gen), int(f.readline()))
self.assertEqual(next(gen), int(f.readline())) self.assertEqual(next(gen), int(f.readline()))
def test_LFSR(self): def test_LFSR(self):
"""Test the LFSR generator""" """Test the LFSR generator"""
with open("./tests/expected-results/LFSR") as f: with open("./tests/expected-results/LFSR", "r") as f:
self.assertEqual( self.assertEqual(
tuple(itertools.islice(generators.LFSR(2**8), 256)), tuple(itertools.islice(generators.LFSR(2**8), 256)),
tuple(int(line) for line in f), tuple(int(line) for line in f),
@ -174,8 +175,9 @@ class TestGenerators(unittest.TestCase):
test_file_reshaped = test_file.reshape( test_file_reshaped = test_file.reshape(
int(test_file.shape[0]), int(test_file.shape[1]) int(test_file.shape[0]), int(test_file.shape[1])
) )
res = np.testing.assert_allclose(corners, test_file_reshaped, rtol=1e-0, atol=0) # type: ignore self.assertIsNone(
self.assertIsNone(res) np.testing.assert_allclose(corners, test_file_reshaped, rtol=1e-0, atol=0)
)
@staticmethod @staticmethod
def shi_tomashi_reconfigure( def shi_tomashi_reconfigure(

View file

@ -1,8 +1,10 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*-
# Stegano - Stegano is a pure Python steganography module. # Stegano - Stegano is a pure Python steganography module.
# Copyright (C) 2010-2024 Cédric Bonhomme - https://www.cedricbonhomme.org # Copyright (C) 2010-2022 Cédric Bonhomme - https://www.cedricbonhomme.org
# #
# For more information : https://github.com/cedricbonhomme/Stegano # For more information : https://git.sr.ht/~cedric/stegano
# #
# This program is free software: you can redistribute it and/or modify # This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by # it under the terms of the GNU General Public License as published by
@ -171,12 +173,7 @@ class TestLSB(unittest.TestCase):
@patch("builtins.input", return_value="n") @patch("builtins.input", return_value="n")
def test_refuse_convert_rgb(self, input): def test_refuse_convert_rgb(self, input):
message_to_hide = "Hello World!" message_to_hide = "Hello World!"
# lsb.hide( with self.assertRaises(Exception):
# "./tests/sample-files/Lenna-grayscale.png",
# message_to_hide,
# generators.eratosthenes(),
# )
with self.assertRaisesRegex(Exception, "Not a RGB image."):
lsb.hide( lsb.hide(
"./tests/sample-files/Lenna-grayscale.png", "./tests/sample-files/Lenna-grayscale.png",
message_to_hide, message_to_hide,
@ -213,9 +210,7 @@ class TestLSB(unittest.TestCase):
with open("./tests/sample-files/lorem_ipsum.txt") as f: with open("./tests/sample-files/lorem_ipsum.txt") as f:
message = f.read() message = f.read()
message += message * 2 message += message * 2
with self.assertRaisesRegex( with self.assertRaises(Exception):
Exception, "The message you want to hide is too long:"
):
lsb.hide("./tests/sample-files/Lenna.png", message, generators.identity()) lsb.hide("./tests/sample-files/Lenna.png", message, generators.identity())
def test_hide_and_reveal_with_bad_generator(self): def test_hide_and_reveal_with_bad_generator(self):

View file

@ -1,8 +1,10 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*-
# Stegano - Stegano is a pure Python steganography module. # Stegano - Stegano is a pure Python steganography module.
# Copyright (C) 2010-2025 Cédric Bonhomme - https://www.cedricbonhomme.org # Copyright (C) 2010-2022 Cédric Bonhomme - https://www.cedricbonhomme.org
# #
# For more information : https://github.com/cedricbonhomme/Stegano # For more information : https://git.sr.ht/~cedric/stegano
# #
# This program is free software: you can redistribute it and/or modify # This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by # it under the terms of the GNU General Public License as published by

View file

@ -1,9 +1,10 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*-
# Stegano - Stegano is a pure Python steganography module. # Stegano - Stegano is a pure Python steganography module.
# Copyright (C) 2010-2017 Cédric Bonhomme - https://www.cedricbonhomme.org # Copyright (C) 2010-2017 Cédric Bonhomme - https://www.cedricbonhomme.org
# #
# For more information : https://github.com/cedricbonhomme/Stegano # For more information : https://git.sr.ht/~cedric/stegano
# #
# This program is free software: you can redistribute it and/or modify # This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by # it under the terms of the GNU General Public License as published by
@ -26,10 +27,9 @@ __license__ = "GPLv3"
import unittest import unittest
from PIL import Image, ImageChops
from stegano import lsb from stegano import lsb
from stegano.steganalysis import parity, statistics from stegano.steganalysis import parity, statistics
from PIL import Image, ImageChops
class TestSteganalysis(unittest.TestCase): class TestSteganalysis(unittest.TestCase):

View file

@ -1,8 +1,10 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*-
# Stegano - Stegano is a pure Python steganography module. # Stegano - Stegano is a pure Python steganography module.
# Copyright (C) 2010-2025 Cédric Bonhomme - https://www.cedricbonhomme.org # Copyright (C) 2010-2022 Cédric Bonhomme - https://www.cedricbonhomme.org
# #
# For more information : https://github.com/cedricbonhomme/Stegano # For more information : https://git.sr.ht/~cedric/stegano
# #
# This program is free software: you can redistribute it and/or modify # This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by # it under the terms of the GNU General Public License as published by
@ -81,7 +83,7 @@ class TestTools(unittest.TestCase):
self.assertEqual(list(result), [(1, 2), (3, 4), (5, "X")]) self.assertEqual(list(result), [(1, 2), (3, 4), (5, "X")])
def test_binary2base64(self): def test_binary2base64(self):
with open("./tests/expected-results/binary2base64") as f: with open("./tests/expected-results/binary2base64", "r") as f:
expected_value = f.read() expected_value = f.read()
value = tools.binary2base64("tests/sample-files/free-software-song.ogg") value = tools.binary2base64("tests/sample-files/free-software-song.ogg")
self.assertEqual(expected_value, value) self.assertEqual(expected_value, value)