mirror of
https://github.com/cedricbonhomme/Stegano.git
synced 2025-05-20 12:28:31 +02:00
Compare commits
303 commits
Author | SHA1 | Date | |
---|---|---|---|
|
bd3238acfa | ||
|
eb28c60d27 | ||
|
7ba387ecdc | ||
|
ac66655ad8 | ||
|
2e8b0fdec9 | ||
|
7849955cdb | ||
|
d6015157bf | ||
|
43f02702f9 | ||
|
9c69075d34 | ||
|
10256ba28b | ||
|
70572bf047 | ||
|
8ebd760f7e | ||
|
0d0d328230 | ||
|
5a6e08232f | ||
|
6fac279580 | ||
|
a6140fef36 | ||
|
80d7e6c88a | ||
|
d078b7a734 | ||
|
b4114bca00 | ||
|
ada72ecb47 | ||
|
d8685270ff | ||
|
924d8e4979 | ||
|
40c0e83c88 | ||
|
20bb3dc6fc | ||
|
68b56779b1 | ||
|
e41c5b7cb0 | ||
|
00415e364d | ||
|
bd1a82f397 | ||
|
dfce98008c | ||
|
e0360062b5 | ||
|
a660ff8628 | ||
|
53d897b808 | ||
|
fd2a784df3 | ||
|
136ce42ae9 | ||
|
c66a54875c | ||
|
b1133d8e35 | ||
|
a78c520058 | ||
|
b592ce4bcb | ||
|
0054c89b7d | ||
|
ab046d24f2 | ||
|
1de253dd0a | ||
|
a70b552cb8 | ||
|
eae8ab29fc | ||
|
0d20be7d82 | ||
|
33c9cd55b1 | ||
|
d02f8b8b14 | ||
|
d0ca9322cc | ||
|
91fef6a178 | ||
|
178347f61f | ||
|
ed66aaa159 | ||
|
1293a3575b | ||
|
1ef59ac80b | ||
|
0ca92d1d3a | ||
|
46da77ef1c | ||
|
5c94a790bc | ||
|
220501adbc | ||
|
e253929ad6 | ||
|
d863dbfddb | ||
|
0f550178ff | ||
|
a3dd6be618 | ||
|
29322e759a | ||
|
c6437af0d1 | ||
|
25447f88e9 | ||
|
ad916563da | ||
|
88ce965e7c | ||
|
25a7105487 | ||
|
29d427afef | ||
|
7434ed9a1e | ||
|
e3c1312d14 | ||
|
166d9d1291 | ||
|
556473c81d | ||
|
84b1855130 | ||
|
11348dda9a | ||
|
c2168422df | ||
|
bd2835d7d0 | ||
|
116e011d37 | ||
|
84a34d0bfa | ||
|
8c14555adb | ||
|
e33bebad46 | ||
|
2a3b92e62a | ||
|
0537c13a08 | ||
|
e490e32791 | ||
|
91789f065e | ||
|
bc9b91e983 | ||
|
c12b371e7d | ||
|
3fd55e2e79 | ||
|
7abce7d5d9 | ||
|
6d87ba2921 | ||
|
eb639510ce | ||
|
d10805427a | ||
|
bfc5d332be | ||
|
e2fe642e3d | ||
|
92d3e0697d | ||
|
4575319307 | ||
|
0080ec06f7 | ||
|
d6a18a71e4 | ||
|
fa741328f3 | ||
|
2abe01660d | ||
|
3dd5dd2be3 | ||
|
857d311a79 | ||
|
0daa5208c7 | ||
|
ea2f1a6eda | ||
|
03ebf467a2 | ||
|
7f9c34c6da | ||
|
dd16c1cb61 | ||
|
257fa03c9f | ||
|
e68404176a | ||
|
e988e9e02a | ||
|
fb4b596726 | ||
|
0f8b0cef1f | ||
|
3b619afa17 | ||
|
4c37a41e77 | ||
|
a67793e7a2 | ||
|
298acb7d57 | ||
|
3eaa93e36c | ||
|
18d1176ae8 | ||
|
2e4f1629fd | ||
|
bee5f819d9 | ||
|
1f684966cc | ||
|
a610da148e | ||
|
cf40cb26b0 | ||
|
e290d1260e | ||
|
fc53f2a6b9 | ||
|
e6e0f4f80f | ||
|
053c27ad4f | ||
|
43b963d4f1 | ||
|
076a5d447f | ||
|
82b59f73db | ||
|
54938159d4 | ||
|
f6aa2207f4 | ||
|
eed1f08526 | ||
|
3103a3ae9b | ||
|
0d98e1834c | ||
|
58dbb94c5e | ||
|
257d2c2f68 | ||
|
cf7209f94c | ||
|
ee1044e627 | ||
|
afb095c5ae | ||
|
12410c2548 | ||
|
ce472f9dd9 | ||
|
fea529d84d | ||
|
cc255e2e55 | ||
|
35a4c20d8d | ||
|
1c53b1d01b | ||
|
a18efe6ab8 | ||
|
1b8d8042b7 | ||
|
0a17b29121 | ||
|
9c981005e4 | ||
|
555cf1acc2 | ||
|
d7ea51eed7 | ||
|
b6c8cc3d35 | ||
|
871f1f60b4 | ||
|
0460acd7f3 | ||
|
27083934d0 | ||
|
1033933f85 | ||
|
c25cdc09f7 | ||
|
a1213a9163 | ||
|
891051f77b | ||
|
a5912ba3f1 | ||
|
4a657625f5 | ||
|
c774e9e91c | ||
|
8ea1553538 | ||
|
b00dc73fd4 | ||
|
4aac55ec77 | ||
|
b64d039389 | ||
|
be3e32ef6c | ||
|
415afc3ac8 | ||
|
7681cee3d3 | ||
|
b9e01071f0 | ||
|
0602d8328a | ||
|
44fb07ab69 | ||
|
317eb0d889 | ||
|
b7d26d15ee | ||
|
16e12e8edf | ||
|
96ff4ae620 | ||
|
d29d42509e | ||
|
3ef0239b8c | ||
|
ff09bfb267 | ||
|
d765fee7cf | ||
|
99971ff14f | ||
|
d030b8db7d | ||
|
b7ca9a9704 | ||
|
0290b2836b | ||
|
183daf3155 | ||
|
50320b0cd2 | ||
|
048322532b | ||
|
0fac8cb22c | ||
|
1b484563f5 | ||
|
6d3818ceca | ||
|
c1ec5b9ab1 | ||
|
e9a7721ccc | ||
|
45de0524d6 | ||
|
c2047f569e | ||
|
ecc858b9c3 | ||
|
3aeb032623 | ||
|
e5c7e003d6 | ||
|
6e24ec6374 | ||
|
e9605589b6 | ||
|
3dfebda205 | ||
|
13693ab3b5 | ||
|
8260dd6444 | ||
|
d160c4f1cf | ||
|
f72afd2f12 | ||
|
04531488ca | ||
|
71f6c08c28 | ||
|
9b216d9d59 | ||
|
a61a7916a3 | ||
|
c0abda31bc | ||
|
e5cfceb261 | ||
|
5f2dd1dad8 | ||
|
0f5a251398 | ||
|
13b06ee04a | ||
|
6e5a0a3523 | ||
|
9acc9afa99 | ||
|
a03c59db5c | ||
|
481b315605 | ||
|
cc626f8954 | ||
|
15b62a8034 | ||
|
6e9dff3c79 | ||
|
cfe3a1325c | ||
|
213531f63d | ||
|
23ad27f267 | ||
|
3c4042ac91 | ||
|
37a0bc2fa2 | ||
|
54277960d9 | ||
|
a265cbaa18 | ||
|
6645bd62c8 | ||
|
ab19ea12f2 | ||
|
c5298a8411 | ||
|
a9b9136141 | ||
|
3de95517e9 | ||
|
b116202adb | ||
|
c6481938de | ||
|
dbe54238b4 | ||
|
0d5a8a1362 | ||
|
207ac6c4f8 | ||
|
fbe9f1b6e1 | ||
|
ea165fe1b1 | ||
|
649fd542d7 | ||
|
4019e792d4 | ||
|
5e909bfc24 | ||
|
d3b8a65318 | ||
|
2dc6430373 | ||
|
e74662cc90 | ||
|
44fc336c8a | ||
|
508834f7ea | ||
|
027d3a57f4 | ||
|
f8cc0f4cb3 | ||
|
5122f204f6 | ||
|
e7017e61a6 | ||
|
d47a105ac7 | ||
|
a060757f76 | ||
|
b054fc7a0c | ||
|
2d74094dc3 | ||
|
064456e2a3 | ||
|
e4784ad04d | ||
|
f72cfec815 | ||
|
6a691c7927 | ||
|
a3019f6c1a | ||
|
ffd9fca5f1 | ||
|
3780379f91 | ||
|
097692900a | ||
|
9f032fffbc | ||
|
bf094c0361 | ||
|
1533db0425 | ||
|
c6603e276b | ||
|
3f350c3569 | ||
|
8f8cd483fd | ||
|
45cdb9a3a5 | ||
|
5df01ea737 | ||
|
c3f6f392f3 | ||
|
37aeb39167 | ||
|
f3cbe09b21 | ||
|
5694e5c806 | ||
|
1704aec690 | ||
|
0e18b2f95a | ||
|
3b6be169b2 | ||
|
48ff22476f | ||
|
316d3eea08 | ||
|
47f70cc820 | ||
|
502e630379 | ||
|
5bb91d2ff0 | ||
|
ba3cde6112 | ||
|
c5b7df87f5 | ||
|
d8fe34e5bc | ||
|
93de151025 | ||
|
538316c722 | ||
|
8d1985dfae | ||
|
3e8b6cdfd0 | ||
|
fa18ab4fef | ||
|
a1db2997a9 | ||
|
07b44e8606 | ||
|
5b297efc0e | ||
|
ea2de8142c | ||
|
a86361dd27 | ||
|
bd5b156453 | ||
|
adcc3cf9a5 | ||
|
c4b1fdac56 | ||
|
9f2f73973a | ||
|
f9619a8e1c | ||
|
041a4ee05b | ||
|
d863dd71a1 | ||
|
a3f02db66c |
65 changed files with 4594 additions and 2238 deletions
16
.editorconfig
Normal file
16
.editorconfig
Normal file
|
@ -0,0 +1,16 @@
|
|||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
max_line_length = 88
|
||||
|
||||
[*.{yml,yaml,json,js,css,html}]
|
||||
indent_size = 2
|
||||
|
||||
[*.{md,rst}]
|
||||
trim_trailing_whitespace = false
|
42
.github/workflows/pythonapp.yaml
vendored
Normal file
42
.github/workflows/pythonapp.yaml
vendored
Normal file
|
@ -0,0 +1,42 @@
|
|||
name: Python application
|
||||
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: ["3.10", "3.11", "3.12"]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
pip install poetry
|
||||
poetry install --with dev
|
||||
|
||||
- name: Lint with flake8
|
||||
run: |
|
||||
# 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
|
||||
# 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
|
||||
|
||||
- name: Test with pytest
|
||||
run: |
|
||||
poetry run nose2 -v --pretty-assert
|
||||
env:
|
||||
testing: actions
|
||||
|
||||
# - name: Type check with mypy
|
||||
# run: |
|
||||
# poetry run mypy .
|
27
.github/workflows/release.yml
vendored
Normal file
27
.github/workflows/release.yml
vendored
Normal file
|
@ -0,0 +1,27 @@
|
|||
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
|
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -12,7 +12,7 @@ build
|
|||
|
||||
# setuptools
|
||||
build/*
|
||||
Stegano.egg-info/*
|
||||
stegano.egg-info/
|
||||
dist/*
|
||||
|
||||
# tests
|
||||
|
|
36
.pre-commit-config.yaml
Normal file
36
.pre-commit-config.yaml
Normal file
|
@ -0,0 +1,36 @@
|
|||
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
|
22
.readthedocs.yaml
Normal file
22
.readthedocs.yaml
Normal file
|
@ -0,0 +1,22 @@
|
|||
# .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
|
20
.travis.yml
20
.travis.yml
|
@ -1,20 +0,0 @@
|
|||
language: python
|
||||
python:
|
||||
- 3.6
|
||||
|
||||
env:
|
||||
- PIPENV_VENV_IN_PROJECT=1
|
||||
|
||||
install:
|
||||
- "pip install pipenv"
|
||||
- "pipenv install"
|
||||
- "pipenv install --dev"
|
||||
|
||||
script:
|
||||
# Run the test suit
|
||||
- nosetests --with-coverage --cover-package=stegano
|
||||
# Run the type checker
|
||||
- python tools/run_mypy.py
|
||||
|
||||
after_success:
|
||||
- coveralls
|
341
CHANGELOG.md
Normal file
341
CHANGELOG.md
Normal file
|
@ -0,0 +1,341 @@
|
|||
## 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)
|
||||
|
||||
- Fixed a bug in the command line when no sub-command is specified.
|
||||
|
||||
|
||||
### 0.11.0 (2022-11-20)
|
||||
|
||||
- Reduced memory footprint and processing speed,
|
||||
the modules ``lsb`` and ``lsbset`` have been merged
|
||||
([PR #34](https://github.com/cedricbonhomme/Stegano/pull/34)).
|
||||
|
||||
|
||||
### 0.10.2 (2022-01-13)
|
||||
|
||||
- Stegano now uses Pillow 9.0.0 (CVE-2022-22815).
|
||||
|
||||
|
||||
### 0.10.1 (2021-11-30)
|
||||
|
||||
- Stegano now uses OpenCV Python 4.5.4 abd Numpy 1.21.4.
|
||||
|
||||
|
||||
### 0.10.0 (2021-11-29)
|
||||
|
||||
- new: Implemented Shi-Tomashi corner generator
|
||||
([PR #32](https://github.com/cedricbonhomme/Stegano/pull/32)).
|
||||
Implemented by thundersparkf (see CONTRIBUTORS.md file).
|
||||
|
||||
|
||||
### 0.9.9 (2021-07-02)
|
||||
|
||||
- Stegano now uses Pillow 8.3.0.
|
||||
|
||||
|
||||
### 0.9.8 (2019-12-20)
|
||||
|
||||
- Stegano is now using poetry;
|
||||
- minor improvements to the command line.
|
||||
|
||||
|
||||
### 0.9.7 (2019-10-27)
|
||||
|
||||
- fixed markdown of the previous release.
|
||||
|
||||
|
||||
### 0.9.6 (2019-10-27)
|
||||
|
||||
- fixed markdown of the previous release;
|
||||
|
||||
|
||||
### 0.9.5 (2019-10-27)
|
||||
|
||||
- updated dependencies;
|
||||
- home page of the project is now: https://github.com/cedricbonhomme/Stegano
|
||||
|
||||
|
||||
### 0.9.4 (2019-06-05)
|
||||
|
||||
- new: Implemented LFSR generator (with tests and CLI)
|
||||
([PR #27](https://github.com/cedricbonhomme/Stegano/pull/27))
|
||||
- new: Implemented Ackermann generators CLI interface
|
||||
([PR #26](https://github.com/cedricbonhomme/Stegano/pull/26))
|
||||
- new: The Ackermann functions are not actual generators
|
||||
([#24](https://github.com/cedricbonhomme/Stegano/issues/24))
|
||||
- new: add a shift parameter for the lsbmodule
|
||||
([#25](https://github.com/cedricbonhomme/Stegano/issues/25))
|
||||
- fix: lsbset.hide cause .png transparent area lost
|
||||
([#23](https://github.com/cedricbonhomme/Stegano/issues/23))
|
||||
|
||||
|
||||
### 0.9.3 (2019-04-10)
|
||||
|
||||
- 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;
|
||||
- code re-formatted a bit with black.
|
||||
|
||||
|
||||
### 0.9.2 (2019-04-04)
|
||||
|
||||
- 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).
|
||||
|
||||
|
||||
### 0.9.1 (2019-03-06)
|
||||
|
||||
- updated Pillow dependency in order to fix a bug when opening some PNG files.
|
||||
|
||||
|
||||
### 0.9.0 (2018-12-18)
|
||||
|
||||
- added the possibility to shift the encoded bits when using the lsbset module.
|
||||
|
||||
|
||||
### 0.8.6 (2018-11-05)
|
||||
|
||||
- fixed a potential security issue related to CVE-2018-18074.
|
||||
|
||||
|
||||
### 0.8.5 (2018-04-18)
|
||||
|
||||
- Fixed an encoding problem which occured on Windows during the installation
|
||||
of the module.
|
||||
|
||||
|
||||
### 0.8.4 (2018-02-28)
|
||||
|
||||
- Stegano is ready for use with pipenv and pipsi.
|
||||
|
||||
|
||||
### 0.8.3 (2018-02-23)
|
||||
|
||||
- the recommended way to install Stegano is now to use pipenv.
|
||||
|
||||
|
||||
### 0.8.2 (2017-12-20)
|
||||
|
||||
- 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.
|
||||
|
||||
|
||||
### 0.8.1 (2017-05-16)
|
||||
|
||||
- it is now possible to specify the encoding (UTF-8 or UTF-32LE) of the message
|
||||
to hide/reveal through the command line;
|
||||
- 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
|
||||
generators);
|
||||
- tests expected results lies now in a dedicated folder;
|
||||
- a script has been added in order to get proper exit code check for mypy.
|
||||
|
||||
|
||||
### 0.8 (2017-05-06)
|
||||
|
||||
- updated command line. All commands are now prefixed with *stegano-*;
|
||||
- improved type hints;
|
||||
- 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.
|
||||
|
||||
|
||||
### 0.7.1 (2017-05-05)
|
||||
|
||||
- improved generators for the lsb-set module;
|
||||
- improved tests for the generators;
|
||||
- improved type hints.
|
||||
|
||||
|
||||
### 0.7 (2017-05-04)
|
||||
|
||||
- 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
|
||||
since it is possible to hide longer messages with it.
|
||||
- improved checks with type hints.
|
||||
|
||||
|
||||
### 0.6.9 (2017-03-10)
|
||||
|
||||
- introduces some type hints (PEP 484);
|
||||
- more tests for the generators and for the tools module;
|
||||
- updated descriptions of generators;
|
||||
- fixed a bug with a generator that has been previously renamed.
|
||||
|
||||
|
||||
### 0.6.8 (2017-03-08)
|
||||
|
||||
- bugfix: fixed #12: Error when revealing a hidden binary file in an image.
|
||||
|
||||
|
||||
### 0.6.7 (2017-02-21)
|
||||
|
||||
- bugfix: added missing dependency in the setup.py file.
|
||||
|
||||
|
||||
### 0.6.6 (2017-02-20)
|
||||
|
||||
- improved docstrings for the desciption of the generators;
|
||||
- improved the command which displays the list of generators.
|
||||
|
||||
|
||||
### 0.6.5 (2017-02-16)
|
||||
|
||||
- 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.
|
||||
|
||||
|
||||
### 0.6.4 (2017-02-06)
|
||||
|
||||
- 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
|
||||
specified by the user.
|
||||
|
||||
|
||||
### 0.6.3 (2017-01-29)
|
||||
|
||||
- Support for transparent PNG images has been added (lsb and lsbset modules).
|
||||
|
||||
|
||||
### 0.6.2 (2017-01-19)
|
||||
|
||||
- bugfix: solved a bug when the image data is coming via byte streams (ByteIO),
|
||||
for the exifHeader hiding method.
|
||||
|
||||
|
||||
### 0.6.1 (2016-08-25)
|
||||
|
||||
- reorganization of the steganalysis sub-module.
|
||||
|
||||
|
||||
### 0.6 (2016-08-04)
|
||||
|
||||
- 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
|
||||
of the command line). No changes if you use Stéganô as a module in your
|
||||
software. The documentation has been updated accordingly.
|
||||
|
||||
|
||||
### 0.5.5 (2016-08-03)
|
||||
|
||||
- bugfix: Incorrect padding size in `base642string` in tools.base642binary().
|
||||
|
||||
|
||||
### 0.5.4 (2016-05-22)
|
||||
|
||||
- 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
|
||||
generator (not in the module lsbset.generators).
|
||||
- performance improvements for the lsb and lsbset modules.
|
||||
|
||||
|
||||
### 0.5.3 (2016-05-19)
|
||||
|
||||
- reorganization of all modules. No impact for the users of Stegano.
|
||||
|
||||
|
||||
### 0.5.2 (2016-05-18)
|
||||
|
||||
- improvements and bug fixes for the exifHeader module;
|
||||
- added unit tests for the exifHeader module;
|
||||
- improvements of the documentation.
|
||||
|
||||
|
||||
### 0.5.1 (2016-04-16)
|
||||
|
||||
- minor improvements and bug fixes;
|
||||
- added unit tests for the slsb and slsbset modules.
|
||||
|
||||
|
||||
### 0.5 (2016-03-18)
|
||||
|
||||
- management of greyscale images.
|
||||
|
||||
|
||||
### 0.4.6 (2016-03-12)
|
||||
|
||||
- bugfix when the length of the message to hide is not divisible by 3,
|
||||
for the slsb and slsbset module.
|
||||
|
||||
|
||||
### 0.4.5 (2015-12-23)
|
||||
|
||||
- bugfix.
|
||||
|
||||
|
||||
### 0.4.4 (2015-12-23)
|
||||
|
||||
- new project home page;
|
||||
- minor updated to the documentation.
|
||||
|
||||
|
||||
### 0.4.3 (2015-10-06)
|
||||
|
||||
- bug fixes for Python 3;
|
||||
- bug fixes in the scripts in *./bin*.
|
||||
|
||||
|
||||
### 0.4.2 (2015-10-05)
|
||||
|
||||
- first stable release on PypI.
|
||||
|
||||
|
||||
### 0.4 (2012-01-02)
|
||||
|
||||
This release introduces a more advanced LSB (Least Significant Bit) method
|
||||
based on integers sets. The sets generated with Python generators
|
||||
(Sieve of Eratosthenes, Fermat, Carmichael numbers, etc.) are used to select
|
||||
the pixels used to hide the information. You can use these new methods in your
|
||||
Python codes as a Python module or as a program in your scripts.
|
||||
|
||||
|
||||
### 0.3 (2011-04-15)
|
||||
|
||||
- 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
|
||||
in the bin directory;
|
||||
- new documentation (reStructuredText) comes with Stéganô.
|
||||
|
||||
|
||||
### 0.2 (2011-03-24)
|
||||
|
||||
- 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
|
||||
binary file (ogg, executable, etc.);
|
||||
- a new technique for hiding/revealing a message in a JPEG picture by using the
|
||||
description field of the image is provided.
|
198
CHANGELOG.rst
198
CHANGELOG.rst
|
@ -1,198 +0,0 @@
|
|||
Release History
|
||||
===============
|
||||
|
||||
0.8.4 (2018-02-28)
|
||||
------------------
|
||||
* Stegano is ready for use with pipenv and pipsi.
|
||||
|
||||
0.8.3 (2018-02-23)
|
||||
------------------
|
||||
* the recommended way to install Stegano is now to use pipenv.
|
||||
|
||||
0.8.2 (2017-12-20)
|
||||
------------------
|
||||
* 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.
|
||||
|
||||
0.8.1 (2017-05-16)
|
||||
------------------
|
||||
* it is now possible to specify the encoding (UTF-8 or UTF-32LE) of the message
|
||||
to hide/reveal through the command line;
|
||||
* 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
|
||||
generators);
|
||||
* tests expected results lies now in a dedicated folder;
|
||||
* a script has been added in order to get proper exit code check for mypy.
|
||||
|
||||
0.8 (2017-05-06)
|
||||
----------------
|
||||
* updated command line. All commands are now prefixed with *stegano-*;
|
||||
* improved type hints;
|
||||
* 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.
|
||||
|
||||
0.7.1 (2017-05-05)
|
||||
------------------
|
||||
|
||||
* improved generators for the lsb-set module;
|
||||
* improved tests for the generators;
|
||||
* improved type hints.
|
||||
|
||||
0.7 (2017-05-04)
|
||||
----------------
|
||||
|
||||
* 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
|
||||
since it is possible to hide longer messages with it.
|
||||
* improved checks with type hints.
|
||||
|
||||
0.6.9 (2017-03-10)
|
||||
------------------
|
||||
|
||||
* introduces some type hints (PEP 484);
|
||||
* more tests for the generators and for the tools module;
|
||||
* updated descriptions of generators;
|
||||
* fixed a bug with a generator that has been previously renamed.
|
||||
|
||||
0.6.8 (2017-03-08)
|
||||
------------------
|
||||
|
||||
* bugfix: fixed #12: Error when revealing a hidden binary file in an image.
|
||||
|
||||
0.6.7 (2017-02-21)
|
||||
------------------
|
||||
|
||||
* bugfix: added missing dependency in the setup.py file.
|
||||
|
||||
0.6.6 (2017-02-20)
|
||||
------------------
|
||||
|
||||
* improved docstrings for the desciption of the generators;
|
||||
* improved the command which displays the list of generators.
|
||||
|
||||
0.6.5 (2017-02-16)
|
||||
------------------
|
||||
|
||||
* 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.
|
||||
|
||||
|
||||
0.6.4 (2017-02-06)
|
||||
------------------
|
||||
|
||||
* 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
|
||||
specified by the user.
|
||||
|
||||
0.6.3 (2017-01-29)
|
||||
------------------
|
||||
|
||||
* Support for transparent PNG images has been added (lsb and lsbset modules).
|
||||
|
||||
0.6.2 (2017-01-19)
|
||||
------------------
|
||||
|
||||
* bugfix: solved a bug when the image data is coming via byte streams (ByteIO),
|
||||
for the exifHeader hiding method.
|
||||
|
||||
0.6.1 (2016-08-25)
|
||||
------------------
|
||||
|
||||
* reorganization of the steganalysis sub-module.
|
||||
|
||||
0.6 (2016-08-04)
|
||||
------------------
|
||||
|
||||
* 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
|
||||
of the command line). No changes if you use Stéganô as a module in your
|
||||
software. The documentation has been updated accordingly.
|
||||
|
||||
0.5.5 (2016-08-03)
|
||||
------------------
|
||||
|
||||
* bugfix: Incorrect padding size in `base642string` in tools.base642binary().
|
||||
|
||||
0.5.4 (2016-05-22)
|
||||
------------------
|
||||
|
||||
* 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
|
||||
generator (not in the module lsbset.generators).
|
||||
* performance improvements for the lsb and lsbset modules.
|
||||
|
||||
0.5.3 (2016-05-19)
|
||||
------------------
|
||||
|
||||
* reorganization of all modules. No impact for the users of Stegano.
|
||||
|
||||
0.5.2 (2016-05-18)
|
||||
------------------
|
||||
|
||||
* improvements and bug fixes for the exifHeader module;
|
||||
* added unit tests for the exifHeader module;
|
||||
* improvements of the documentation.
|
||||
|
||||
0.5.1 (2016-04-16)
|
||||
------------------
|
||||
|
||||
* minor improvements and bug fixes;
|
||||
* added unit tests for the slsb and slsbset modules.
|
||||
|
||||
0.5 (2016-03-18)
|
||||
----------------
|
||||
|
||||
* management of greyscale images.
|
||||
|
||||
0.4.6 (2016-03-12)
|
||||
------------------
|
||||
|
||||
* bugfix when the length of the message to hide is not divisible by 3,
|
||||
for the slsb and slsbset module.
|
||||
|
||||
0.4.5 (2015-12-23)
|
||||
------------------
|
||||
* bugfix.
|
||||
|
||||
0.4.4 (2015-12-23)
|
||||
------------------
|
||||
|
||||
* new project home page;
|
||||
* minor updated to the documentation.
|
||||
|
||||
0.4.3 (2015-10-06)
|
||||
------------------
|
||||
|
||||
* bug fixes for Python 3;
|
||||
* bug fixes in the scripts in *./bin*.
|
||||
|
||||
0.4.2 (2015-10-05)
|
||||
------------------
|
||||
|
||||
* first stable release on PypI.
|
||||
|
||||
0.4 (2012-01-02)
|
||||
----------------
|
||||
|
||||
This release introduces a more advanced LSB (Least Significant Bit) method
|
||||
based on integers sets. The sets generated with Python generators
|
||||
(Sieve of Eratosthenes, Fermat, Carmichael numbers, etc.) are used to select
|
||||
the pixels used to hide the information. You can use these new methods in your
|
||||
Python codes as a Python module or as a program in your scripts.
|
||||
|
||||
0.3 (2011-04-15)
|
||||
----------------
|
||||
|
||||
* 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
|
||||
in the bin directory;
|
||||
* new documentation (reStructuredText) comes with Stéganô.
|
||||
|
||||
0.2 (2011-03-24)
|
||||
----------------
|
||||
|
||||
* 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
|
||||
binary file (ogg, executable, etc.);
|
||||
* a new technique for hiding/revealing a message in a JPEG picture by using the
|
||||
description field of the image is provided.
|
104
CONTRIBUTING.rst
104
CONTRIBUTING.rst
|
@ -1,104 +0,0 @@
|
|||
Contribution Guidelines
|
||||
=======================
|
||||
|
||||
Before opening proposing any pull requests (about code or documentation),
|
||||
please open an issue.
|
||||
|
||||
Code Contributions
|
||||
------------------
|
||||
|
||||
When contributing code, you'll want to follow this checklist:
|
||||
|
||||
1. Fork the repository on GitHub.
|
||||
2. Run the tests to confirm they all pass on your system. If they don't, you'll
|
||||
need to investigate why they fail.
|
||||
3. Write tests that demonstrate your bug or feature. Ensure that they fail.
|
||||
4. Make your change.
|
||||
5. Run the entire test suite again, confirming that all tests pass *including
|
||||
the ones you just added*.
|
||||
6. Send a GitHub Pull Request to the main repository's ``master`` branch.
|
||||
GitHub Pull Requests are the expected method of code collaboration on this
|
||||
project.
|
||||
|
||||
Code Style
|
||||
~~~~~~~~~~
|
||||
|
||||
Please try to respect the `PEP 8`_ code style.
|
||||
|
||||
|
||||
To get the greatest chance of helpful responses, please also observe the
|
||||
following additional notes.
|
||||
|
||||
.. _PEP 8: http://pep8.org
|
||||
|
||||
|
||||
Documentation Contributions
|
||||
---------------------------
|
||||
|
||||
Documentation improvements are always welcome! The documentation files live in
|
||||
the ``docs/`` directory of the codebase. They're written in
|
||||
`reStructuredText`_, and use `Sphinx`_ to generate the full suite of
|
||||
documentation.
|
||||
|
||||
When contributing documentation, please do your best to follow the style of the
|
||||
documentation files. This means a soft-limit of 79 characters wide in your text
|
||||
files and a semi-formal, yet friendly and approachable, prose style.
|
||||
|
||||
When presenting Python code, use single-quoted strings (``'hello'`` instead of
|
||||
``"hello"``).
|
||||
|
||||
.. _reStructuredText: http://docutils.sourceforge.net/rst.html
|
||||
.. _Sphinx: http://sphinx-doc.org/index.html
|
||||
|
||||
|
||||
|
||||
Good Bug Reports
|
||||
----------------
|
||||
|
||||
Please be aware of the following things when filing bug reports:
|
||||
|
||||
1. Avoid raising duplicate issues. *Please* use the GitHub issue search feature
|
||||
to check whether your bug report or feature request has been mentioned in
|
||||
the past. Duplicate bug reports and feature requests are a huge maintenance
|
||||
burden on the limited resources of the project. If it is clear from your
|
||||
report that you would have struggled to find the original, that's ok, but
|
||||
if searching for a selection of words in your issue title would have found
|
||||
the duplicate then the issue will likely be closed extremely abruptly.
|
||||
2. When filing bug reports about exceptions or tracebacks, please include the
|
||||
*complete* traceback. Partial tracebacks, or just the exception text, are
|
||||
not helpful. Issues that do not contain complete tracebacks may be closed
|
||||
without warning.
|
||||
3. Make sure you provide a suitable amount of information to work with. This
|
||||
means you should provide:
|
||||
|
||||
- Guidance on **how to reproduce the issue**. Ideally, this should be a
|
||||
*small* code sample that can be run immediately by the maintainers.
|
||||
Failing that, let us know what you're doing, how often it happens, what
|
||||
environment you're using, etc. Be thorough: it prevents us needing to ask
|
||||
further questions.
|
||||
- Tell us **what you expected to happen**. When we run your example code,
|
||||
what are we expecting to happen? What does "success" look like for your
|
||||
code?
|
||||
- Tell us **what actually happens**. It's not helpful for you to say "it
|
||||
doesn't work" or "it fails". Tell us *how* it fails: do you get an
|
||||
exception? A hang? A non-200 status code? How was the actual result
|
||||
different from your expected result?
|
||||
- Tell us **what version of Stegano you're using**, and
|
||||
**how you installed it**. Different versions of Stegano behave
|
||||
differently and have different bugs.
|
||||
|
||||
If you do not provide all of these things, it will take us much longer to
|
||||
fix your problem. If we ask you to clarify these and you never respond, we
|
||||
will close your issue without fixing it.
|
||||
|
||||
|
||||
Questions
|
||||
=========
|
||||
|
||||
The GitHub issue tracker is for *bug reports* and *feature requests*. Please do
|
||||
not use it to ask questions about how to use Stegano. These questions should
|
||||
instead be directed to Stack Overflow. Make sure
|
||||
that your question is tagged with the `python-stegano` tag when asking it on
|
||||
Stack Overflow, to ensure that it is answered promptly and accurately.
|
||||
You can search for questions with
|
||||
`these tags <http://stackoverflow.com/questions/tagged/python+steganography>`_.
|
21
CONTRIBUTORS.md
Normal file
21
CONTRIBUTORS.md
Normal file
|
@ -0,0 +1,21 @@
|
|||
## Owner
|
||||
|
||||
|
||||
- Cédric Bonhomme <cedric@cedricbonhomme.org>
|
||||
|
||||
|
||||
## Contributors
|
||||
|
||||
|
||||
- Adrien Cosson - https://cosson.io
|
||||
- Andrew Roberts <andy.roberts.uk@gmail.com>
|
||||
- Christophe Goessen - https://github.com/cgoessen
|
||||
- Flavien Roux - https://github.com/FlavienRx
|
||||
- Maxwell Gerber - https://github.com/maxwellgerber
|
||||
- Nejdet Çağdaş Yücesoy <nejdetyucesoy@gmail.com>
|
||||
- panni <panni@fragstore.net>
|
||||
- Peter Justin <peter@peterjustin.me>
|
||||
- thundersparkf - https://github.com/thundersparkf
|
||||
- Mickaël Schoentgen <mschoentgen@nuxeo.com>
|
||||
|
||||
And thank you to the testers!
|
|
@ -1,14 +0,0 @@
|
|||
Owner
|
||||
=====
|
||||
|
||||
- Cédric Bonhomme <cedric@cedricbonhomme.org>
|
||||
|
||||
Contributors
|
||||
============
|
||||
|
||||
- Andrew Roberts <andy.roberts.uk@gmail.com>
|
||||
- Maxwell Gerber
|
||||
- Nejdet Çağdaş Yücesoy <nejdetyucesoy@gmail.com>
|
||||
- panni <panni@fragstore.net>
|
||||
|
||||
Thank you to the testers!
|
2
COPYING
2
COPYING
|
@ -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
|
||||
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
|
||||
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
|
||||
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
|
||||
|
|
10
MANIFEST.in
10
MANIFEST.in
|
@ -1,10 +0,0 @@
|
|||
#documentation
|
||||
recursive-include docs *
|
||||
|
||||
# binary files
|
||||
recursive-include bin *
|
||||
|
||||
#Misc
|
||||
include COPYING
|
||||
include README.rst
|
||||
include CHANGELOG.rst
|
25
Pipfile
25
Pipfile
|
@ -1,25 +0,0 @@
|
|||
[[source]]
|
||||
|
||||
url = "https://pypi.python.org/simple"
|
||||
verify_ssl = true
|
||||
name = "pypi"
|
||||
|
||||
|
||||
[requires]
|
||||
python_version = "3.6"
|
||||
|
||||
|
||||
[packages]
|
||||
|
||||
Pillow = "*"
|
||||
piexif = "*"
|
||||
crayons = "*"
|
||||
|
||||
|
||||
[dev-packages]
|
||||
|
||||
twine = "*"
|
||||
pep8 = "*"
|
||||
coverage = "*"
|
||||
coveralls = "*"
|
||||
mypy = "*"
|
270
Pipfile.lock
generated
270
Pipfile.lock
generated
|
@ -1,270 +0,0 @@
|
|||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "6ceb5a53404f1153a67e9b1474f3a98c00acc70b8190aba9a9b34fb8f1339a87"
|
||||
},
|
||||
"host-environment-markers": {
|
||||
"implementation_name": "cpython",
|
||||
"implementation_version": "3.6.4",
|
||||
"os_name": "posix",
|
||||
"platform_machine": "x86_64",
|
||||
"platform_python_implementation": "CPython",
|
||||
"platform_release": "4.13.0-36-generic",
|
||||
"platform_system": "Linux",
|
||||
"platform_version": "#40-Ubuntu SMP Fri Feb 16 20:07:48 UTC 2018",
|
||||
"python_full_version": "3.6.4",
|
||||
"python_version": "3.6",
|
||||
"sys_platform": "linux"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {
|
||||
"python_version": "3.6"
|
||||
},
|
||||
"sources": [
|
||||
{
|
||||
"name": "pypi",
|
||||
"url": "https://pypi.python.org/simple",
|
||||
"verify_ssl": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"default": {
|
||||
"colorama": {
|
||||
"hashes": [
|
||||
"sha256:463f8483208e921368c9f306094eb6f725c6ca42b0f97e313cb5d5512459feda",
|
||||
"sha256:48eb22f4f8461b1df5734a074b57042430fb06e1d61bd1e11b078c0fe6d7a1f1"
|
||||
],
|
||||
"version": "==0.3.9"
|
||||
},
|
||||
"crayons": {
|
||||
"hashes": [
|
||||
"sha256:6f51241d0c4faec1c04c1c0ac6a68f1d66a4655476ce1570b3f37e5166a599cc",
|
||||
"sha256:5e17691605e564d63482067eb6327d01a584bbaf870beffd4456a3391bd8809d"
|
||||
],
|
||||
"version": "==0.1.2"
|
||||
},
|
||||
"piexif": {
|
||||
"hashes": [
|
||||
"sha256:47befcbc9a6ee171a0b8c35b981eb02df2120427eb19418d257450b055c622ac",
|
||||
"sha256:1d3dde03bd6298393645bc11d585b67a6ea98fd7e9e1aded6d5d6ec3e4cfbdda"
|
||||
],
|
||||
"version": "==1.0.13"
|
||||
},
|
||||
"pillow": {
|
||||
"hashes": [
|
||||
"sha256:718ec7a122b28d64afc5fbc3a9b99bb0545ef511373cac06fe7624520e82cb20",
|
||||
"sha256:801cca8923508311bf5d6d0f7da5362552e8208ebd8ec0d7b9f2cd2ff5705734",
|
||||
"sha256:43334f9581cd067945b8898cef9eb5714ee4883f8de0304c011f1dbdb1d4e2aa",
|
||||
"sha256:153ec6f18f7b61641e0e6e502acfaf4a06c9aba2ea11c0b4b3578ea9f13a4a4a",
|
||||
"sha256:25193f934d37d836a6b1f4c062ce574a96cbca7c6d9dc8ddfbbac7f9c54deaa4",
|
||||
"sha256:b85f703c2ffe539313e39ce0676bed0f355cec45a16e58c9ab7417445843047c",
|
||||
"sha256:8580fc58074a16b749905b26cf8363f7b628dd167ba0130f5382cdc91c86b509",
|
||||
"sha256:2fcde9954c8882d1c7f93bb828caa34a4c5e3ee69dbc7895dc8652ad972b455a",
|
||||
"sha256:1a5b93084e01328a1cb1ecdad99d11d75e881e89a95f88d85b523646553b36c2",
|
||||
"sha256:b2240f298482f823576f397bb9f32ea913ad9456c526e141bc6f0a022b37a3e8",
|
||||
"sha256:b1d33c63a55d0d85df0ad02b2c16158fb4d8153afa7b908f1a67330fac694cd6",
|
||||
"sha256:6977cf073d83358b34f93abf5c1f1193b88675fe0e4441e0e28318bc3dcba7a0",
|
||||
"sha256:1912b7230459fd53682dae32b83cbd8e5d642ba36d4be18566f00a9c063aa13d",
|
||||
"sha256:4bd4a71501b6d51db4abc07e1f43f5a6fed0a1a9583cca0b401d6af50284b0db",
|
||||
"sha256:0013f590a8f260df60bcfd65db19d18efc04e7f046c3c82a40e2e2b3292a937c",
|
||||
"sha256:a224651a81e45ef4f1d0164e256c5f6b4abb49f2ae8f22ba2f3a9d0ff338e608",
|
||||
"sha256:c793dfaa130847ccff958492b76ae8b9304e60b8a79a92962cb19e368276a22b",
|
||||
"sha256:0b899ee80920bb533f26581af9b4660bc12aff4562555afe74e429101ebf3c94",
|
||||
"sha256:9525cd680a6f9e80c6c0af03cf973e6505c59f60b4745f682cd1a449e54b31bb",
|
||||
"sha256:35f7d998b8e82fb3fb51ff88b30485eb81cd7dd56ec7e1a8deba23eb88532d44",
|
||||
"sha256:5b0d657460d9f3615876fec6306e97ca15a471f6169b622d76a47e270998acf1",
|
||||
"sha256:ddd16ab250b4fc97db1c47407e78c25216a75c29d29d10ad37e51b7a2ec7b2c3",
|
||||
"sha256:b9f63451084a718eccdeb1e382768c94647915653af4d6019f64560d9e98642b",
|
||||
"sha256:a370d1c570f1d72e877099651e752332444b1c5009381f043c9da5fd47f3ebae",
|
||||
"sha256:dc4b018d5c9b636f7546583c5591b9ea00c328c3e5871992ef5b95bac353f097",
|
||||
"sha256:e126ff4fed71e78333840c07279e1617f63cfca76d63ad5b27d65a7277206a3d",
|
||||
"sha256:fcf64c91fd44485100a2965d23bb0e227d093e91f7e776c5ca3b32574766eb56",
|
||||
"sha256:2c042352b430d678db50c78c5214e19638eff8b688941271da2de21fd298dfe5",
|
||||
"sha256:17fe25efc785194d48c38fad85dce470013ba19d2fb66639e149f14bccf1327f",
|
||||
"sha256:2e818dbe445e86fc6c266973fe540c35125c42eb2cf13a6095e9adaa89c0deb5",
|
||||
"sha256:135e9aa65150c53f7db85bf2bebb8a0e1a48ea850e80cf66e16dd04fa09d309c",
|
||||
"sha256:7dfbefdb3fb911ca9faed307bf309861e9995e36cca6b761c7ba6d9b77a9744a",
|
||||
"sha256:12f29d6c23424f704c66b5b68c02fe0b571504459605cfe36ab8158359b0e1bb",
|
||||
"sha256:f8d49be8c282df8d2e1ab6ab53ab8abd859b1fa6fed384457ee85c9eff64ef97",
|
||||
"sha256:82b172e3264e62372c01b5b009b5b1a02fbb9276cbe5cc57ab00a6d6e5ed9a18",
|
||||
"sha256:57aa6198ba8acba1313c3b743e267d821a60cac77e6026caf0b55ca58d3d23be",
|
||||
"sha256:d60c1625b108432ace8b1fa1a584017e5efa73f107d0f493c7f39c79bebf1d41",
|
||||
"sha256:82d1ff571489765df2816785d532e243bde213752156c227fca595723ec5ff42",
|
||||
"sha256:37cc0339abfa9e295c75d9a7f227d35cb44716feb95057f9449c4a9e9a17daf7",
|
||||
"sha256:931030d1d6282b7900e6b0a7ff9ecdb503b5e1e6781800dab2b71a9f39405bff",
|
||||
"sha256:5cd36804f9f06a914a883fe682df5711d16d7b4f44d43189c5f013e7cd91e149"
|
||||
],
|
||||
"version": "==5.0.0"
|
||||
}
|
||||
},
|
||||
"develop": {
|
||||
"certifi": {
|
||||
"hashes": [
|
||||
"sha256:14131608ad2fd56836d33a71ee60fa1c82bc9d2c8d98b7bdbc631fe1b3cd1296",
|
||||
"sha256:edbc3f203427eef571f79a7692bb160a2b0f7ccaa31953e99bd17e307cf63f7d"
|
||||
],
|
||||
"version": "==2018.1.18"
|
||||
},
|
||||
"chardet": {
|
||||
"hashes": [
|
||||
"sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691",
|
||||
"sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae"
|
||||
],
|
||||
"version": "==3.0.4"
|
||||
},
|
||||
"coverage": {
|
||||
"hashes": [
|
||||
"sha256:7608a3dd5d73cb06c531b8925e0ef8d3de31fed2544a7de6c63960a1e73ea4bc",
|
||||
"sha256:3a2184c6d797a125dca8367878d3b9a178b6fdd05fdc2d35d758c3006a1cd694",
|
||||
"sha256:f3f501f345f24383c0000395b26b726e46758b71393267aeae0bd36f8b3ade80",
|
||||
"sha256:0b136648de27201056c1869a6c0d4e23f464750fd9a9ba9750b8336a244429ed",
|
||||
"sha256:337ded681dd2ef9ca04ef5d93cfc87e52e09db2594c296b4a0a3662cb1b41249",
|
||||
"sha256:3eb42bf89a6be7deb64116dd1cc4b08171734d721e7a7e57ad64cc4ef29ed2f1",
|
||||
"sha256:be6cfcd8053d13f5f5eeb284aa8a814220c3da1b0078fa859011c7fffd86dab9",
|
||||
"sha256:69bf008a06b76619d3c3f3b1983f5145c75a305a0fea513aca094cae5c40a8f5",
|
||||
"sha256:2eb564bbf7816a9d68dd3369a510be3327f1c618d2357fa6b1216994c2e3d508",
|
||||
"sha256:9d6dd10d49e01571bf6e147d3b505141ffc093a06756c60b053a859cb2128b1f",
|
||||
"sha256:701cd6093d63e6b8ad7009d8a92425428bc4d6e7ab8d75efbb665c806c1d79ba",
|
||||
"sha256:5a13ea7911ff5e1796b6d5e4fbbf6952381a611209b736d48e675c2756f3f74e",
|
||||
"sha256:c1bb572fab8208c400adaf06a8133ac0712179a334c09224fb11393e920abcdd",
|
||||
"sha256:03481e81d558d30d230bc12999e3edffe392d244349a90f4ef9b88425fac74ba",
|
||||
"sha256:28b2191e7283f4f3568962e373b47ef7f0392993bb6660d079c62bd50fe9d162",
|
||||
"sha256:de4418dadaa1c01d497e539210cb6baa015965526ff5afc078c57ca69160108d",
|
||||
"sha256:8c3cb8c35ec4d9506979b4cf90ee9918bc2e49f84189d9bf5c36c0c1119c6558",
|
||||
"sha256:7e1fe19bd6dce69d9fd159d8e4a80a8f52101380d5d3a4d374b6d3eae0e5de9c",
|
||||
"sha256:6bc583dc18d5979dc0f6cec26a8603129de0304d5ae1f17e57a12834e7235062",
|
||||
"sha256:198626739a79b09fa0a2f06e083ffd12eb55449b5f8bfdbeed1df4910b2ca640",
|
||||
"sha256:7aa36d2b844a3e4a4b356708d79fd2c260281a7390d678a10b91ca595ddc9e99",
|
||||
"sha256:3d72c20bd105022d29b14a7d628462ebdc61de2f303322c0212a054352f3b287",
|
||||
"sha256:4635a184d0bbe537aa185a34193898eee409332a8ccb27eea36f262566585000",
|
||||
"sha256:e05cb4d9aad6233d67e0541caa7e511fa4047ed7750ec2510d466e806e0255d6",
|
||||
"sha256:76ecd006d1d8f739430ec50cc872889af1f9c1b6b8f48e29941814b09b0fd3cc",
|
||||
"sha256:7d3f553904b0c5c016d1dad058a7554c7ac4c91a789fca496e7d8347ad040653",
|
||||
"sha256:3c79a6f7b95751cdebcd9037e4d06f8d5a9b60e4ed0cd231342aa8ad7124882a",
|
||||
"sha256:56e448f051a201c5ebbaa86a5efd0ca90d327204d8b059ab25ad0f35fbfd79f1",
|
||||
"sha256:ac4fef68da01116a5c117eba4dd46f2e06847a497de5ed1d64bb99a5fda1ef91",
|
||||
"sha256:1c383d2ef13ade2acc636556fd544dba6e14fa30755f26812f54300e401f98f2",
|
||||
"sha256:b8815995e050764c8610dbc82641807d196927c3dbed207f0a079833ffcf588d",
|
||||
"sha256:104ab3934abaf5be871a583541e8829d6c19ce7bde2923b2751e0d3ca44db60a",
|
||||
"sha256:9e112fcbe0148a6fa4f0a02e8d58e94470fc6cb82a5481618fea901699bf34c4",
|
||||
"sha256:15b111b6a0f46ee1a485414a52a7ad1d703bdf984e9ed3c288a4414d3871dcbd",
|
||||
"sha256:e4d96c07229f58cb686120f168276e434660e4358cc9cf3b0464210b04913e77",
|
||||
"sha256:f8a923a85cb099422ad5a2e345fe877bbc89a8a8b23235824a93488150e45f6e"
|
||||
],
|
||||
"version": "==4.5.1"
|
||||
},
|
||||
"coveralls": {
|
||||
"hashes": [
|
||||
"sha256:84dd8c88c5754e8db70a682f537e2781366064aa3cdd6b24c2dcecbd3181187c",
|
||||
"sha256:510682001517bcca1def9f6252df6ce730fcb9831c62d9fff7c7d55b6fdabdf3"
|
||||
],
|
||||
"version": "==1.2.0"
|
||||
},
|
||||
"docopt": {
|
||||
"hashes": [
|
||||
"sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491"
|
||||
],
|
||||
"version": "==0.6.2"
|
||||
},
|
||||
"idna": {
|
||||
"hashes": [
|
||||
"sha256:8c7309c718f94b3a625cb648ace320157ad16ff131ae0af362c9f21b80ef6ec4",
|
||||
"sha256:2c6a5de3089009e3da7c5dde64a141dbc8551d5b7f6cf4ed7c2568d0cc520a8f"
|
||||
],
|
||||
"version": "==2.6"
|
||||
},
|
||||
"mypy": {
|
||||
"hashes": [
|
||||
"sha256:aa668809ae0dbec5e9feb8929f4b5e1f9318a0a397447fa2f38c382a2ed6a036",
|
||||
"sha256:bd0c9a2fcf0c4f7a54a2b625f466fcc000d415f371298d96fa5d2acc69074aca"
|
||||
],
|
||||
"version": "==0.560"
|
||||
},
|
||||
"pep8": {
|
||||
"hashes": [
|
||||
"sha256:b22cfae5db09833bb9bd7c8463b53e1a9c9b39f12e304a8d0bba729c501827ee",
|
||||
"sha256:fe249b52e20498e59e0b5c5256aa52ee99fc295b26ec9eaa85776ffdb9fe6374"
|
||||
],
|
||||
"version": "==1.7.1"
|
||||
},
|
||||
"pkginfo": {
|
||||
"hashes": [
|
||||
"sha256:31a49103180ae1518b65d3f4ce09c784e2bc54e338197668b4fb7dc539521024",
|
||||
"sha256:bb1a6aeabfc898f5df124e7e00303a5b3ec9a489535f346bfbddb081af93f89e"
|
||||
],
|
||||
"version": "==1.4.1"
|
||||
},
|
||||
"psutil": {
|
||||
"hashes": [
|
||||
"sha256:82a06785db8eeb637b349006cc28a92e40cd190fefae9875246d18d0de7ccac8",
|
||||
"sha256:4152ae231709e3e8b80e26b6da20dc965a1a589959c48af1ed024eca6473f60d",
|
||||
"sha256:230eeb3aeb077814f3a2cd036ddb6e0f571960d327298cc914c02385c3e02a63",
|
||||
"sha256:a3286556d4d2f341108db65d8e20d0cd3fcb9a91741cb5eb496832d7daf2a97c",
|
||||
"sha256:94d4e63189f2593960e73acaaf96be235dd8a455fe2bcb37d8ad6f0e87f61556",
|
||||
"sha256:c91eee73eea00df5e62c741b380b7e5b6fdd553891bee5669817a3a38d036f13",
|
||||
"sha256:779ec7e7621758ca11a8d99a1064996454b3570154277cc21342a01148a49c28",
|
||||
"sha256:8a15d773203a1277e57b1d11a7ccdf70804744ef4a9518a87ab8436995c31a4b",
|
||||
"sha256:e2467e9312c2fa191687b89ff4bc2ad8843be4af6fb4dc95a7cc5f7d7a327b18"
|
||||
],
|
||||
"version": "==5.4.3"
|
||||
},
|
||||
"requests": {
|
||||
"hashes": [
|
||||
"sha256:6a1b267aa90cac58ac3a765d067950e7dbbf75b1da07e895d1f594193a40a38b",
|
||||
"sha256:9c443e7324ba5b85070c4a818ade28bfabedf16ea10206da1132edaa6dda237e"
|
||||
],
|
||||
"version": "==2.18.4"
|
||||
},
|
||||
"requests-toolbelt": {
|
||||
"hashes": [
|
||||
"sha256:42c9c170abc2cacb78b8ab23ac957945c7716249206f90874651971a4acff237",
|
||||
"sha256:f6a531936c6fa4c6cfce1b9c10d5c4f498d16528d2a54a22ca00011205a187b5"
|
||||
],
|
||||
"version": "==0.8.0"
|
||||
},
|
||||
"tqdm": {
|
||||
"hashes": [
|
||||
"sha256:f66468c14ccd011a627734c9b3fd72f20ce16f8faecc47384eb2507af5924fb9",
|
||||
"sha256:5ec0d4442358e55cdb4a0471d04c6c831518fd8837f259db5537d90feab380df"
|
||||
],
|
||||
"version": "==4.19.6"
|
||||
},
|
||||
"twine": {
|
||||
"hashes": [
|
||||
"sha256:d3ce5c480c22ccfb761cd358526e862b32546d2fe4bc93d46b5cf04ea3cc46ca",
|
||||
"sha256:caa45b7987fc96321258cd7668e3be2ff34064f5c66d2d975b641adca659c1ab"
|
||||
],
|
||||
"version": "==1.9.1"
|
||||
},
|
||||
"typed-ast": {
|
||||
"hashes": [
|
||||
"sha256:0948004fa228ae071054f5208840a1e88747a357ec1101c17217bfe99b299d58",
|
||||
"sha256:25d8feefe27eb0303b73545416b13d108c6067b846b543738a25ff304824ed9a",
|
||||
"sha256:c05b41bc1deade9f90ddc5d988fe506208019ebba9f2578c622516fd201f5863",
|
||||
"sha256:519425deca5c2b2bdac49f77b2c5625781abbaf9a809d727d3a5596b30bb4ded",
|
||||
"sha256:6de012d2b166fe7a4cdf505eee3aaa12192f7ba365beeefaca4ec10e31241a85",
|
||||
"sha256:79b91ebe5a28d349b6d0d323023350133e927b4de5b651a8aa2db69c761420c6",
|
||||
"sha256:a8034021801bc0440f2e027c354b4eafd95891b573e12ff0418dec385c76785c",
|
||||
"sha256:f19f2a4f547505fe9072e15f6f4ae714af51b5a681a97f187971f50c283193b6",
|
||||
"sha256:c9b060bd1e5a26ab6e8267fd46fc9e02b54eb15fffb16d112d4c7b1c12987559",
|
||||
"sha256:2e214b72168ea0275efd6c884b114ab42e316de3ffa125b267e732ed2abda892",
|
||||
"sha256:bc978ac17468fe868ee589c795d06777f75496b1ed576d308002c8a5756fb9ea",
|
||||
"sha256:edb04bdd45bfd76c8292c4d9654568efaedf76fe78eb246dde69bdb13b2dad87",
|
||||
"sha256:668d0cec391d9aed1c6a388b0d5b97cd22e6073eaa5fbaa6d2946603b4871efe",
|
||||
"sha256:29464a177d56e4e055b5f7b629935af7f49c196be47528cc94e0a7bf83fbc2b9",
|
||||
"sha256:8550177fa5d4c1f09b5e5f524411c44633c80ec69b24e0e98906dd761941ca46",
|
||||
"sha256:3e0d5e48e3a23e9a4d1a9f698e32a542a4a288c871d33ed8df1b092a40f3a0f9",
|
||||
"sha256:68ba70684990f59497680ff90d18e756a47bf4863c604098f10de9716b2c0bdd",
|
||||
"sha256:57fe287f0cdd9ceaf69e7b71a2e94a24b5d268b35df251a88fef5cc241bf73aa"
|
||||
],
|
||||
"version": "==1.1.0"
|
||||
},
|
||||
"urllib3": {
|
||||
"hashes": [
|
||||
"sha256:06330f386d6e4b195fbfc736b297f58c5a892e4440e54d294d7004e3a9bbea1b",
|
||||
"sha256:cc44da8e1145637334317feebd728bd869a35285b93cbb4cca2577da7e62db4f"
|
||||
],
|
||||
"version": "==1.22"
|
||||
}
|
||||
}
|
||||
}
|
115
README.md
Normal file
115
README.md
Normal file
|
@ -0,0 +1,115 @@
|
|||
# Stegano
|
||||
|
||||
[](https://github.com/cedricbonhomme/Stegano/actions?query=workflow%3A%22Python+application%22)
|
||||
|
||||
[Stegano](https://github.com/cedricbonhomme/Stegano), a pure Python Steganography
|
||||
module.
|
||||
|
||||
Steganography is the art and science of writing hidden messages in such a way
|
||||
that no one, apart from the sender and intended recipient, suspects the
|
||||
existence of the message, a form of security through obscurity. Consequently,
|
||||
functions provided by Stegano only hide messages, without encryption.
|
||||
Steganography is often used with cryptography.
|
||||
|
||||
|
||||
## Installation
|
||||
|
||||
|
||||
```bash
|
||||
$ poetry install stegano
|
||||
```
|
||||
|
||||
You will be able to use Stegano in your Python programs.
|
||||
|
||||
If you only want to install Stegano as a command line tool:
|
||||
|
||||
```bash
|
||||
$ pipx install stegano
|
||||
```
|
||||
|
||||
pipx installs scripts (system wide available) provided by Python packages into
|
||||
separate virtualenvs to shield them from your system and each other.
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
A [tutorial](https://stegano.readthedocs.io) is available.
|
||||
|
||||
|
||||
## Use Stegano as a library in your Python program
|
||||
|
||||
If you want to use Stegano in your Python program you just have to import the
|
||||
appropriate steganography technique. For example:
|
||||
|
||||
```python
|
||||
>>> 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")
|
||||
```
|
||||
|
||||
|
||||
## Use Stegano as a command line tool
|
||||
|
||||
### Hide and reveal a message
|
||||
|
||||
```bash
|
||||
$ stegano-lsb hide -i ./tests/sample-files/Lenna.png -m "Secret Message" -o Lena1.png
|
||||
$ stegano-lsb reveal -i Lena1.png
|
||||
Secret Message
|
||||
```
|
||||
|
||||
|
||||
### Hide the message with the Sieve of Eratosthenes
|
||||
|
||||
```bash
|
||||
$ stegano-lsb hide -i ./tests/sample-files/Lenna.png -m 'Secret Message' --generator eratosthenes -o Lena2.png
|
||||
```
|
||||
|
||||
The message will be scattered in the picture, following a set described by the
|
||||
Sieve of Eratosthenes. Other sets are available. You can also use your own
|
||||
generators.
|
||||
|
||||
This will make a steganalysis more complicated.
|
||||
|
||||
|
||||
## Running the tests
|
||||
|
||||
```bash
|
||||
$ python -m unittest discover -v
|
||||
```
|
||||
|
||||
Running the static type checker:
|
||||
|
||||
```bash
|
||||
$ mypy stegano
|
||||
```
|
||||
|
||||
|
||||
## Contributions
|
||||
|
||||
Contributions are welcome. If you want to contribute to Stegano I highly
|
||||
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:
|
||||
|
||||
[](https://github.com/sponsors/cedricbonhomme)
|
||||
|
||||
or with Bitcoin to this address:
|
||||
bc1q56u6sj7cvlwu58v5lemljcvkh7v2gc3tv8mj0e
|
||||
|
||||
Thank you !
|
||||
|
||||
|
||||
## License
|
||||
|
||||
This software is licensed under
|
||||
[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)
|
||||
|
||||
For more information, [the list of authors and contributors](CONTRIBUTORS.md) is available.
|
133
README.rst
133
README.rst
|
@ -1,133 +0,0 @@
|
|||
Stegano
|
||||
=======
|
||||
|
||||
.. image:: https://img.shields.io/pypi/pyversions/Stegano.svg?style=flat-square
|
||||
:target: https://pypi.python.org/pypi/Stegano
|
||||
|
||||
.. image:: https://img.shields.io/pypi/v/Stegano.svg?style=flat-square
|
||||
:target: https://github.com/cedricbonhomme/Stegano/releases/latest
|
||||
|
||||
.. image:: https://img.shields.io/pypi/l/Stegano.svg?style=flat-square
|
||||
:target: https://www.gnu.org/licenses/gpl-3.0.html
|
||||
|
||||
.. image:: https://img.shields.io/travis/cedricbonhomme/Stegano/master.svg?style=flat-square
|
||||
:target: https://travis-ci.org/cedricbonhomme/Stegano
|
||||
|
||||
.. image:: https://img.shields.io/coveralls/cedricbonhomme/Stegano/master.svg?style=flat-square
|
||||
:target: https://coveralls.io/github/cedricbonhomme/Stegano?branch=master
|
||||
|
||||
.. image:: https://img.shields.io/github/stars/cedricbonhomme/Stegano.svg?style=flat-square
|
||||
:target: https://github.com/cedricbonhomme/Stegano/stargazers
|
||||
|
||||
.. image:: https://img.shields.io/badge/SayThanks.io-%E2%98%BC-1EAEDB.svg?style=flat-square
|
||||
:target: https://saythanks.io/to/cedricbonhomme
|
||||
|
||||
|
||||
`Stegano <https://github.com/cedricbonhomme/Stegano>`_, a pure Python
|
||||
Steganography module.
|
||||
|
||||
Steganography is the art and science of writing hidden messages in such a way
|
||||
that no one, apart from the sender and intended recipient, suspects the
|
||||
existence of the message, a form of security through obscurity. Consequently,
|
||||
functions provided by Stegano only hide messages, without encryption.
|
||||
Steganography is often used with cryptography.
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
.. code:: bash
|
||||
|
||||
$ pipenv install Stegano
|
||||
✨🐍✨
|
||||
|
||||
You will be able to use Stegano in your Python programs.
|
||||
|
||||
If you only want to install Stegano as a command line tool:
|
||||
|
||||
.. code:: bash
|
||||
|
||||
$ pipsi install Stegano
|
||||
✨🍰✨
|
||||
|
||||
pipsi installs scripts (system wide available) provided by Python packages into
|
||||
separate virtualenvs to shield them from your system and each other.
|
||||
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
A `tutorial <https://stegano.readthedocs.io>`_ is available.
|
||||
|
||||
|
||||
Use Stegano as a library in your Python program
|
||||
'''''''''''''''''''''''''''''''''''''''''''''''
|
||||
|
||||
If you want to use Stegano in your Python program you just have to import the
|
||||
appropriate steganography technique. For example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
>>> 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")
|
||||
|
||||
|
||||
Use Stegano as a command line tool
|
||||
''''''''''''''''''''''''''''''''''
|
||||
|
||||
Hide and reveal a message
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. code:: bash
|
||||
|
||||
$ stegano-lsb hide -i ./tests/sample-files/Lenna.png -m "Secret Message" -o Lena1.png
|
||||
$ stegano-lsb reveal -i Lena1.png
|
||||
Secret Message
|
||||
|
||||
|
||||
Hide the message with the Sieve of Eratosthenes
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. code:: bash
|
||||
|
||||
$ stegano-lsb-set hide -i ./tests/sample-files/Lenna.png -m 'Secret Message' --generator eratosthenes -o Lena2.png
|
||||
|
||||
The message will be scattered in the picture, following a set described by the
|
||||
Sieve of Eratosthenes. Other sets are available. You can also use your own
|
||||
generators.
|
||||
|
||||
This will make a steganalysis more complicated.
|
||||
|
||||
|
||||
Running the tests
|
||||
-----------------
|
||||
|
||||
.. code:: bash
|
||||
|
||||
$ python -m unittest discover -v
|
||||
|
||||
Running the static type checker:
|
||||
|
||||
.. code:: bash
|
||||
|
||||
$ python tools/run_mypy.py
|
||||
|
||||
|
||||
Contributions
|
||||
-------------
|
||||
|
||||
Contributions are welcome. If you want to contribute to Stegano I highly
|
||||
recommend you to install it in a Python virtual environment with pipenv.
|
||||
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
This software is licensed under
|
||||
`GNU General Public License version 3 <https://www.gnu.org/licenses/gpl-3.0.html>`_
|
||||
|
||||
Copyright (C) 2010-2018 `Cédric Bonhomme <https://www.cedricbonhomme.org>`_
|
||||
|
||||
For more information, `the list of authors and contributors <CONTRIBUTORS.rst>`_ is available.
|
|
@ -1,98 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
#-*- coding: utf-8 -*-
|
||||
|
||||
# Stéganô - Stéganô is a basic Python Steganography module.
|
||||
# Copyright (C) 2010-2017 Cédric Bonhomme - https://www.cedricbonhomme.org
|
||||
#
|
||||
# For more information : https://github.com/cedricbonhomme/Stegano
|
||||
#
|
||||
# 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
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
__author__ = "Cedric Bonhomme"
|
||||
__version__ = "$Revision: 0.7 $"
|
||||
__date__ = "$Date: 2016/08/04 $"
|
||||
__revision__ = "$Date: 2017/05/16 $"
|
||||
__license__ = "GPLv3"
|
||||
|
||||
try:
|
||||
from stegano import lsb
|
||||
except:
|
||||
print("Install Stegano: sudo pip install Stegano")
|
||||
|
||||
from stegano import tools
|
||||
|
||||
import argparse
|
||||
parser = argparse.ArgumentParser(prog='stegano-lsb')
|
||||
subparsers = parser.add_subparsers(help='sub-command help', dest='command')
|
||||
|
||||
# Subparser: Hide
|
||||
parser_hide = subparsers.add_parser('hide', help='hide help')
|
||||
# Original image
|
||||
parser_hide.add_argument("-i", "--input", dest="input_image_file",
|
||||
required=True, help="Input image file.")
|
||||
parser_hide.add_argument("-e", "--encoding", dest="encoding",
|
||||
choices=tools.ENCODINGS.keys(), default='UTF-8',
|
||||
help="Specify the encoding of the message to hide." +
|
||||
" UTF-8 (default) or UTF-32LE.")
|
||||
|
||||
group_secret = parser_hide.add_mutually_exclusive_group(required=True)
|
||||
# Non binary secret message to hide
|
||||
group_secret.add_argument("-m", dest="secret_message",
|
||||
help="Your secret message to hide (non binary).")
|
||||
# Binary secret message to hide
|
||||
group_secret.add_argument("-f", dest="secret_file",
|
||||
help="Your secret to hide (Text or any binary file).")
|
||||
|
||||
# Image containing the secret
|
||||
parser_hide.add_argument("-o", "--output", dest="output_image_file",
|
||||
required=True, help="Output image containing the secret.")
|
||||
|
||||
|
||||
# Subparser: Reveal
|
||||
parser_reveal = subparsers.add_parser('reveal', help='reveal help')
|
||||
parser_reveal.add_argument("-i", "--input", dest="input_image_file",
|
||||
required=True, help="Input image file.")
|
||||
parser_reveal.add_argument("-e", "--encoding", dest="encoding",
|
||||
choices=tools.ENCODINGS.keys(), default='UTF-8',
|
||||
help="Specify the encoding of the message to reveal." +
|
||||
" UTF-8 (default) or UTF-32LE.")
|
||||
parser_reveal.add_argument("-o", dest="secret_binary",
|
||||
help="Output for the binary secret (Text or any binary file).")
|
||||
|
||||
|
||||
arguments = parser.parse_args()
|
||||
|
||||
|
||||
if arguments.command == 'hide':
|
||||
if arguments.secret_message != None:
|
||||
secret = arguments.secret_message
|
||||
elif arguments.secret_file != None:
|
||||
secret = tools.binary2base64(arguments.secret_file)
|
||||
|
||||
img_encoded = lsb.hide(arguments.input_image_file, secret,
|
||||
arguments.encoding)
|
||||
try:
|
||||
img_encoded.save(arguments.output_image_file)
|
||||
except Exception as e:
|
||||
# If hide() returns an error (Too long message).
|
||||
print(e)
|
||||
|
||||
elif arguments.command == 'reveal':
|
||||
secret = lsb.reveal(arguments.input_image_file, arguments.encoding)
|
||||
if arguments.secret_binary != None:
|
||||
data = tools.base642binary(secret)
|
||||
with open(arguments.secret_binary, "wb") as f:
|
||||
f.write(data)
|
||||
else:
|
||||
print(secret)
|
|
@ -1,140 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
#-*- coding: utf-8 -*-
|
||||
|
||||
# Stéganô - Stéganô is a basic Python Steganography module.
|
||||
# Copyright (C) 2010-2017 Cédric Bonhomme - https://www.cedricbonhomme.org
|
||||
#
|
||||
# For more information : https://github.com/cedricbonhomme/Stegano
|
||||
#
|
||||
# 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
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
__author__ = "Cedric Bonhomme"
|
||||
__version__ = "$Revision: 0.7 $"
|
||||
__date__ = "$Date: 2016/03/18 $"
|
||||
__revision__ = "$Date: 2017/05/16 $"
|
||||
__license__ = "GPLv3"
|
||||
|
||||
import inspect
|
||||
import crayons
|
||||
|
||||
try:
|
||||
from stegano import lsbset
|
||||
from stegano.lsbset import generators
|
||||
except:
|
||||
print("Install stegano: sudo pip install Stegano")
|
||||
|
||||
from stegano import tools
|
||||
|
||||
import argparse
|
||||
parser = argparse.ArgumentParser(prog='stegano-lsb-set')
|
||||
subparsers = parser.add_subparsers(help='sub-command help', dest='command')
|
||||
|
||||
# Subparser: Hide
|
||||
parser_hide = subparsers.add_parser('hide', help='hide help')
|
||||
# Original image
|
||||
parser_hide.add_argument("-i", "--input", dest="input_image_file",
|
||||
required=True, help="Input image file.")
|
||||
parser_hide.add_argument("-e", "--encoding", dest="encoding",
|
||||
choices=tools.ENCODINGS.keys(), default='UTF-8',
|
||||
help="Specify the encoding of the message to hide." +
|
||||
" UTF-8 (default) or UTF-32LE.")
|
||||
|
||||
# Generator
|
||||
parser_hide.add_argument("-g", "--generator", dest="generator_function",
|
||||
choices=[generator[0] for generator in \
|
||||
inspect.getmembers(generators, inspect.isfunction)],
|
||||
required=True, help="Generator")
|
||||
|
||||
group_secret = parser_hide.add_mutually_exclusive_group(required=True)
|
||||
# Non binary secret message to hide
|
||||
group_secret.add_argument("-m", dest="secret_message",
|
||||
help="Your secret message to hide (non binary).")
|
||||
# Binary secret message to hide
|
||||
group_secret.add_argument("-f", dest="secret_file",
|
||||
help="Your secret to hide (Text or any binary file).")
|
||||
|
||||
# Image containing the secret
|
||||
parser_hide.add_argument("-o", "--output", dest="output_image_file",
|
||||
required=True, help="Output image containing the secret.")
|
||||
|
||||
|
||||
# Subparser: Reveal
|
||||
parser_reveal = subparsers.add_parser('reveal', help='reveal help')
|
||||
parser_reveal.add_argument("-i", "--input", dest="input_image_file",
|
||||
required=True, help="Input image file.")
|
||||
parser_reveal.add_argument("-e", "--encoding", dest="encoding",
|
||||
choices=tools.ENCODINGS.keys(), default='UTF-8',
|
||||
help="Specify the encoding of the message to reveal." +
|
||||
" UTF-8 (default) or UTF-32LE.")
|
||||
parser_reveal.add_argument("-g", "--generator", dest="generator_function",
|
||||
choices=[generator[0] for generator in \
|
||||
inspect.getmembers(generators, inspect.isfunction)],
|
||||
required=True, help="Generator")
|
||||
parser_reveal.add_argument("-o", dest="secret_binary",
|
||||
help="Output for the binary secret (Text or any binary file).")
|
||||
|
||||
|
||||
# Subparser: List generators
|
||||
parser_list_generators = subparsers.add_parser('list-generators',
|
||||
help='list-generators help')
|
||||
|
||||
arguments = parser.parse_args()
|
||||
|
||||
if arguments.command != 'list-generators':
|
||||
try:
|
||||
arguments.generator_function
|
||||
except AttributeError:
|
||||
print('You must specify the name of a generator.')
|
||||
parser.print_help()
|
||||
exit(1)
|
||||
|
||||
try:
|
||||
generator = getattr(generators, arguments.generator_function)()
|
||||
except AttributeError as e:
|
||||
print("Unknown generator: {}".format(arguments.generator_function))
|
||||
exit(1)
|
||||
|
||||
if arguments.command == 'hide':
|
||||
if arguments.secret_message != None:
|
||||
secret = arguments.secret_message
|
||||
elif arguments.secret_file != "":
|
||||
secret = tools.binary2base64(arguments.secret_file)
|
||||
|
||||
img_encoded = lsbset.hide(arguments.input_image_file, secret, generator)
|
||||
try:
|
||||
img_encoded.save(arguments.output_image_file)
|
||||
except Exception as e:
|
||||
# If hide() returns an error (Too long message).
|
||||
print(e)
|
||||
|
||||
elif arguments.command == 'reveal':
|
||||
try:
|
||||
secret = lsbset.reveal(arguments.input_image_file, generator)
|
||||
except IndexError:
|
||||
print("Impossible to detect message.")
|
||||
exit(0)
|
||||
if arguments.secret_binary != None:
|
||||
data = tools.base642binary(secret)
|
||||
with open(arguments.secret_binary, "w") as f:
|
||||
f.write(data)
|
||||
else:
|
||||
print(secret)
|
||||
|
||||
elif arguments.command == 'list-generators':
|
||||
all_generators = inspect.getmembers(generators, inspect.isfunction)
|
||||
for generator in all_generators:
|
||||
print('Generator id:')
|
||||
print(' {}'.format(crayons.green(generator[0], bold=True)))
|
||||
print('Desciption:')
|
||||
print(' {}'.format(generator[1].__doc__))
|
|
@ -1,56 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Stéganô - Stéganô is a basic Python Steganography module.
|
||||
# Copyright (C) 2010-2017 Cédric Bonhomme - https://www.cedricbonhomme.org
|
||||
#
|
||||
# For more information : https://github.com/cedricbonhomme/Stegano
|
||||
#
|
||||
# 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
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
__author__ = "Cedric Bonhomme"
|
||||
__version__ = "$Revision: 0.1 $"
|
||||
__date__ = "$Date: 2017/02/06 $"
|
||||
__license__ = "GPLv3"
|
||||
|
||||
try:
|
||||
from stegano import red
|
||||
except:
|
||||
print("Install stegano: sudo pip install Stegano")
|
||||
|
||||
import argparse
|
||||
parser = argparse.ArgumentParser(prog='stegano-red')
|
||||
subparsers = parser.add_subparsers(help='sub-command help', dest='command')
|
||||
|
||||
parser_hide = subparsers.add_parser('hide', help='hide help')
|
||||
parser_hide.add_argument("-i", "--input", dest="input_image_file",
|
||||
help="Image file")
|
||||
parser_hide.add_argument("-m", dest="secret_message",
|
||||
help="Your secret message to hide (non binary).")
|
||||
parser_hide.add_argument("-o", "--output", dest="output_image_file",
|
||||
help="Image file")
|
||||
|
||||
parser_reveal = subparsers.add_parser('reveal', help='reveal help')
|
||||
parser_reveal.add_argument("-i", "--input", dest="input_image_file",
|
||||
help="Image file")
|
||||
|
||||
arguments = parser.parse_args()
|
||||
|
||||
if arguments.command == 'hide':
|
||||
secret = red.hide(arguments.input_image_file, arguments.secret_message)
|
||||
secret.save(arguments.output_image_file)
|
||||
|
||||
elif arguments.command == 'reveal':
|
||||
secret = red.reveal(arguments.input_image_file)
|
||||
print(secret)
|
164
docs/conf.py
164
docs/conf.py
|
@ -1,4 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Stéganô documentation build configuration file, created by
|
||||
# sphinx-quickstart on Wed Jul 25 13:33:39 2012.
|
||||
|
@ -10,233 +9,162 @@
|
|||
#
|
||||
# All configuration values have a default; values that are commented out
|
||||
# serve to show the default.
|
||||
|
||||
import sys, os
|
||||
|
||||
# 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
|
||||
# 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 -----------------------------------------------------
|
||||
|
||||
# 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
|
||||
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
||||
extensions = []
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
templates_path = ["_templates"]
|
||||
|
||||
# The suffix of source filenames.
|
||||
source_suffix = '.rst'
|
||||
source_suffix = ".rst"
|
||||
|
||||
# The encoding of source files.
|
||||
#source_encoding = 'utf-8-sig'
|
||||
# source_encoding = 'utf-8-sig'
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = 'index'
|
||||
master_doc = "index"
|
||||
|
||||
# General information about the project.
|
||||
project = u'Stéganô'
|
||||
copyright = u'2012-2017, Cédric Bonhomme'
|
||||
project = "Stegano"
|
||||
copyright = "2010-2025, Cédric Bonhomme"
|
||||
author = "Cédric Bonhomme <cedric@cedricbonhomme.org>"
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = '0.8'
|
||||
version = "0.11"
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = '0.8.1'
|
||||
release = "0.11.0"
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
#language = None
|
||||
# language = None
|
||||
|
||||
# There are two options for replacing |today|: either, you set today to some
|
||||
# non-false value, then it is used:
|
||||
#today = ''
|
||||
# today = ''
|
||||
# Else, today_fmt is used as the format for a strftime call.
|
||||
#today_fmt = '%B %d, %Y'
|
||||
# today_fmt = '%B %d, %Y'
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
exclude_patterns = ['_build']
|
||||
exclude_patterns = ["_build"]
|
||||
|
||||
# The reST default role (used for this markup: `text`) to use for all documents.
|
||||
#default_role = None
|
||||
# default_role = None
|
||||
|
||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||
#add_function_parentheses = True
|
||||
# add_function_parentheses = True
|
||||
|
||||
# If true, the current module name will be prepended to all description
|
||||
# unit titles (such as .. function::).
|
||||
#add_module_names = True
|
||||
# add_module_names = True
|
||||
|
||||
# If true, sectionauthor and moduleauthor directives will be shown in the
|
||||
# output. They are ignored by default.
|
||||
#show_authors = False
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = 'sphinx'
|
||||
# show_authors = False
|
||||
|
||||
# A list of ignored prefixes for module index sorting.
|
||||
#modindex_common_prefix = []
|
||||
# modindex_common_prefix = []
|
||||
|
||||
|
||||
# -- Options for HTML output ---------------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
html_theme = 'bizstyle'
|
||||
html_theme = "sphinx_rtd_theme"
|
||||
|
||||
# Theme options are theme-specific and customize the look and feel of a theme
|
||||
# further. For a list of options available for each theme, see the
|
||||
# documentation.
|
||||
#html_theme_options = {}
|
||||
# html_theme_options = {}
|
||||
|
||||
# Add any paths that contain custom themes here, relative to this directory.
|
||||
#html_theme_path = []
|
||||
# html_theme_path = []
|
||||
|
||||
# The name for this set of Sphinx documents. If None, it defaults to
|
||||
# "<project> v<release> documentation".
|
||||
#html_title = None
|
||||
# html_title = None
|
||||
|
||||
# A shorter title for the navigation bar. Default is the same as html_title.
|
||||
#html_short_title = None
|
||||
# html_short_title = None
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top
|
||||
# of the sidebar.
|
||||
#html_logo = None
|
||||
# html_logo = None
|
||||
|
||||
# The name of an image file (within the static path) to use as favicon of the
|
||||
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
||||
# pixels large.
|
||||
#html_favicon = None
|
||||
# html_favicon = None
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = ['_static']
|
||||
html_static_path = ["_static"]
|
||||
|
||||
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
||||
# using the given strftime format.
|
||||
#html_last_updated_fmt = '%b %d, %Y'
|
||||
# html_last_updated_fmt = '%b %d, %Y'
|
||||
|
||||
# If true, SmartyPants will be used to convert quotes and dashes to
|
||||
# typographically correct entities.
|
||||
#html_use_smartypants = True
|
||||
# html_use_smartypants = True
|
||||
|
||||
# Custom sidebar templates, maps document names to template names.
|
||||
#html_sidebars = {}
|
||||
# html_sidebars = {}
|
||||
|
||||
# Additional templates that should be rendered to pages, maps page names to
|
||||
# template names.
|
||||
#html_additional_pages = {}
|
||||
# html_additional_pages = {}
|
||||
|
||||
# If false, no module index is generated.
|
||||
#html_domain_indices = True
|
||||
|
||||
# If false, no index is generated.
|
||||
html_use_index = False
|
||||
# html_domain_indices = True
|
||||
|
||||
# If true, the index is split into individual pages for each letter.
|
||||
#html_split_index = False
|
||||
# html_split_index = False
|
||||
|
||||
# If true, links to the reST sources are added to the pages.
|
||||
#html_show_sourcelink = True
|
||||
# html_show_sourcelink = True
|
||||
|
||||
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
|
||||
#html_show_sphinx = True
|
||||
# html_show_sphinx = True
|
||||
|
||||
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
|
||||
#html_show_copyright = True
|
||||
# html_show_copyright = True
|
||||
|
||||
# If true, an OpenSearch description file will be output, and all pages will
|
||||
# contain a <link> tag referring to it. The value of this option must be the
|
||||
# base URL from which the finished HTML is served.
|
||||
#html_use_opensearch = ''
|
||||
# html_use_opensearch = ''
|
||||
|
||||
# This is the file name suffix for HTML files (e.g. ".xhtml").
|
||||
#html_file_suffix = None
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = 'Stgandoc'
|
||||
# html_file_suffix = None
|
||||
|
||||
|
||||
# -- Options for LaTeX output --------------------------------------------------
|
||||
|
||||
latex_elements = {
|
||||
# The paper size ('letterpaper' or 'a4paper').
|
||||
#'papersize': 'letterpaper',
|
||||
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
#'pointsize': '10pt',
|
||||
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
#'preamble': '',
|
||||
}
|
||||
latex_engine = "pdflatex"
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title, author, documentclass [howto/manual]).
|
||||
latex_documents = [
|
||||
('index', 'Stgan.tex', u'Stéganô Documentation',
|
||||
u'Cédric Bonhomme', 'manual'),
|
||||
("index", "Stgan.tex", "Stegano Documentation", "Cédric Bonhomme", "howto"),
|
||||
]
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top of
|
||||
# the title page.
|
||||
#latex_logo = None
|
||||
latex_show_urls = True
|
||||
latex_show_pagerefs = True
|
||||
|
||||
# For "manual" documents, if this is true, then toplevel headings are parts,
|
||||
# not chapters.
|
||||
#latex_use_parts = False
|
||||
|
||||
# If true, show page references after internal links.
|
||||
#latex_show_pagerefs = False
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
#latex_show_urls = False
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
#latex_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
#latex_domain_indices = True
|
||||
|
||||
|
||||
# -- Options for manual page output --------------------------------------------
|
||||
|
||||
# One entry per manual page. List of tuples
|
||||
# (source start file, name, description, authors, manual section).
|
||||
man_pages = [
|
||||
('index', 'stgan', u'Stéganô Documentation',
|
||||
[u'Cédric Bonhomme'], 1)
|
||||
]
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
#man_show_urls = False
|
||||
|
||||
|
||||
# -- Options for Texinfo output ------------------------------------------------
|
||||
|
||||
# Grouping the document tree into Texinfo files. List of tuples
|
||||
# (source start file, target name, title, author,
|
||||
# dir menu entry, description, category)
|
||||
texinfo_documents = [
|
||||
('index', 'Stgan', u'Stéganô Documentation',
|
||||
u'Cédric Bonhomme', 'Stgan', 'One line description of project.',
|
||||
'Miscellaneous'),
|
||||
]
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
#texinfo_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
#texinfo_domain_indices = True
|
||||
|
||||
# How to display URL addresses: 'footnote', 'no', or 'inline'.
|
||||
#texinfo_show_urls = 'footnote'
|
||||
ADDITIONAL_PREAMBLE = r"""
|
||||
\setcounter{tocdepth}{3}
|
||||
"""
|
||||
|
|
|
@ -6,33 +6,15 @@
|
|||
Presentation
|
||||
============
|
||||
|
||||
.. image:: https://img.shields.io/pypi/pyversions/Stegano.svg?style=flat-square
|
||||
:target: https://pypi.python.org/pypi/Stegano
|
||||
|
||||
.. image:: https://img.shields.io/pypi/v/Stegano.svg?style=flat-square
|
||||
:target: https://github.com/cedricbonhomme/Stegano/releases/latest
|
||||
|
||||
.. image:: https://img.shields.io/pypi/l/Stegano.svg?style=flat-square
|
||||
:target: https://www.gnu.org/licenses/gpl-3.0.html
|
||||
|
||||
.. image:: https://img.shields.io/travis/cedricbonhomme/Stegano.svg?style=flat-square
|
||||
:target: https://travis-ci.org/cedricbonhomme/Stegano
|
||||
|
||||
.. image:: https://img.shields.io/github/stars/cedricbonhomme/Stegano.svg?maxAge=2592000&style=flat-square
|
||||
:target: https://github.com/cedricbonhomme/Stegano/stargazers
|
||||
|
||||
.. image:: https://img.shields.io/badge/SayThanks.io-%E2%98%BC-1EAEDB.svg?style=flat-square
|
||||
:target: https://saythanks.io/to/cedricbonhomme
|
||||
|
||||
Stéganô_ 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
|
||||
that no one, apart from the sender and intended recipient, suspects the
|
||||
existence of the message, a form of security through obscurity.
|
||||
Consequently, functions provided by Stéganô only hide messages,
|
||||
Consequently, functions provided by Stegano only hide messages,
|
||||
without encryption. Steganography is often used with cryptography.
|
||||
|
||||
Stéganô implements these methods of hiding:
|
||||
Stegano implements these methods of hiding:
|
||||
|
||||
- using the red portion of a pixel to hide ASCII messages;
|
||||
- using the `Least Significant Bit <http://en.wikipedia.org/wiki/Least_significant_bit>`_ (LSB) technique;
|
||||
|
@ -44,6 +26,8 @@ Moreover some methods of steganalysis_ are provided:
|
|||
- steganalysis of LSB encoding in color images;
|
||||
- statistical steganalysis.
|
||||
|
||||
You can also use Stegano through a `Web service <https://github.com/cedricbonhomme/stegano-web>`_.
|
||||
Not all functionalities of Stegano are covered.
|
||||
|
||||
Requirements
|
||||
============
|
||||
|
@ -53,7 +37,7 @@ Requirements
|
|||
- `piexif`_.
|
||||
|
||||
|
||||
Turorial
|
||||
Tutorial
|
||||
========
|
||||
|
||||
.. toctree::
|
||||
|
@ -64,21 +48,21 @@ Turorial
|
|||
software
|
||||
steganalysis
|
||||
|
||||
You can also have a look at the
|
||||
You can have a look at the
|
||||
`unit tests <https://github.com/cedricbonhomme/Stegano/tree/master/tests>`_.
|
||||
|
||||
|
||||
License
|
||||
=======
|
||||
|
||||
Stéganô_ is under GPL v3 license.
|
||||
Stegano_ is under GPL v3 license.
|
||||
|
||||
|
||||
Donation
|
||||
========
|
||||
|
||||
If you wish and if you like Stéganô, you can donate via bitcoin.
|
||||
My bitcoin address: `1GVmhR9fbBeEh7rP1qNq76jWArDdDQ3otZ <http://blockexplorer.com/address/1GVmhR9fbBeEh7rP1qNq76jWArDdDQ3otZ>`_
|
||||
If you wish and if you like Stegano, you can
|
||||
`donate <https://github.com/sponsors/cedricbonhomme>`_.
|
||||
|
||||
|
||||
|
||||
|
@ -89,7 +73,7 @@ Contact
|
|||
|
||||
|
||||
.. _Python: https://www.python.org
|
||||
.. _Stéganô: https://github.com/cedricbonhomme/Stegano
|
||||
.. _Stegano: https://github.com/cedricbonhomme/Stegano
|
||||
.. _`Pillow`: https://pypi.python.org/pypi/Pillow
|
||||
.. _`piexif`: https://pypi.python.org/pypi/piexif
|
||||
.. _steganography: http://en.wikipedia.org/wiki/Steganography
|
||||
|
|
|
@ -3,16 +3,13 @@ Installation
|
|||
|
||||
.. code-block:: bash
|
||||
|
||||
$ pipenv install Stegano
|
||||
$ poetry install Stegano
|
||||
|
||||
You will be able to use Stéganô in your Python programs
|
||||
You will be able to use Stegano in your Python programs
|
||||
or as a command line tool.
|
||||
|
||||
If you want to retrieve the source code (with the unit tests):
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ git clone https://github.com/cedricbonhomme/Stegano.git
|
||||
|
||||
.. image:: https://api.travis-ci.org/cedricbonhomme/Stegano.svg?branch=master
|
||||
:target: https://travis-ci.org/cedricbonhomme/Stegano
|
||||
$ git clone https://github.com/cedricbonhomme/Stegano
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
Using Stéganô as a Python module
|
||||
Using Stegano as a Python module
|
||||
================================
|
||||
|
||||
You can find more examples in the
|
||||
|
@ -9,8 +9,7 @@ LSB method
|
|||
|
||||
.. code-block:: python
|
||||
|
||||
Python 3.5.1 (default, Dec 7 2015, 11:33:57)
|
||||
[GCC 4.9.2] on linux
|
||||
Python 3.11.0 (main, Oct 31 2022, 15:15:22) [GCC 12.2.0] on linux
|
||||
Type "help", "copyright", "credits" or "license" for more information.
|
||||
>>> from stegano import lsb
|
||||
>>> secret = lsb.hide("./tests/sample-files/Lenna.png", "Hello world!")
|
||||
|
@ -27,29 +26,33 @@ Sets are used in order to select the pixels where the message will be hidden.
|
|||
|
||||
.. code-block:: python
|
||||
|
||||
Python 3.5.1 (default, Dec 7 2015, 11:33:57)
|
||||
[GCC 4.9.2] on linux
|
||||
Python 3.11.0 (main, Oct 31 2022, 15:15:22) [GCC 12.2.0] on linux
|
||||
Type "help", "copyright", "credits" or "license" for more information.
|
||||
>>> from stegano import lsbset
|
||||
>>> from stegano.lsbset import generators
|
||||
>>> from stegano import lsb
|
||||
>>> from stegano.lsb import generators
|
||||
|
||||
# Hide a secret with the Sieve of Eratosthenes
|
||||
>>> secret_message = "Hello World!"
|
||||
>>> secret_image = lsbset.hide("./tests/sample-files/Lenna.png",
|
||||
secret_message,
|
||||
generators.eratosthenes())
|
||||
>>> secret_image = lsb.hide("./tests/sample-files/Lenna.png", secret_message, generators.eratosthenes())
|
||||
>>> secret_image.save("./image.png")
|
||||
|
||||
# Try to decode with another generator
|
||||
>>> message = lsbset.reveal("./image.png", generators.fibonacci())
|
||||
>>> message = lsb.reveal("./image.png", generators.fibonacci())
|
||||
Traceback (most recent call last):
|
||||
File "<stdin>", line 1, in <module>
|
||||
File "/home/cedric/projects/Stegano/stegano/lsbset/lsbset.py", line 111, in reveal
|
||||
for color in img_list[generated_number]:
|
||||
IndexError: list index out of range
|
||||
File "/Users/flavien/.local/share/virtualenvs/Stegano-sY_cwr69/bin/stegano-lsb", line 6, in <module>
|
||||
sys.exit(main())
|
||||
File "/Users/flavien/Perso/dev/Stegano/bin/lsb.py", line 190, in main
|
||||
img_encoded = lsb.hide(
|
||||
File "/Users/flavien/Perso/dev/Stegano/stegano/lsb/lsb.py", line 63, in hide
|
||||
hider.encode_pixel((col, row))
|
||||
File "/Users/flavien/Perso/dev/Stegano/stegano/tools.py", line 165, in encode_pixel
|
||||
r, g, b, *a = self.encoded_image.getpixel(coordinate)
|
||||
File "/Users/flavien/.local/share/virtualenvs/Stegano-sY_cwr69/lib/python3.10/site-packages/PIL/Image.py", line 1481, in getpixel
|
||||
return self.im.getpixel(xy)
|
||||
IndexError: image index out of range
|
||||
|
||||
# Decode with Eratosthenes
|
||||
>>> message = lsbset.reveal("./image.png", generators.eratosthenes())
|
||||
>>> message = lsb.reveal("./image.png", generators.eratosthenes())
|
||||
>>> message
|
||||
'Hello World!'
|
||||
|
||||
|
@ -94,6 +97,12 @@ Sets are used in order to select the pixels where the message will be hidden.
|
|||
syracuse
|
||||
Generate the sequence of Syracuse.
|
||||
|
||||
shi_tomashi Shi-Tomachi corner generator of the given points
|
||||
https://docs.opencv.org/4.x/d4/d8c/tutorial_py_shi_tomasi.html
|
||||
|
||||
triangular_numbers Triangular numbers: a(n) = C(n+1,2) = n(n+1)/2 = 0+1+2+...+n.
|
||||
http://oeis.org/A000217
|
||||
|
||||
|
||||
|
||||
Description field of the image
|
||||
|
@ -103,8 +112,7 @@ For JPEG and TIFF images.
|
|||
|
||||
.. code-block:: python
|
||||
|
||||
Python 3.5.1 (default, Dec 7 2015, 11:33:57)
|
||||
[GCC 4.9.2] on linux
|
||||
Python 3.11.0 (main, Oct 31 2022, 15:15:22) [GCC 12.2.0] on linux
|
||||
Type "help", "copyright", "credits" or "license" for more information.
|
||||
>>> from stegano import exifHeader
|
||||
>>> secret = exifHeader.hide("./tests/sample-files/20160505T130442.jpg",
|
||||
|
|
2
docs/requirements.txt
Normal file
2
docs/requirements.txt
Normal file
|
@ -0,0 +1,2 @@
|
|||
sphinx
|
||||
sphinx_rtd_theme
|
|
@ -1,4 +1,4 @@
|
|||
Using Stéganô in command line
|
||||
Using Stegano in command line
|
||||
=============================
|
||||
|
||||
The command ``stegano-lsb``
|
||||
|
@ -12,52 +12,56 @@ Display help
|
|||
.. code-block:: bash
|
||||
|
||||
$ stegano-lsb --help
|
||||
usage: stegano-lsb [-h] {hide,reveal} ...
|
||||
usage: stegano-lsb [-h] {hide,reveal,list-generators} ...
|
||||
|
||||
positional arguments:
|
||||
{hide,reveal} sub-command help
|
||||
hide hide help
|
||||
reveal reveal help
|
||||
{hide,reveal,list-generators}
|
||||
sub-command help
|
||||
hide hide help
|
||||
reveal reveal help
|
||||
list-generators list-generators help
|
||||
|
||||
optional arguments:
|
||||
-h, --help show this help message and exit
|
||||
options:
|
||||
-h, --help show this help message and exit
|
||||
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ stegano-lsb hide --help
|
||||
usage: stegano-lsb hide [-h] -i INPUT_IMAGE_FILE [-e {UTF-8,UTF-32LE}]
|
||||
(-m SECRET_MESSAGE | -f SECRET_FILE) -o
|
||||
OUTPUT_IMAGE_FILE
|
||||
usage: stegano-lsb hide [-h] -i INPUT_IMAGE_FILE [-e {UTF-8,UTF-32LE}] [-g [GENERATOR_FUNCTION ...]] [-s SHIFT] (-m SECRET_MESSAGE | -f SECRET_FILE) -o OUTPUT_IMAGE_FILE
|
||||
|
||||
optional arguments:
|
||||
-h, --help show this help message and exit
|
||||
-i INPUT_IMAGE_FILE, --input INPUT_IMAGE_FILE
|
||||
options:
|
||||
-h, --help show this help message and exit
|
||||
-i INPUT_IMAGE_FILE, --input INPUT_IMAGE_FILE
|
||||
Input image file.
|
||||
-e {UTF-8,UTF-32LE}, --encoding {UTF-8,UTF-32LE}
|
||||
Specify the encoding of the message to hide. UTF-8
|
||||
(default) or UTF-32LE.
|
||||
-m SECRET_MESSAGE Your secret message to hide (non binary).
|
||||
-f SECRET_FILE Your secret to hide (Text or any binary file).
|
||||
-o OUTPUT_IMAGE_FILE, --output OUTPUT_IMAGE_FILE
|
||||
-e {UTF-8,UTF-32LE}, --encoding {UTF-8,UTF-32LE}
|
||||
Specify the encoding of the message to hide. UTF-8 (default) or UTF-32LE.
|
||||
-g [GENERATOR_FUNCTION ...], --generator [GENERATOR_FUNCTION ...]
|
||||
Generator (with optional arguments)
|
||||
-s SHIFT, --shift SHIFT
|
||||
Shift for the generator
|
||||
-m SECRET_MESSAGE Your secret message to hide (non binary).
|
||||
-f SECRET_FILE Your secret to hide (Text or any binary file).
|
||||
-o OUTPUT_IMAGE_FILE, --output OUTPUT_IMAGE_FILE
|
||||
Output image containing the secret.
|
||||
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ stegano-lsb reveal --help
|
||||
usage: stegano-lsb reveal [-h] -i INPUT_IMAGE_FILE [-e {UTF-8,UTF-32LE}]
|
||||
[-o SECRET_BINARY]
|
||||
usage: stegano-lsb reveal [-h] -i INPUT_IMAGE_FILE [-e {UTF-8,UTF-32LE}] [-g [GENERATOR_FUNCTION ...]] [-s SHIFT] [-o SECRET_BINARY]
|
||||
|
||||
optional arguments:
|
||||
-h, --help show this help message and exit
|
||||
-i INPUT_IMAGE_FILE, --input INPUT_IMAGE_FILE
|
||||
options:
|
||||
-h, --help show this help message and exit
|
||||
-i INPUT_IMAGE_FILE, --input INPUT_IMAGE_FILE
|
||||
Input image file.
|
||||
-e {UTF-8,UTF-32LE}, --encoding {UTF-8,UTF-32LE}
|
||||
Specify the encoding of the message to reveal. UTF-8
|
||||
(default) or UTF-32LE.
|
||||
-o SECRET_BINARY Output for the binary secret (Text or any binary
|
||||
file).
|
||||
-e {UTF-8,UTF-32LE}, --encoding {UTF-8,UTF-32LE}
|
||||
Specify the encoding of the message to reveal. UTF-8 (default) or UTF-32LE.
|
||||
-g [GENERATOR_FUNCTION ...], --generator [GENERATOR_FUNCTION ...]
|
||||
Generator (with optional arguments)
|
||||
-s SHIFT, --shift SHIFT
|
||||
Shift for the generator
|
||||
-o SECRET_BINARY Output for the binary secret (Text or any binary file).
|
||||
|
||||
|
||||
Hide and reveal a text message
|
||||
|
@ -92,52 +96,40 @@ Hide and reveal a binary file
|
|||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
The command ``stegano-lsb-set``
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Sets are used in order to select the pixels where the message will be hidden.
|
||||
|
||||
Hide and reveal a text message
|
||||
------------------------------
|
||||
Hide and reveal a text message with set
|
||||
---------------------------------------
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# Hide the message with the Sieve of Eratosthenes
|
||||
$ stegano-lsb-set hide -i ./tests/sample-files/Montenach.png --generator eratosthenes -m 'Joyeux Noël!' -o ./surprise.png
|
||||
$ stegano-lsb hide -i ./tests/sample-files/Montenach.png --generator eratosthenes -m 'Joyeux Noël!' -o ./surprise.png
|
||||
|
||||
# Try to reveal with Mersenne numbers
|
||||
$ stegano-lsb-set reveal --generator mersenne -i ./surprise.png
|
||||
$ stegano-lsb reveal --generator mersenne -i ./surprise.png
|
||||
|
||||
# Try to reveal with fermat numbers
|
||||
$ stegano-lsb-set reveal --generator fermat -i ./surprise.png
|
||||
$ stegano-lsb reveal --generator fermat -i ./surprise.png
|
||||
|
||||
# Try to reveal with carmichael numbers
|
||||
$ stegano-lsb-set reveal --generator carmichael -i ./surprise.png
|
||||
$ stegano-lsb reveal --generator carmichael -i ./surprise.png
|
||||
|
||||
# Try to reveal with Sieve of Eratosthenes
|
||||
$ stegano-lsb-set reveal --generator eratosthenes -i ./surprise.png
|
||||
$ stegano-lsb reveal --generator eratosthenes -i ./surprise.png
|
||||
|
||||
|
||||
Sometimes it can be useful to skip the first values of a set. For example if you want
|
||||
to hide several messages or because due to the selected generator
|
||||
(Fibonacci starts with 0, 1, 1, etc.). Or maybe you just want to add more complexity.
|
||||
In this case, simply use the optional arguments ``--shift`` or ``-s``:
|
||||
|
||||
An other example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# Hide the message - LSB with a set defined by the identity function (f(x) = x).
|
||||
stegano-lsb-set hide -i ./tests/sample-files/Montenach.png --generator identity -m 'I like steganography.' -o ./enc-identity.png
|
||||
|
||||
# Hide the message - LSB only.
|
||||
stegano-lsb hide -i ./tests/sample-files/Montenach.png -m 'I like steganography.' -o ./enc.png
|
||||
|
||||
# Check if the two generated files are the same.
|
||||
sha1sum ./enc-identity.png ./enc.png
|
||||
|
||||
# The output of lsb is given to lsb-set.
|
||||
stegano-lsb-set reveal -i ./enc.png --generator identity
|
||||
|
||||
# The output of lsb-set is given to lsb.
|
||||
stegano-lsb reveal -i ./enc-identity.png
|
||||
$ stegano-lsb hide -i ./tests/sample-files/Lenna.png -m 'Shifted secret message' -o ~/Lenna1.png --shift 7
|
||||
$ stegano-lsb reveal -i ~/Lenna1.png --shift 7
|
||||
Shifted secret message
|
||||
|
||||
|
||||
List all available generators
|
||||
|
@ -145,7 +137,7 @@ List all available generators
|
|||
|
||||
.. code-block:: bash
|
||||
|
||||
$ stegano-lsb-set list-generators
|
||||
$ stegano-lsb list-generators
|
||||
Generator id:
|
||||
ackermann
|
||||
Desciption:
|
||||
|
|
|
@ -7,7 +7,7 @@ Parity
|
|||
.. code-block:: bash
|
||||
|
||||
# Hide the message with Sieve of Eratosthenes
|
||||
stegano-lsb-set hide -i ./tests/sample-files/20160505T130442.jpg -o ./surprise.png --generator eratosthenes -m 'Very important message.'
|
||||
stegano-lsb hide -i ./tests/sample-files/20160505T130442.jpg -o ./surprise.png --generator eratosthenes -m 'Very important message.'
|
||||
|
||||
# Steganalysis of the original photo
|
||||
stegano-steganalysis-parity -i ./tests/sample-files/20160505T130442.jpg -o ./surprise_st_original.png
|
||||
|
@ -16,4 +16,4 @@ Parity
|
|||
stegano-steganalysis-parity -i ./surprise.png -o ./surprise_st_secret.png
|
||||
|
||||
# Reveal with Sieve of Eratosthenes
|
||||
stegano-lsb-set reveal -i ./surprise.png --generator eratosthenes
|
||||
stegano-lsb reveal -i ./surprise.png --generator eratosthenes
|
||||
|
|
1095
poetry.lock
generated
Normal file
1095
poetry.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
93
pyproject.toml
Normal file
93
pyproject.toml
Normal file
|
@ -0,0 +1,93 @@
|
|||
[build-system]
|
||||
requires = ["poetry-core>=2.0"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
|
||||
|
||||
[project]
|
||||
name = "stegano"
|
||||
version = "1.0.1"
|
||||
description = "A pure Python Steganography module."
|
||||
authors = [
|
||||
{name = "Cédric Bonhomme", email= "cedric@cedricbonhomme.org"}
|
||||
]
|
||||
license = "GPL-3.0-or-later"
|
||||
readme = "README.md"
|
||||
keywords = ["Steganography", "Security", "Stegano"]
|
||||
|
||||
dynamic = ["classifiers"]
|
||||
|
||||
requires-python = ">=3.10,<4.0"
|
||||
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 = [
|
||||
"Development Status :: 5 - Production/Stable",
|
||||
"Environment :: Console",
|
||||
"Intended Audience :: Developers",
|
||||
"Intended Audience :: Science/Research",
|
||||
"Topic :: Security",
|
||||
"Operating System :: POSIX :: Linux",
|
||||
"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+)"
|
||||
]
|
||||
include = [
|
||||
"README.md",
|
||||
"COPYING",
|
||||
"CHANGELOG.md",
|
||||
"docs/**/*",
|
||||
]
|
||||
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
mypy = "^1.8.0"
|
||||
flake8 = "^6.0.0"
|
||||
nose2 = "^0.14.0"
|
||||
Sphinx = "^6.2.1"
|
||||
pre-commit = "^3.6.0"
|
||||
|
||||
|
||||
[tool.poetry.group.dev]
|
||||
optional = true
|
||||
|
||||
|
||||
[tool.mypy]
|
||||
python_version = "3.12"
|
||||
check_untyped_defs = true
|
||||
ignore_errors = false
|
||||
ignore_missing_imports = true
|
||||
strict_optional = true
|
||||
no_implicit_optional = true
|
||||
warn_unused_ignores = true
|
||||
warn_redundant_casts = true
|
||||
warn_unused_configs = true
|
||||
warn_unreachable = true
|
||||
|
||||
show_error_context = true
|
||||
pretty = true
|
||||
|
||||
exclude = "build|dist|docs|stegano.egg-info"
|
||||
|
||||
[tool.isort]
|
||||
profile = "black"
|
|
@ -1,2 +0,0 @@
|
|||
[metadata]
|
||||
description-file = README.rst
|
55
setup.py
55
setup.py
|
@ -1,55 +0,0 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from setuptools import setup
|
||||
|
||||
packages = [
|
||||
'stegano',
|
||||
'stegano.red',
|
||||
'stegano.exifHeader',
|
||||
'stegano.lsb',
|
||||
'stegano.lsbset',
|
||||
'stegano.steganalysis'
|
||||
]
|
||||
|
||||
scripts = [
|
||||
'bin/stegano-lsb',
|
||||
'bin/stegano-lsb-set',
|
||||
'bin/stegano-red',
|
||||
'bin/stegano-steganalysis-parity',
|
||||
'bin/stegano-steganalysis-statistics'
|
||||
]
|
||||
|
||||
requires = ['pillow', 'piexif', 'crayons']
|
||||
|
||||
with open('README.rst', 'r') as f:
|
||||
readme = f.read()
|
||||
with open('CHANGELOG.rst', 'r') as f:
|
||||
changelog = f.read()
|
||||
|
||||
setup(
|
||||
name='Stegano',
|
||||
version='0.8.4',
|
||||
author='Cédric Bonhomme',
|
||||
author_email='cedric@cedricbonhomme.org',
|
||||
packages=packages,
|
||||
include_package_data=True,
|
||||
scripts=scripts,
|
||||
url='https://github.com/cedricbonhomme/Stegano',
|
||||
description='A pure Python Steganography module.',
|
||||
long_description=readme + '\n|\n\n' + changelog,
|
||||
platforms = ['Linux'],
|
||||
license='GPLv3',
|
||||
install_requires=requires,
|
||||
zip_safe=False,
|
||||
classifiers=[
|
||||
'Development Status :: 5 - Production/Stable',
|
||||
'Environment :: Console',
|
||||
'Intended Audience :: Developers',
|
||||
'Intended Audience :: Science/Research',
|
||||
'Topic :: Security',
|
||||
'Operating System :: OS Independent',
|
||||
'Programming Language :: Python :: 3.6',
|
||||
'License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)'
|
||||
]
|
||||
)
|
|
@ -1,9 +1,5 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import red
|
||||
from . import exifHeader
|
||||
from . import lsb
|
||||
from . import lsbset
|
||||
from . import exifHeader, lsb, red, steganalysis
|
||||
|
||||
from . import steganalysis
|
||||
__all__ = ["red", "exifHeader", "lsb", "steganalysis"]
|
||||
|
|
0
stegano/console/__init__.py
Normal file
0
stegano/console/__init__.py
Normal file
227
stegano/console/lsb.py
Executable file
227
stegano/console/lsb.py
Executable file
|
@ -0,0 +1,227 @@
|
|||
#!/usr/bin/env python
|
||||
# Stegano - Stegano is a pure Python steganography module.
|
||||
# Copyright (C) 2010-2025 Cédric Bonhomme - https://www.cedricbonhomme.org
|
||||
#
|
||||
# For more information : https://github.com/cedricbonhomme/Stegano
|
||||
#
|
||||
# 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
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
__author__ = "Cedric Bonhomme"
|
||||
__version__ = "$Revision: 0.7 $"
|
||||
__date__ = "$Date: 2016/03/18 $"
|
||||
__revision__ = "$Date: 2019/06/04 $"
|
||||
__license__ = "GPLv3"
|
||||
|
||||
import inspect
|
||||
|
||||
import crayons
|
||||
|
||||
try:
|
||||
from stegano import lsb
|
||||
from stegano.lsb import generators
|
||||
except Exception:
|
||||
print("Install stegano: pipx install Stegano")
|
||||
|
||||
import argparse
|
||||
|
||||
from stegano import tools
|
||||
|
||||
|
||||
class ValidateGenerator(argparse.Action):
|
||||
def __call__(self, parser, args, values, option_string=None):
|
||||
valid_generators = [
|
||||
generator[0]
|
||||
for generator in inspect.getmembers(generators, inspect.isfunction)
|
||||
]
|
||||
# Verify that the generator is valid
|
||||
generator = values[0]
|
||||
if generator not in valid_generators:
|
||||
raise ValueError("Unknown generator: %s" % generator)
|
||||
# Set the generator_function arg of the parser
|
||||
setattr(args, self.dest, values)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(prog="stegano-lsb")
|
||||
subparsers = parser.add_subparsers(
|
||||
help="sub-command help", dest="command", required=True
|
||||
)
|
||||
|
||||
# Subparser: Hide
|
||||
parser_hide = subparsers.add_parser("hide", help="hide help")
|
||||
# Original image
|
||||
parser_hide.add_argument(
|
||||
"-i",
|
||||
"--input",
|
||||
dest="input_image_file",
|
||||
required=True,
|
||||
help="Input image file.",
|
||||
)
|
||||
parser_hide.add_argument(
|
||||
"-e",
|
||||
"--encoding",
|
||||
dest="encoding",
|
||||
choices=tools.ENCODINGS.keys(),
|
||||
default="UTF-8",
|
||||
help="Specify the encoding of the message to hide."
|
||||
" UTF-8 (default) or UTF-32LE.",
|
||||
)
|
||||
|
||||
# Generator
|
||||
parser_hide.add_argument(
|
||||
"-g",
|
||||
"--generator",
|
||||
dest="generator_function",
|
||||
action=ValidateGenerator,
|
||||
nargs="*",
|
||||
required=False,
|
||||
default=None,
|
||||
help="Generator (with optional arguments)",
|
||||
)
|
||||
|
||||
# Shift the message to hide
|
||||
parser_hide.add_argument(
|
||||
"-s", "--shift", dest="shift", default=0, help="Shift for the generator"
|
||||
)
|
||||
|
||||
group_secret = parser_hide.add_mutually_exclusive_group(required=True)
|
||||
# Non binary secret message to hide
|
||||
group_secret.add_argument(
|
||||
"-m", dest="secret_message", help="Your secret message to hide (non binary)."
|
||||
)
|
||||
# Binary secret message to hide
|
||||
group_secret.add_argument(
|
||||
"-f", dest="secret_file", help="Your secret to hide (Text or any binary file)."
|
||||
)
|
||||
|
||||
# Image containing the secret
|
||||
parser_hide.add_argument(
|
||||
"-o",
|
||||
"--output",
|
||||
dest="output_image_file",
|
||||
required=True,
|
||||
help="Output image containing the secret.",
|
||||
)
|
||||
|
||||
# Subparser: Reveal
|
||||
parser_reveal = subparsers.add_parser("reveal", help="reveal help")
|
||||
parser_reveal.add_argument(
|
||||
"-i",
|
||||
"--input",
|
||||
dest="input_image_file",
|
||||
required=True,
|
||||
help="Input image file.",
|
||||
)
|
||||
parser_reveal.add_argument(
|
||||
"-e",
|
||||
"--encoding",
|
||||
dest="encoding",
|
||||
choices=tools.ENCODINGS.keys(),
|
||||
default="UTF-8",
|
||||
help="Specify the encoding of the message to reveal."
|
||||
" UTF-8 (default) or UTF-32LE.",
|
||||
)
|
||||
|
||||
# Generator
|
||||
parser_reveal.add_argument(
|
||||
"-g",
|
||||
"--generator",
|
||||
dest="generator_function",
|
||||
action=ValidateGenerator,
|
||||
nargs="*",
|
||||
required=False,
|
||||
help="Generator (with optional arguments)",
|
||||
)
|
||||
|
||||
# Shift the message to reveal
|
||||
parser_reveal.add_argument(
|
||||
"-s", "--shift", dest="shift", default=0, help="Shift for the generator"
|
||||
)
|
||||
parser_reveal.add_argument(
|
||||
"-o",
|
||||
dest="secret_binary",
|
||||
help="Output for the binary secret (Text or any binary file).",
|
||||
)
|
||||
|
||||
# Subparser: List generators
|
||||
subparsers.add_parser("list-generators", help="list-generators help")
|
||||
|
||||
arguments = parser.parse_args()
|
||||
|
||||
if arguments.command != "list-generators":
|
||||
if not arguments.generator_function:
|
||||
generator = None
|
||||
else:
|
||||
try:
|
||||
if arguments.generator_function[0] == "LFSR":
|
||||
# Compute the size of the image for use by the LFSR generator if needed
|
||||
tmp = tools.open_image(arguments.input_image_file)
|
||||
size = tmp.width * tmp.height
|
||||
tmp.close()
|
||||
arguments.generator_function.append(size)
|
||||
if len(arguments.generator_function) > 1:
|
||||
generator = getattr(generators, arguments.generator_function[0])(
|
||||
*[int(e) for e in arguments.generator_function[1:]]
|
||||
)
|
||||
else:
|
||||
generator = getattr(generators, arguments.generator_function[0])()
|
||||
|
||||
except AttributeError:
|
||||
print(f"Unknown generator: {arguments.generator_function}")
|
||||
exit(1)
|
||||
|
||||
if arguments.command == "hide":
|
||||
if arguments.secret_message is not None:
|
||||
secret = arguments.secret_message
|
||||
elif arguments.secret_file != "":
|
||||
secret = tools.binary2base64(arguments.secret_file)
|
||||
|
||||
img_encoded = lsb.hide(
|
||||
image=arguments.input_image_file,
|
||||
message=secret,
|
||||
generator=generator,
|
||||
shift=int(arguments.shift),
|
||||
encoding=arguments.encoding,
|
||||
)
|
||||
try:
|
||||
img_encoded.save(arguments.output_image_file)
|
||||
except Exception as e:
|
||||
# If hide() returns an error (Too long message).
|
||||
print(e)
|
||||
|
||||
elif arguments.command == "reveal":
|
||||
try:
|
||||
secret = lsb.reveal(
|
||||
encoded_image=arguments.input_image_file,
|
||||
generator=generator,
|
||||
shift=int(arguments.shift),
|
||||
encoding=arguments.encoding,
|
||||
)
|
||||
except IndexError:
|
||||
print("Impossible to detect message.")
|
||||
exit(0)
|
||||
if arguments.secret_binary is not None:
|
||||
data = tools.base642binary(secret)
|
||||
with open(arguments.secret_binary, "wb") as f:
|
||||
f.write(data)
|
||||
else:
|
||||
print(secret)
|
||||
|
||||
elif arguments.command == "list-generators":
|
||||
all_generators = inspect.getmembers(generators, inspect.isfunction)
|
||||
for generator in all_generators:
|
||||
print("Generator id:")
|
||||
print(f" {crayons.green(generator[0], bold=True)}")
|
||||
print("Desciption:")
|
||||
print(f" {generator[1].__doc__}")
|
|
@ -1,8 +1,6 @@
|
|||
#!/usr/bin/env python
|
||||
#-*- coding: utf-8 -*-
|
||||
|
||||
# Stéganô - Stéganô is a basic Python Steganography module.
|
||||
# Copyright (C) 2010-2017 Cédric Bonhomme - https://www.cedricbonhomme.org
|
||||
# Stegano - Stegano is a pure Python steganography module.
|
||||
# Copyright (C) 2010-2025 Cédric Bonhomme - https://www.cedricbonhomme.org
|
||||
#
|
||||
# For more information : https://github.com/cedricbonhomme/Stegano
|
||||
#
|
||||
|
@ -24,21 +22,34 @@ __version__ = "$Revision: 0.7 $"
|
|||
__date__ = "$Date: 2016/08/25 $"
|
||||
__license__ = "GPLv3"
|
||||
|
||||
try:
|
||||
from stegano.steganalysis import parity
|
||||
except:
|
||||
print("Install Stegano: sudo pip install Stegano")
|
||||
import argparse
|
||||
|
||||
from PIL import Image
|
||||
|
||||
import argparse
|
||||
parser = argparse.ArgumentParser(prog='stegano-steganalysis-parity')
|
||||
parser.add_argument("-i", "--input", dest="input_image_file",
|
||||
help="Image file")
|
||||
parser.add_argument("-o", "--output", dest="output_image_file",
|
||||
help="Image file")
|
||||
arguments = parser.parse_args()
|
||||
try:
|
||||
from stegano.steganalysis import parity
|
||||
except Exception:
|
||||
print("Install Stegano: pipx install Stegano")
|
||||
|
||||
input_image_file = Image.open(arguments.input_image_file)
|
||||
output_image = parity.steganalyse(input_image_file)
|
||||
output_image.save(arguments.output_image_file)
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(prog="stegano-steganalysis-parity")
|
||||
parser.add_argument(
|
||||
"-i",
|
||||
"--input",
|
||||
dest="input_image_file",
|
||||
required=True,
|
||||
help="Input image file.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-o",
|
||||
"--output",
|
||||
dest="output_image_file",
|
||||
required=True,
|
||||
help="Output image file.",
|
||||
)
|
||||
arguments = parser.parse_args()
|
||||
|
||||
input_image_file = Image.open(arguments.input_image_file)
|
||||
output_image = parity.steganalyse(input_image_file)
|
||||
output_image.save(arguments.output_image_file)
|
61
stegano/console/red.py
Normal file
61
stegano/console/red.py
Normal file
|
@ -0,0 +1,61 @@
|
|||
#!/usr/bin/env python
|
||||
# Stegano - Stegano is a pure Python steganography module.
|
||||
# Copyright (C) 2010-2025 Cédric Bonhomme - https://www.cedricbonhomme.org
|
||||
#
|
||||
# For more information : https://github.com/cedricbonhomme/Stegano
|
||||
#
|
||||
# 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
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
__author__ = "Cedric Bonhomme"
|
||||
__version__ = "$Revision: 0.1 $"
|
||||
__date__ = "$Date: 2017/02/06 $"
|
||||
__license__ = "GPLv3"
|
||||
|
||||
import argparse
|
||||
|
||||
try:
|
||||
from stegano import red
|
||||
except Exception:
|
||||
print("Install stegano: sudo pip install Stegano")
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(prog="stegano-red")
|
||||
subparsers = parser.add_subparsers(help="sub-command help", dest="command")
|
||||
|
||||
parser_hide = subparsers.add_parser("hide", help="hide help")
|
||||
parser_hide.add_argument(
|
||||
"-i", "--input", dest="input_image_file", help="Image file"
|
||||
)
|
||||
parser_hide.add_argument(
|
||||
"-m", dest="secret_message", help="Your secret message to hide (non binary)."
|
||||
)
|
||||
parser_hide.add_argument(
|
||||
"-o", "--output", dest="output_image_file", help="Image file"
|
||||
)
|
||||
|
||||
parser_reveal = subparsers.add_parser("reveal", help="reveal help")
|
||||
parser_reveal.add_argument(
|
||||
"-i", "--input", dest="input_image_file", help="Image file"
|
||||
)
|
||||
|
||||
arguments = parser.parse_args()
|
||||
|
||||
if arguments.command == "hide":
|
||||
secret = red.hide(arguments.input_image_file, arguments.secret_message)
|
||||
secret.save(arguments.output_image_file)
|
||||
|
||||
elif arguments.command == "reveal":
|
||||
secret = red.reveal(arguments.input_image_file)
|
||||
print(secret)
|
|
@ -1,8 +1,6 @@
|
|||
#!/usr/bin/env python
|
||||
#-*- coding: utf-8 -*-
|
||||
|
||||
# Stéganô - Stéganô is a basic Python Steganography module.
|
||||
# Copyright (C) 2010-2017 Cédric Bonhomme - https://www.cedricbonhomme.org
|
||||
# Stegano - Stegano is a pure Python steganography module.
|
||||
# Copyright (C) 2010-2025 Cédric Bonhomme - https://www.cedricbonhomme.org
|
||||
#
|
||||
# For more information : https://github.com/cedricbonhomme/Stegano
|
||||
#
|
||||
|
@ -25,21 +23,22 @@ __date__ = "$Date: 2016/08/26 $"
|
|||
__revision__ = "$Date: 2016/08/26 $"
|
||||
__license__ = "GPLv3"
|
||||
|
||||
try:
|
||||
from stegano.steganalysis import statistics
|
||||
except:
|
||||
print("Install Stegano: sudo pip install Stegano")
|
||||
import argparse
|
||||
|
||||
from PIL import Image
|
||||
|
||||
import argparse
|
||||
parser = argparse.ArgumentParser(prog='stegano-steganalysis-parity')
|
||||
parser.add_argument("-i", "--input", dest="input_image_file",
|
||||
help="Image file")
|
||||
parser.add_argument("-o", "--output", dest="output_image_file",
|
||||
help="Image file")
|
||||
arguments = parser.parse_args()
|
||||
try:
|
||||
from stegano.steganalysis import statistics
|
||||
except Exception:
|
||||
print("Install Stegano: sudo pip install Stegano")
|
||||
|
||||
input_image_file = Image.open(arguments.input_image_file)
|
||||
output_image = statistics.steganalyse(input_image_file)
|
||||
output_image.save(arguments.output_image_file)
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(prog="stegano-steganalysis-parity")
|
||||
parser.add_argument("-i", "--input", dest="input_image_file", help="Image file")
|
||||
parser.add_argument("-o", "--output", dest="output_image_file", help="Image file")
|
||||
arguments = parser.parse_args()
|
||||
|
||||
input_image_file = Image.open(arguments.input_image_file)
|
||||
output_image = statistics.steganalyse(input_image_file)
|
||||
output_image.save(arguments.output_image_file)
|
|
@ -1,4 +1,5 @@
|
|||
#!/usr/bin/env python
|
||||
#-*- coding: utf-8 -*-
|
||||
|
||||
from .exifHeader import *
|
||||
from .exifHeader import hide, reveal
|
||||
|
||||
__all__ = ["hide", "reveal"]
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
#!/usr/bin/env python
|
||||
#-*- coding: utf-8 -*-
|
||||
|
||||
# Stéganô - Stéganô is a basic Python Steganography module.
|
||||
# Copyright (C) 2010-2017 Cédric Bonhomme - https://www.cedricbonhomme.org
|
||||
# Stegano - Stegano is a pure Python steganography module.
|
||||
# Copyright (C) 2010-2025 Cédric Bonhomme - https://www.cedricbonhomme.org
|
||||
#
|
||||
# For more information : https://github.com/cedricbonhomme/Stegano
|
||||
#
|
||||
|
@ -25,25 +23,32 @@ __date__ = "$Date: 2016/05/26 $"
|
|||
__revision__ = "$Date: 2017/01/18 $"
|
||||
__license__ = "GPLv3"
|
||||
|
||||
from PIL import Image
|
||||
import piexif
|
||||
|
||||
def hide(input_image_file, img_enc, secret_message = None, secret_file = None, img_format = None):
|
||||
"""Hide a message (string) in an image.
|
||||
"""
|
||||
from zlib import compress
|
||||
from base64 import b64encode
|
||||
from stegano import tools
|
||||
|
||||
if secret_file != None:
|
||||
with open(secret_file, "r") as f:
|
||||
|
||||
def hide(
|
||||
input_image_file,
|
||||
img_enc,
|
||||
secret_message=None,
|
||||
secret_file=None,
|
||||
img_format=None,
|
||||
):
|
||||
"""Hide a message (string) in an image."""
|
||||
from base64 import b64encode
|
||||
from zlib import compress
|
||||
|
||||
if secret_file is not None:
|
||||
with open(secret_file) as f:
|
||||
secret_message = f.read()
|
||||
|
||||
try:
|
||||
text = compress(b64encode(bytes(secret_message, "utf-8")))
|
||||
except:
|
||||
except Exception:
|
||||
text = compress(b64encode(secret_message))
|
||||
|
||||
img = Image.open(input_image_file)
|
||||
img = tools.open_image(input_image_file)
|
||||
|
||||
if img_format is None:
|
||||
img_format = img.format
|
||||
|
@ -61,20 +66,20 @@ def hide(input_image_file, img_enc, secret_message = None, secret_file = None, i
|
|||
|
||||
|
||||
def reveal(input_image_file):
|
||||
"""Find a message in an image.
|
||||
"""
|
||||
"""Find a message in an image."""
|
||||
from base64 import b64decode
|
||||
from zlib import decompress
|
||||
|
||||
img = Image.open(input_image_file)
|
||||
img = tools.open_image(input_image_file)
|
||||
|
||||
try:
|
||||
if img.format in ['JPEG', 'TIFF']:
|
||||
if 'exif' in img.info:
|
||||
exif_dict = piexif.load(img.info.get("exif", b''))
|
||||
if img.format in ["JPEG", "TIFF"]:
|
||||
if "exif" in img.info:
|
||||
exif_dict = piexif.load(img.info.get("exif", b""))
|
||||
description_key = piexif.ImageIFD.ImageDescription
|
||||
encoded_message = exif_dict["0th"][description_key]
|
||||
else:
|
||||
encoded_message = b''
|
||||
encoded_message = b""
|
||||
else:
|
||||
raise ValueError("Given file is neither JPEG nor TIFF.")
|
||||
finally:
|
||||
|
@ -87,41 +92,70 @@ if __name__ == "__main__":
|
|||
# Point of entry in execution mode.
|
||||
# TODO: improve the management of arguments
|
||||
from optparse import OptionParser
|
||||
|
||||
parser = OptionParser(version=__version__)
|
||||
parser.add_option('--hide', action='store_true', default=False,
|
||||
help="Hides a message in an image.")
|
||||
parser.add_option('--reveal', action='store_true', default=False,
|
||||
help="Reveals the message hided in an image.")
|
||||
parser.add_option(
|
||||
"--hide",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Hides a message in an image.",
|
||||
)
|
||||
parser.add_option(
|
||||
"--reveal",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Reveals the message hided in an image.",
|
||||
)
|
||||
# Original image
|
||||
parser.add_option("-i", "--input", dest="input_image_file",
|
||||
help="Input image file.")
|
||||
parser.add_option(
|
||||
"-i", "--input", dest="input_image_file", help="Input image file."
|
||||
)
|
||||
# Image containing the secret
|
||||
parser.add_option("-o", "--output", dest="output_image_file",
|
||||
help="Output image containing the secret.")
|
||||
parser.add_option(
|
||||
"-o",
|
||||
"--output",
|
||||
dest="output_image_file",
|
||||
help="Output image containing the secret.",
|
||||
)
|
||||
|
||||
# Secret raw message to hide
|
||||
parser.add_option("-m", "--secret-message", dest="secret_message",
|
||||
help="Your raw secret message to hide.")
|
||||
parser.add_option(
|
||||
"-m",
|
||||
"--secret-message",
|
||||
dest="secret_message",
|
||||
help="Your raw secret message to hide.",
|
||||
)
|
||||
|
||||
# Secret text file to hide.
|
||||
parser.add_option("-f", "--secret-file", dest="secret_file",
|
||||
help="Your secret text file to hide.")
|
||||
parser.add_option(
|
||||
"-f",
|
||||
"--secret-file",
|
||||
dest="secret_file",
|
||||
help="Your secret text file to hide.",
|
||||
)
|
||||
|
||||
parser.set_defaults(input_image_file = './pictures/Elisha-Cuthbert.jpg',
|
||||
output_image_file = './pictures/Elisha-Cuthbert_enc.jpg',
|
||||
secret_message = '', secret_file = '')
|
||||
parser.set_defaults(
|
||||
input_image_file="./pictures/Elisha-Cuthbert.jpg",
|
||||
output_image_file="./pictures/Elisha-Cuthbert_enc.jpg",
|
||||
secret_message="",
|
||||
secret_file="",
|
||||
)
|
||||
|
||||
(options, args) = parser.parse_args()
|
||||
|
||||
if options.hide:
|
||||
if options.secret_message != "" and options.secret_file == "":
|
||||
hide(input_image_file=options.input_image_file, \
|
||||
img_enc=options.output_image_file, \
|
||||
secret_message=options.secret_message)
|
||||
hide(
|
||||
input_image_file=options.input_image_file,
|
||||
img_enc=options.output_image_file,
|
||||
secret_message=options.secret_message,
|
||||
)
|
||||
elif options.secret_message == "" and options.secret_file != "":
|
||||
hide(input_image_file=options.input_image_file, \
|
||||
img_enc=options.output_image_file, \
|
||||
secret_file=options.secret_file)
|
||||
hide(
|
||||
input_image_file=options.input_image_file,
|
||||
img_enc=options.output_image_file,
|
||||
secret_file=options.secret_file,
|
||||
)
|
||||
|
||||
elif options.reveal:
|
||||
reveal(input_image_file=options.input_image_file)
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#!/usr/bin/env python
|
||||
#-*- coding: utf-8 -*-
|
||||
|
||||
from .lsb import *
|
||||
from .lsb import hide, reveal
|
||||
|
||||
__all__ = ["hide", "reveal"]
|
||||
|
|
254
stegano/lsb/generators.py
Normal file
254
stegano/lsb/generators.py
Normal file
|
@ -0,0 +1,254 @@
|
|||
#!/usr/bin/env python
|
||||
# Stegano - Stegano is a pure Python steganography module.
|
||||
# Copyright (C) 2010-2025 Cédric Bonhomme - https://www.cedricbonhomme.org
|
||||
#
|
||||
# For more information : https://github.com/cedricbonhomme/Stegano
|
||||
#
|
||||
# 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
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
__author__ = "Cedric Bonhomme"
|
||||
__version__ = "$Revision: 0.3 $"
|
||||
__date__ = "$Date: 2011/12/28 $"
|
||||
__revision__ = "$Date: 2021/11/29 $"
|
||||
__license__ = "GPLv3"
|
||||
|
||||
import itertools
|
||||
import math
|
||||
from typing import Any, Dict, Iterator, List
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
|
||||
|
||||
def identity() -> Iterator[int]:
|
||||
"""f(x) = x"""
|
||||
n = 0
|
||||
while True:
|
||||
yield n
|
||||
n += 1
|
||||
|
||||
|
||||
def triangular_numbers() -> Iterator[int]:
|
||||
"""Triangular numbers: a(n) = C(n+1,2) = n(n+1)/2 = 0+1+2+...+n.
|
||||
http://oeis.org/A000217
|
||||
"""
|
||||
n = 0
|
||||
while True:
|
||||
yield (n * (n + 1)) // 2
|
||||
n += 1
|
||||
|
||||
|
||||
def fermat() -> Iterator[int]:
|
||||
"""Generate the n-th Fermat Number.
|
||||
https://oeis.org/A000215
|
||||
"""
|
||||
y = 3
|
||||
while True:
|
||||
yield y
|
||||
y = pow(y - 1, 2) + 1
|
||||
|
||||
|
||||
def mersenne() -> Iterator[int]:
|
||||
"""Generate 2^p - 1, where p is prime.
|
||||
https://oeis.org/A001348
|
||||
"""
|
||||
prime_numbers = eratosthenes()
|
||||
while True:
|
||||
yield 2 ** next(prime_numbers) - 1
|
||||
|
||||
|
||||
def eratosthenes() -> Iterator[int]:
|
||||
"""Generate the prime numbers with the sieve of Eratosthenes.
|
||||
https://oeis.org/A000040
|
||||
"""
|
||||
d: Dict[int, List[int]] = {}
|
||||
for i in itertools.count(2):
|
||||
if i in d:
|
||||
for j in d[i]:
|
||||
d[i + j] = d.get(i + j, []) + [j]
|
||||
del d[i]
|
||||
else:
|
||||
d[i * i] = [i]
|
||||
yield i
|
||||
|
||||
|
||||
def composite() -> Iterator[int]:
|
||||
"""Generate the composite numbers using the sieve of Eratosthenes.
|
||||
https://oeis.org/A002808
|
||||
"""
|
||||
p1 = 3
|
||||
for p2 in eratosthenes():
|
||||
yield from range(p1 + 1, p2)
|
||||
p1 = p2
|
||||
|
||||
|
||||
def carmichael() -> Iterator[int]:
|
||||
"""Composite numbers n such that a^(n-1) == 1 (mod n) for every a coprime
|
||||
to n.
|
||||
https://oeis.org/A002997
|
||||
"""
|
||||
for m in composite():
|
||||
for a in range(2, m):
|
||||
if pow(a, m, m) != a:
|
||||
break
|
||||
else:
|
||||
yield m
|
||||
|
||||
|
||||
def ackermann_slow(m: int, n: int) -> int:
|
||||
"""Ackermann number."""
|
||||
if m == 0:
|
||||
return n + 1
|
||||
elif n == 0:
|
||||
return ackermann_slow(m - 1, 1)
|
||||
else:
|
||||
return ackermann_slow(m - 1, ackermann_slow(m, n - 1))
|
||||
|
||||
|
||||
def ackermann_naive(m: int) -> Iterator[int]:
|
||||
"""Naive Ackermann encapsulated in a generator."""
|
||||
n = 0
|
||||
while True:
|
||||
yield ackermann_slow(m, n)
|
||||
n += 1
|
||||
|
||||
|
||||
def ackermann_fast(m: int, n: int) -> int:
|
||||
"""Ackermann number."""
|
||||
while m >= 4:
|
||||
if n == 0:
|
||||
n = 1
|
||||
else:
|
||||
n = ackermann_fast(m, n - 1)
|
||||
m -= 1
|
||||
if m == 3:
|
||||
return (1 << n + 3) - 3
|
||||
elif m == 2:
|
||||
return (n << 1) + 3
|
||||
elif m == 1:
|
||||
return n + 2
|
||||
else:
|
||||
return n + 1
|
||||
|
||||
|
||||
def ackermann(m: int) -> Iterator[int]:
|
||||
"""Ackermann encapsulated in a generator."""
|
||||
n = 0
|
||||
while True:
|
||||
yield ackermann_fast(m, n)
|
||||
n += 1
|
||||
|
||||
|
||||
def fibonacci() -> Iterator[int]:
|
||||
"""Generate the sequence of Fibonacci.
|
||||
https://oeis.org/A000045
|
||||
"""
|
||||
a, b = 1, 2
|
||||
while True:
|
||||
yield a
|
||||
a, b = b, a + b
|
||||
|
||||
|
||||
def log_gen() -> Iterator[int]:
|
||||
"""Logarithmic generator."""
|
||||
y = 1
|
||||
while True:
|
||||
adder = max(1, math.pow(10, int(math.log10(y))))
|
||||
yield int(y)
|
||||
y = y + int(adder)
|
||||
|
||||
|
||||
polys = {
|
||||
2: [2, 1],
|
||||
3: [3, 1],
|
||||
4: [4, 1],
|
||||
5: [5, 2],
|
||||
6: [6, 1],
|
||||
7: [7, 1],
|
||||
8: [8, 4, 3, 2],
|
||||
9: [9, 4],
|
||||
10: [10, 3],
|
||||
11: [11, 2],
|
||||
12: [12, 6, 4, 1],
|
||||
13: [13, 4, 3, 1],
|
||||
14: [14, 8, 6, 1],
|
||||
15: [15, 1],
|
||||
16: [16, 12, 3, 1],
|
||||
17: [17, 3],
|
||||
18: [18, 7],
|
||||
19: [19, 5, 2, 1],
|
||||
20: [20, 3],
|
||||
21: [21, 2],
|
||||
22: [22, 1],
|
||||
23: [23, 5],
|
||||
24: [24, 7, 2, 1],
|
||||
25: [25, 3],
|
||||
26: [26, 6, 2, 1],
|
||||
27: [27, 5, 2, 1],
|
||||
28: [28, 3],
|
||||
29: [29, 2],
|
||||
30: [30, 23, 2, 1],
|
||||
31: [31, 3],
|
||||
}
|
||||
|
||||
|
||||
def LFSR(m: int) -> Iterator[int]:
|
||||
"""LFSR generator of the given size
|
||||
https://en.wikipedia.org/wiki/Linear-feedback_shift_register
|
||||
"""
|
||||
n: int = m.bit_length() - 1
|
||||
# Set initial state to {1 0 0 ... 0}
|
||||
state: List[int] = [0] * n
|
||||
state[0] = 1
|
||||
feedback: int = 0
|
||||
poly: List[int] = polys[n]
|
||||
while True:
|
||||
# Compute the feedback bit
|
||||
feedback = 0
|
||||
for i in range(len(poly)):
|
||||
feedback = feedback ^ state[poly[i] - 1]
|
||||
# Roll the registers
|
||||
state.pop()
|
||||
# Add the feedback bit
|
||||
state.insert(0, feedback)
|
||||
# Convert the registers to an int
|
||||
out = sum(e * (2**i) for i, e in enumerate(state))
|
||||
yield out
|
||||
|
||||
|
||||
def shi_tomashi(
|
||||
image_path: str,
|
||||
max_corners: int = 100,
|
||||
quality: float = 0.01,
|
||||
min_distance: float = 10.0,
|
||||
) -> Iterator[int]:
|
||||
"""Shi-Tomachi corner generator of the given points
|
||||
https://docs.opencv.org/4.x/d4/d8c/tutorial_py_shi_tomasi.html
|
||||
"""
|
||||
image = cv2.imread(image_path)
|
||||
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
|
||||
corners: np.ndarray = cv2.goodFeaturesToTrack(
|
||||
gray, max_corners, quality, min_distance
|
||||
)
|
||||
corners_int: np.ndarray[Any, np.dtype[np.signedinteger[Any]]] = np.array(
|
||||
np.intp(corners)
|
||||
)
|
||||
i = 0
|
||||
while True:
|
||||
x, y = corners_int[i].ravel()
|
||||
# Compute the pixel number with top left of image as origin
|
||||
# using coordinates of the corner.
|
||||
# (y * number of pixels a row) + pixels left in last row
|
||||
yield (y * image.shape[1]) + x
|
||||
i += 1
|
143
stegano/lsb/lsb.py
Executable file → Normal file
143
stegano/lsb/lsb.py
Executable file → Normal file
|
@ -1,8 +1,6 @@
|
|||
#!/usr/bin/env python
|
||||
#-*- coding: utf-8 -*-
|
||||
|
||||
# Stéganô - Stéganô is a basic Python Steganography module.
|
||||
# Copyright (C) 2010-2017 Cédric Bonhomme - https://www.cedricbonhomme.org
|
||||
# Stegano - Stegano is a pure Python steganography module.
|
||||
# Copyright (C) 2010-2025 Cédric Bonhomme - https://www.cedricbonhomme.org
|
||||
#
|
||||
# For more information : https://github.com/cedricbonhomme/Stegano
|
||||
#
|
||||
|
@ -20,106 +18,73 @@
|
|||
# along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
__author__ = "Cedric Bonhomme"
|
||||
__version__ = "$Revision: 0.3 $"
|
||||
__date__ = "$Date: 2016/08/04 $"
|
||||
__revision__ = "$Date: 2017/05/04 $"
|
||||
__version__ = "$Revision: 0.7 $"
|
||||
__date__ = "$Date: 2016/03/13 $"
|
||||
__revision__ = "$Date: 2019/05/31 $"
|
||||
__license__ = "GPLv3"
|
||||
|
||||
import sys
|
||||
|
||||
from PIL import Image
|
||||
from typing import Union, IO
|
||||
from typing import IO, Iterator, Union
|
||||
|
||||
from stegano import tools
|
||||
|
||||
def hide(input_image: Union[str, IO[bytes]],
|
||||
message: str,
|
||||
encoding: str = 'UTF-8',
|
||||
auto_convert_rgb: bool = False):
|
||||
from .generators import identity
|
||||
|
||||
|
||||
def hide(
|
||||
image: Union[str, IO[bytes]],
|
||||
message: str,
|
||||
generator: Union[None, Iterator[int]] = None,
|
||||
shift: int = 0,
|
||||
encoding: str = "UTF-8",
|
||||
auto_convert_rgb: bool = False,
|
||||
):
|
||||
"""Hide a message (string) in an image with the
|
||||
LSB (Least Significant Bit) technique.
|
||||
"""
|
||||
message_length = len(message)
|
||||
assert message_length != 0, "message length is zero"
|
||||
hider = tools.Hider(image, message, encoding, auto_convert_rgb)
|
||||
width = hider.encoded_image.width
|
||||
|
||||
img = Image.open(input_image)
|
||||
if not generator:
|
||||
generator = identity()
|
||||
|
||||
if img.mode not in ['RGB', 'RGBA']:
|
||||
if not auto_convert_rgb:
|
||||
print('The mode of the image is not RGB. Mode is {}'.\
|
||||
format(img.mode))
|
||||
answer = input('Convert the image to RGB ? [Y / n]\n') or 'Y'
|
||||
if answer.lower() == 'n':
|
||||
raise Exception('Not a RGB image.')
|
||||
img = img.convert('RGB')
|
||||
while shift != 0:
|
||||
next(generator)
|
||||
shift -= 1
|
||||
|
||||
encoded = img.copy()
|
||||
width, height = img.size
|
||||
index = 0
|
||||
while hider.encode_another_pixel():
|
||||
generated_number = next(generator)
|
||||
|
||||
message = str(message_length) + ":" + str(message)
|
||||
message_bits = "".join(tools.a2bits_list(message, encoding))
|
||||
message_bits += '0' * ((3 - (len(message_bits) % 3)) % 3)
|
||||
col = generated_number % width
|
||||
row = int(generated_number / width)
|
||||
|
||||
npixels = width * height
|
||||
len_message_bits = len(message_bits)
|
||||
if len_message_bits > npixels * 3:
|
||||
raise Exception("The message you want to hide is too long: {}". \
|
||||
format(message_length))
|
||||
for row in range(height):
|
||||
for col in range(width):
|
||||
if index + 3 <= len_message_bits :
|
||||
hider.encode_pixel((col, row))
|
||||
|
||||
# Get the colour component.
|
||||
pixel = img.getpixel((col, row))
|
||||
r = pixel[0]
|
||||
g = pixel[1]
|
||||
b = pixel[2]
|
||||
|
||||
# Change the Least Significant Bit of each colour component.
|
||||
r = tools.setlsb(r, message_bits[index])
|
||||
g = tools.setlsb(g, message_bits[index+1])
|
||||
b = tools.setlsb(b, message_bits[index+2])
|
||||
|
||||
# Save the new pixel
|
||||
if img.mode == 'RGBA':
|
||||
encoded.putpixel((col, row), (r, g, b, pixel[3]))
|
||||
else:
|
||||
encoded.putpixel((col, row), (r, g, b))
|
||||
|
||||
index += 3
|
||||
else:
|
||||
img.close()
|
||||
return encoded
|
||||
return hider.encoded_image
|
||||
|
||||
|
||||
def reveal(input_image: Union[str, IO[bytes]], encoding='UTF-8'):
|
||||
"""Find a message in an image (with the LSB technique).
|
||||
"""
|
||||
img = Image.open(input_image)
|
||||
width, height = img.size
|
||||
buff, count = 0, 0
|
||||
bitab = []
|
||||
limit = None
|
||||
for row in range(height):
|
||||
for col in range(width):
|
||||
def reveal(
|
||||
encoded_image: Union[str, IO[bytes]],
|
||||
generator: Union[None, Iterator[int]] = None,
|
||||
shift: int = 0,
|
||||
encoding: str = "UTF-8",
|
||||
close_file: bool = True,
|
||||
):
|
||||
"""Find a message in an image (with the LSB technique)."""
|
||||
revealer = tools.Revealer(encoded_image, encoding, close_file)
|
||||
width = revealer.encoded_image.width
|
||||
|
||||
# pixel = [r, g, b] or [r,g,b,a]
|
||||
pixel = img.getpixel((col, row))
|
||||
if img.mode == 'RGBA':
|
||||
pixel = pixel[:3] # ignore the alpha
|
||||
for color in pixel:
|
||||
buff += (color&1)<<(tools.ENCODINGS[encoding]-1 - count)
|
||||
count += 1
|
||||
if count == tools.ENCODINGS[encoding]:
|
||||
bitab.append(chr(buff))
|
||||
buff, count = 0, 0
|
||||
if bitab[-1] == ":" and limit == None:
|
||||
try:
|
||||
limit = int("".join(bitab[:-1]))
|
||||
except:
|
||||
pass
|
||||
if not generator:
|
||||
generator = identity()
|
||||
|
||||
if len(bitab)-len(str(limit))-1 == limit :
|
||||
img.close()
|
||||
return "".join(bitab)[len(str(limit))+1:]
|
||||
while shift != 0:
|
||||
next(generator)
|
||||
shift -= 1
|
||||
|
||||
while True:
|
||||
generated_number = next(generator)
|
||||
|
||||
col = generated_number % width
|
||||
row = int(generated_number / width)
|
||||
|
||||
if revealer.decode_pixel((col, row)):
|
||||
return revealer.secret_message
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
#-*- coding: utf-8 -*-
|
||||
|
||||
from .lsbset import *
|
|
@ -1,146 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Stéganô - Stéganô is a basic Python Steganography module.
|
||||
# Copyright (C) 2010-2017 Cédric Bonhomme - https://www.cedricbonhomme.org
|
||||
#
|
||||
# For more information : https://github.com/cedricbonhomme/Stegano
|
||||
#
|
||||
# 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
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
__author__ = "Cedric Bonhomme"
|
||||
__version__ = "$Revision: 0.3 $"
|
||||
__date__ = "$Date: 2011/12/28 $"
|
||||
__revision__ = "$Date: 2017/03/10 $"
|
||||
__license__ = "GPLv3"
|
||||
|
||||
import math
|
||||
import itertools
|
||||
from typing import Iterator, List, Dict
|
||||
|
||||
def identity() -> Iterator[int]:
|
||||
"""f(x) = x
|
||||
"""
|
||||
n = 0
|
||||
while True:
|
||||
yield n
|
||||
n += 1
|
||||
|
||||
def triangular_numbers() -> Iterator[int]:
|
||||
"""Triangular numbers: a(n) = C(n+1,2) = n(n+1)/2 = 0+1+2+...+n.
|
||||
http://oeis.org/A000217
|
||||
"""
|
||||
n = 0
|
||||
while True:
|
||||
yield (n*(n+1))//2
|
||||
n += 1
|
||||
|
||||
def fermat() -> Iterator[int]:
|
||||
"""Generate the n-th Fermat Number.
|
||||
https://oeis.org/A000215
|
||||
"""
|
||||
y = 3
|
||||
while True:
|
||||
yield y
|
||||
y = pow(y-1,2)+1
|
||||
|
||||
def mersenne() -> Iterator[int]:
|
||||
"""Generate 2^p - 1, where p is prime.
|
||||
https://oeis.org/A001348
|
||||
"""
|
||||
prime_numbers = eratosthenes()
|
||||
while True:
|
||||
yield 2**next(prime_numbers) - 1
|
||||
|
||||
def eratosthenes() -> Iterator[int]:
|
||||
"""Generate the prime numbers with the sieve of Eratosthenes.
|
||||
https://oeis.org/A000040
|
||||
"""
|
||||
d = {} # type: Dict[int, List[int]]
|
||||
for i in itertools.count(2):
|
||||
if i in d:
|
||||
for j in d[i]:
|
||||
d[i + j] = d.get(i + j, []) + [j]
|
||||
del d[i]
|
||||
else:
|
||||
d[i * i] = [i]
|
||||
yield i
|
||||
|
||||
def composite() -> Iterator[int]:
|
||||
"""Generate the composite numbers using the sieve of Eratosthenes.
|
||||
https://oeis.org/A002808
|
||||
"""
|
||||
p1 = 3
|
||||
for p2 in eratosthenes():
|
||||
for n in range(p1 + 1, p2):
|
||||
yield n
|
||||
p1 = p2
|
||||
|
||||
def carmichael() -> Iterator[int]:
|
||||
"""Composite numbers n such that a^(n-1) == 1 (mod n) for every a coprime
|
||||
to n.
|
||||
https://oeis.org/A002997
|
||||
"""
|
||||
for m in composite():
|
||||
for a in range(2, m):
|
||||
if pow(a,m,m) != a:
|
||||
break
|
||||
else:
|
||||
yield m
|
||||
|
||||
def ackermann_naive(m: int, n: int) -> int:
|
||||
"""Ackermann number.
|
||||
"""
|
||||
if m == 0:
|
||||
return n + 1
|
||||
elif n == 0:
|
||||
return ackermann(m - 1, 1)
|
||||
else:
|
||||
return ackermann(m - 1, ackermann(m, n - 1))
|
||||
|
||||
def ackermann(m: int, n: int) -> int:
|
||||
"""Ackermann number.
|
||||
"""
|
||||
while m >= 4:
|
||||
if n == 0:
|
||||
n = 1
|
||||
else:
|
||||
n = ackermann(m, n - 1)
|
||||
m -= 1
|
||||
if m == 3:
|
||||
return (1 << n + 3) - 3
|
||||
elif m == 2:
|
||||
return (n << 1) + 3
|
||||
elif m == 1:
|
||||
return n + 2
|
||||
else:
|
||||
return n + 1
|
||||
|
||||
def fibonacci() -> Iterator[int]:
|
||||
"""Generate the sequence of Fibonacci.
|
||||
https://oeis.org/A000045
|
||||
"""
|
||||
a, b = 1, 2
|
||||
while True:
|
||||
yield a
|
||||
a, b = b, a + b
|
||||
|
||||
def log_gen() -> Iterator[int]:
|
||||
"""Logarithmic generator.
|
||||
"""
|
||||
y = 1
|
||||
while True:
|
||||
adder = max(1, math.pow(10, int(math.log10(y))))
|
||||
yield int(y)
|
||||
y = y + int(adder)
|
|
@ -1,125 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
#-*- coding: utf-8 -*-
|
||||
|
||||
# Stéganô - Stéganô is a basic Python Steganography module.
|
||||
# Copyright (C) 2010-2017 Cédric Bonhomme - https://www.cedricbonhomme.org
|
||||
#
|
||||
# For more information : https://github.com/cedricbonhomme/Stegano
|
||||
#
|
||||
# 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
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
__author__ = "Cedric Bonhomme"
|
||||
__version__ = "$Revision: 0.5 $"
|
||||
__date__ = "$Date: 2016/03/13 $"
|
||||
__revision__ = "$Date: 2017/05/04 $"
|
||||
__license__ = "GPLv3"
|
||||
|
||||
import sys
|
||||
|
||||
from PIL import Image
|
||||
from typing import Union, Iterator, IO
|
||||
|
||||
from stegano import tools
|
||||
from . import generators
|
||||
|
||||
def hide(input_image: Union[str, IO[bytes]],
|
||||
message: str,
|
||||
generator: Iterator[int],
|
||||
encoding: str = 'UTF-8',
|
||||
auto_convert_rgb: bool = False):
|
||||
"""Hide a message (string) in an image with the
|
||||
LSB (Least Significant Bit) technique.
|
||||
"""
|
||||
message_length = len(message)
|
||||
assert message_length != 0, "message length is zero"
|
||||
|
||||
img = Image.open(input_image)
|
||||
|
||||
if img.mode not in ['RGB', 'RGBA']:
|
||||
if not auto_convert_rgb:
|
||||
print('The mode of the image is not RGB. Mode is {}'.\
|
||||
format(img.mode))
|
||||
answer = input('Convert the image to RGB ? [Y / n]\n') or 'Y'
|
||||
if answer.lower() == 'n':
|
||||
raise Exception('Not a RGB image.')
|
||||
img = img.convert('RGB')
|
||||
|
||||
img_list = list(img.getdata())
|
||||
width, height = img.size
|
||||
index = 0
|
||||
|
||||
message = str(message_length) + ":" + str(message)
|
||||
message_bits = "".join(tools.a2bits_list(message, encoding))
|
||||
message_bits += '0' * ((3 - (len(message_bits) % 3)) % 3)
|
||||
|
||||
npixels = width * height
|
||||
len_message_bits = len(message_bits)
|
||||
if len_message_bits > npixels * 3:
|
||||
raise Exception("The message you want to hide is too long: {}". \
|
||||
format(message_length))
|
||||
|
||||
while index + 3 <= len_message_bits :
|
||||
generated_number = next(generator)
|
||||
r, g, b, *a = img_list[generated_number]
|
||||
|
||||
# Change the Least Significant Bit of each colour component.
|
||||
r = tools.setlsb(r, message_bits[index])
|
||||
g = tools.setlsb(g, message_bits[index+1])
|
||||
b = tools.setlsb(b, message_bits[index+2])
|
||||
|
||||
# Save the new pixel
|
||||
if img.mode == 'RGBA':
|
||||
img_list[generated_number] = (r, g , b, a[0])
|
||||
else:
|
||||
img_list[generated_number] = (r, g , b)
|
||||
|
||||
index += 3
|
||||
|
||||
# create empty new image of appropriate format
|
||||
encoded = Image.new('RGB', (img.size))
|
||||
|
||||
# insert saved data into the image
|
||||
encoded.putdata(img_list)
|
||||
|
||||
return encoded
|
||||
|
||||
|
||||
def reveal(input_image: Union[str, IO[bytes]],
|
||||
generator: Iterator[int],
|
||||
encoding: str = 'UTF-8'):
|
||||
"""Find a message in an image (with the LSB technique).
|
||||
"""
|
||||
img = Image.open(input_image)
|
||||
img_list = list(img.getdata())
|
||||
width, height = img.size
|
||||
buff, count = 0, 0
|
||||
bitab = []
|
||||
limit = None
|
||||
|
||||
while True:
|
||||
generated_number = next(generator)
|
||||
# color = [r, g, b]
|
||||
for color in img_list[generated_number]:
|
||||
buff += (color&1)<<(tools.ENCODINGS[encoding]-1 - count)
|
||||
count += 1
|
||||
if count == tools.ENCODINGS[encoding]:
|
||||
bitab.append(chr(buff))
|
||||
buff, count = 0, 0
|
||||
if bitab[-1] == ":" and limit == None:
|
||||
if "".join(bitab[:-1]).isdigit():
|
||||
limit = int("".join(bitab[:-1]))
|
||||
else:
|
||||
raise IndexError("Impossible to detect message.")
|
||||
if len(bitab)-len(str(limit))-1 == limit :
|
||||
return "".join(bitab)[len(str(limit))+1:]
|
|
@ -1,4 +1,5 @@
|
|||
#!/usr/bin/env python
|
||||
#-*- coding: utf-8 -*-
|
||||
|
||||
from .red import *
|
||||
from .red import hide, reveal
|
||||
|
||||
__all__ = ["hide", "reveal"]
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Stéganô - Stéganô is a basic Python Steganography module.
|
||||
# Copyright (C) 2010-2017 Cédric Bonhomme - https://www.cedricbonhomme.org
|
||||
# Stegano - Stéganô is a basic Python Steganography module.
|
||||
# Copyright (C) 2010-2024 Cédric Bonhomme - https://www.cedricbonhomme.org
|
||||
#
|
||||
# For more information : https://github.com/cedricbonhomme/Stegano
|
||||
#
|
||||
|
@ -25,10 +23,10 @@ __date__ = "$Date: 2010/10/01 $"
|
|||
__revision__ = "$Date: 2017/02/06 $"
|
||||
__license__ = "GPLv3"
|
||||
|
||||
import sys
|
||||
from typing import IO, Union
|
||||
|
||||
from stegano import tools
|
||||
|
||||
from PIL import Image
|
||||
from typing import Union, IO
|
||||
|
||||
def hide(input_image: Union[str, IO[bytes]], message: str):
|
||||
"""
|
||||
|
@ -41,7 +39,7 @@ def hide(input_image: Union[str, IO[bytes]], message: str):
|
|||
message_length = len(message)
|
||||
assert message_length != 0, "message message_length is zero"
|
||||
assert message_length < 255, "message is too long"
|
||||
img = Image.open(input_image)
|
||||
img = tools.open_image(input_image)
|
||||
# Use a copy of image to hide the text in
|
||||
encoded = img.copy()
|
||||
width, height = img.size
|
||||
|
@ -53,15 +51,16 @@ def hide(input_image: Union[str, IO[bytes]], message: str):
|
|||
if row == 0 and col == 0 and index < message_length:
|
||||
asc = message_length
|
||||
elif index <= message_length:
|
||||
c = message[index -1]
|
||||
c = message[index - 1]
|
||||
asc = ord(c)
|
||||
else:
|
||||
asc = r
|
||||
encoded.putpixel((col, row), (asc, g , b))
|
||||
encoded.putpixel((col, row), (asc, g, b))
|
||||
index += 1
|
||||
img.close()
|
||||
return encoded
|
||||
|
||||
|
||||
def reveal(input_image: Union[str, IO[bytes]]):
|
||||
"""
|
||||
Find a message in an image.
|
||||
|
@ -70,7 +69,7 @@ def reveal(input_image: Union[str, IO[bytes]]):
|
|||
hidden message characters (ASCII values).
|
||||
The red value of the first pixel is used for message_length of string.
|
||||
"""
|
||||
img = Image.open(input_image)
|
||||
img = tools.open_image(input_image)
|
||||
width, height = img.size
|
||||
message = ""
|
||||
index = 0
|
||||
|
|
|
@ -1,5 +1 @@
|
|||
#!/usr/bin/env python
|
||||
#-*- coding: utf-8 -*-
|
||||
|
||||
from .parity import *
|
||||
from .statistics import *
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
#!/usr/bin/env python
|
||||
#-*- coding: utf-8 -*-
|
||||
|
||||
# Stéganô - Stéganô is a basic Python Steganography module.
|
||||
# Copyright (C) 2010-2017 Cédric Bonhomme - https://www.cedricbonhomme.org
|
||||
# Stegano - Stegano is a pure Python steganography module.
|
||||
# Copyright (C) 2010-2025 Cédric Bonhomme - https://www.cedricbonhomme.org
|
||||
#
|
||||
# For more information : https://github.com/cedricbonhomme/Stegano
|
||||
#
|
||||
|
@ -20,22 +18,26 @@
|
|||
# along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
__author__ = "Cedric Bonhomme"
|
||||
__version__ = "$Revision: 0.1 $"
|
||||
__version__ = "$Revision: 0.9.4 $"
|
||||
__date__ = "$Date: 2010/10/01 $"
|
||||
__revision__ = "$Date: 2019/06/06 $"
|
||||
__license__ = "GPLv3"
|
||||
|
||||
from PIL import Image
|
||||
|
||||
def steganalyse(img):
|
||||
|
||||
def steganalyse(img: Image.Image) -> Image.Image:
|
||||
"""
|
||||
Steganlysis of the LSB technique.
|
||||
"""
|
||||
encoded = img.copy()
|
||||
encoded = Image.new(img.mode, (img.size))
|
||||
width, height = img.size
|
||||
bits = ""
|
||||
for row in range(height):
|
||||
for col in range(width):
|
||||
r, g, b = img.getpixel((col, row))
|
||||
if pixel := img.getpixel((col, row)):
|
||||
r, g, b = pixel[0:3]
|
||||
else:
|
||||
raise Exception("Error during steganlysis.")
|
||||
if r % 2 == 0:
|
||||
r = 0
|
||||
else:
|
||||
|
@ -48,5 +50,5 @@ def steganalyse(img):
|
|||
b = 0
|
||||
else:
|
||||
b = 255
|
||||
encoded.putpixel((col, row), (r, g , b))
|
||||
encoded.putpixel((col, row), (r, g, b))
|
||||
return encoded
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
#!/usr/bin/env python
|
||||
#-*- coding: utf-8 -*-
|
||||
|
||||
# Stéganô - Stéganô is a basic Python Steganography module.
|
||||
# Copyright (C) 2010-2017 Cédric Bonhomme - https://www.cedricbonhomme.org
|
||||
# Stegano - Stegano is a pure Python steganography module.
|
||||
# Copyright (C) 2010-2025 Cédric Bonhomme - https://www.cedricbonhomme.org
|
||||
#
|
||||
# For more information : https://github.com/cedricbonhomme/Stegano
|
||||
#
|
||||
|
@ -22,35 +20,33 @@
|
|||
__author__ = "Cedric Bonhomme"
|
||||
__version__ = "$Revision: 0.2 $"
|
||||
__date__ = "$Date: 2010/10/01 $"
|
||||
__revision__ = "$Date: 2016/08/26 $"
|
||||
__revision__ = "$Date: 2021/11/01 $"
|
||||
__license__ = "GPLv3"
|
||||
|
||||
import operator
|
||||
import typing
|
||||
from collections import Counter, OrderedDict
|
||||
|
||||
from PIL import Image
|
||||
from collections import Counter
|
||||
from collections import OrderedDict
|
||||
|
||||
def steganalyse(img):
|
||||
"""
|
||||
Steganlysis of the LSB technique.
|
||||
"""
|
||||
encoded = img.copy()
|
||||
width, height = img.size
|
||||
colours_counter = Counter() # type: Counter[int]
|
||||
colours_counter: typing.Counter[int] = Counter()
|
||||
for row in range(height):
|
||||
for col in range(width):
|
||||
r, g, b = img.getpixel((col, row))
|
||||
colours_counter[r] += 1
|
||||
|
||||
most_common = colours_counter.most_common(10)
|
||||
dict_colours = OrderedDict(sorted(list(colours_counter.items()),
|
||||
key=lambda t: t[1]))
|
||||
dict_colours = OrderedDict(
|
||||
sorted(list(colours_counter.items()), key=lambda t: t[1])
|
||||
)
|
||||
|
||||
colours = 0 # type: float
|
||||
colours: float = 0
|
||||
for colour in list(dict_colours.keys()):
|
||||
colours += colour
|
||||
colours = colours / len(dict_colours)
|
||||
|
||||
#return colours.most_common(10)
|
||||
# return colours.most_common(10)
|
||||
return list(dict_colours.keys())[:30], most_common
|
||||
|
|
166
stegano/tools.py
166
stegano/tools.py
|
@ -1,8 +1,6 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Stéganô - Stéganô is a basic Python Steganography module.
|
||||
# Copyright (C) 2010-2017 Cédric Bonhomme - https://www.cedricbonhomme.org
|
||||
# Stegano - Stegano is a pure Python steganography module.
|
||||
# Copyright (C) 2010-2025 Cédric Bonhomme - https://www.cedricbonhomme.org
|
||||
#
|
||||
# For more information : https://github.com/cedricbonhomme/Stegano
|
||||
#
|
||||
|
@ -27,13 +25,13 @@ __license__ = "GPLv3"
|
|||
|
||||
import base64
|
||||
import itertools
|
||||
from typing import List, Iterator, Tuple, Union
|
||||
from functools import reduce
|
||||
from typing import IO, List, Union
|
||||
|
||||
from PIL import Image
|
||||
|
||||
ENCODINGS = {"UTF-8": 8, "UTF-32LE": 32}
|
||||
|
||||
ENCODINGS = {
|
||||
'UTF-8': 8,
|
||||
'UTF-32LE': 32
|
||||
}
|
||||
|
||||
def a2bits(chars: str) -> str:
|
||||
"""Converts a string to its bits representation as a string of 0's and 1's.
|
||||
|
@ -41,9 +39,10 @@ def a2bits(chars: str) -> str:
|
|||
>>> a2bits("Hello World!")
|
||||
'010010000110010101101100011011000110111100100000010101110110111101110010011011000110010000100001'
|
||||
"""
|
||||
return bin(reduce(lambda x, y : (x<<8)+y, (ord(c) for c in chars), 1))[3:]
|
||||
return bin(reduce(lambda x, y: (x << 8) + y, (ord(c) for c in chars), 1))[3:]
|
||||
|
||||
def a2bits_list(chars: str, encoding: str ='UTF-8') -> List[str]:
|
||||
|
||||
def a2bits_list(chars: str, encoding: str = "UTF-8") -> List[str]:
|
||||
"""Convert a string to its bits representation as a list of 0's and 1's.
|
||||
|
||||
>>> a2bits_list("Hello World!")
|
||||
|
@ -62,20 +61,20 @@ def a2bits_list(chars: str, encoding: str ='UTF-8') -> List[str]:
|
|||
>>> "".join(a2bits_list("Hello World!"))
|
||||
'010010000110010101101100011011000110111100100000010101110110111101110010011011000110010000100001'
|
||||
"""
|
||||
return [bin(ord(x))[2:].rjust(ENCODINGS[encoding],"0") for x in chars]
|
||||
return [bin(ord(x))[2:].rjust(ENCODINGS[encoding], "0") for x in chars]
|
||||
|
||||
|
||||
def bs(s: int) -> str:
|
||||
"""Converts an int to its bits representation as a string of 0's and 1's.
|
||||
"""
|
||||
return str(s) if s<=1 else bs(s>>1) + str(s&1)
|
||||
"""Converts an int to its bits representation as a string of 0's and 1's."""
|
||||
return str(s) if s <= 1 else bs(s >> 1) + str(s & 1)
|
||||
|
||||
|
||||
def setlsb(component: int, bit: str) -> int:
|
||||
"""Set Least Significant Bit of a colour component.
|
||||
"""
|
||||
"""Set Least Significant Bit of a colour component."""
|
||||
return component & ~1 | int(bit)
|
||||
|
||||
def n_at_a_time(items: List[int], n: int, fillvalue: str) \
|
||||
-> Iterator[Tuple[Union[int, str]]]:
|
||||
|
||||
def n_at_a_time(items: List[int], n: int, fillvalue: str):
|
||||
"""Returns an iterator which groups n items at a time.
|
||||
Any final partial tuple will be padded with the fillvalue
|
||||
|
||||
|
@ -85,6 +84,7 @@ def n_at_a_time(items: List[int], n: int, fillvalue: str) \
|
|||
it = iter(items)
|
||||
return itertools.zip_longest(*[it] * n, fillvalue=fillvalue)
|
||||
|
||||
|
||||
def binary2base64(binary_file: str) -> str:
|
||||
"""Convert a binary file (OGG, executable, etc.) to a
|
||||
printable string.
|
||||
|
@ -94,8 +94,130 @@ def binary2base64(binary_file: str) -> str:
|
|||
encoded_string = base64.b64encode(bin_file.read())
|
||||
return encoded_string.decode()
|
||||
|
||||
|
||||
def base642binary(b64_fname: str) -> bytes:
|
||||
"""Convert a printable string to a binary file.
|
||||
"""
|
||||
b64_fname += '==='
|
||||
"""Convert a printable string to a binary file."""
|
||||
b64_fname += "==="
|
||||
return base64.b64decode(b64_fname)
|
||||
|
||||
|
||||
def open_image(fname_or_instance: Union[str, IO[bytes]]):
|
||||
"""Opens a Image and returns it.
|
||||
|
||||
:param fname_or_instance: Can either be the location of the image as a
|
||||
string or the Image.Image instance itself.
|
||||
"""
|
||||
if isinstance(fname_or_instance, Image.Image):
|
||||
return fname_or_instance
|
||||
|
||||
return Image.open(fname_or_instance)
|
||||
|
||||
|
||||
class Hider:
|
||||
def __init__(
|
||||
self,
|
||||
input_image: Union[str, IO[bytes]],
|
||||
message: str,
|
||||
encoding: str = "UTF-8",
|
||||
auto_convert_rgb: bool = False,
|
||||
):
|
||||
self._index = 0
|
||||
|
||||
message_length = len(message)
|
||||
assert message_length != 0, "message length is zero"
|
||||
|
||||
image = open_image(input_image)
|
||||
|
||||
if image.mode not in ["RGB", "RGBA"]:
|
||||
if not auto_convert_rgb:
|
||||
print(f"The mode of the image is not RGB. Mode is {image.mode}")
|
||||
answer = input("Convert the image to RGB ? [Y / n]\n") or "Y"
|
||||
if answer.lower() == "n":
|
||||
raise Exception("Not a RGB image.")
|
||||
|
||||
image = image.convert("RGB")
|
||||
|
||||
self.encoded_image = image.copy()
|
||||
image.close()
|
||||
|
||||
message = str(message_length) + ":" + str(message)
|
||||
self._message_bits = "".join(a2bits_list(message, encoding))
|
||||
self._message_bits += "0" * ((3 - (len(self._message_bits) % 3)) % 3)
|
||||
|
||||
width, height = self.encoded_image.size
|
||||
npixels = width * height
|
||||
self._len_message_bits = len(self._message_bits)
|
||||
|
||||
if self._len_message_bits > npixels * 3:
|
||||
raise Exception(
|
||||
f"The message you want to hide is too long: {message_length}"
|
||||
)
|
||||
|
||||
def encode_another_pixel(self):
|
||||
return True if self._index + 3 <= self._len_message_bits else False
|
||||
|
||||
def encode_pixel(self, coordinate: tuple):
|
||||
# Get the colour component.
|
||||
r, g, b, *a = self.encoded_image.getpixel(coordinate)
|
||||
|
||||
# Change the Least Significant Bit of each colour component.
|
||||
r = setlsb(r, self._message_bits[self._index])
|
||||
g = setlsb(g, self._message_bits[self._index + 1])
|
||||
b = setlsb(b, self._message_bits[self._index + 2])
|
||||
|
||||
# Save the new pixel
|
||||
if self.encoded_image.mode == "RGBA":
|
||||
self.encoded_image.putpixel(coordinate, (r, g, b, *a))
|
||||
else:
|
||||
self.encoded_image.putpixel(coordinate, (r, g, b))
|
||||
|
||||
self._index += 3
|
||||
|
||||
|
||||
class Revealer:
|
||||
def __init__(
|
||||
self,
|
||||
encoded_image: Union[str, IO[bytes]],
|
||||
encoding: str = "UTF-8",
|
||||
close_file: bool = True,
|
||||
):
|
||||
self.encoded_image = open_image(encoded_image)
|
||||
self._encoding_length = ENCODINGS[encoding]
|
||||
self._buff, self._count = 0, 0
|
||||
self._bitab: List[str] = []
|
||||
self._limit: Union[None, int] = None
|
||||
self.secret_message = ""
|
||||
self.close_file = close_file
|
||||
|
||||
def decode_pixel(self, coordinate: tuple):
|
||||
# pixel = [r, g, b] or [r,g,b,a]
|
||||
pixel = self.encoded_image.getpixel(coordinate)
|
||||
|
||||
if self.encoded_image.mode == "RGBA":
|
||||
pixel = pixel[:3] # ignore the alpha
|
||||
|
||||
for color in pixel:
|
||||
self._buff += (color & 1) << (self._encoding_length - 1 - self._count)
|
||||
self._count += 1
|
||||
|
||||
if self._count == self._encoding_length:
|
||||
self._bitab.append(chr(self._buff))
|
||||
self._buff, self._count = 0, 0
|
||||
|
||||
if self._bitab[-1] == ":" and self._limit is None:
|
||||
if "".join(self._bitab[:-1]).isdigit():
|
||||
self._limit = int("".join(self._bitab[:-1]))
|
||||
else:
|
||||
raise IndexError("Impossible to detect message.")
|
||||
|
||||
if len(self._bitab) - len(str(self._limit)) - 1 == self._limit:
|
||||
self.secret_message = "".join(self._bitab)[
|
||||
len(str(self._limit)) + 1 : # noqa: E203
|
||||
]
|
||||
if self.close_file:
|
||||
self.encoded_image.close()
|
||||
|
||||
return True
|
||||
|
||||
else:
|
||||
return False
|
||||
|
|
256
tests/expected-results/LFSR
Normal file
256
tests/expected-results/LFSR
Normal file
|
@ -0,0 +1,256 @@
|
|||
2
|
||||
5
|
||||
11
|
||||
22
|
||||
44
|
||||
88
|
||||
177
|
||||
99
|
||||
199
|
||||
143
|
||||
30
|
||||
61
|
||||
122
|
||||
244
|
||||
232
|
||||
208
|
||||
161
|
||||
67
|
||||
135
|
||||
15
|
||||
31
|
||||
63
|
||||
127
|
||||
255
|
||||
254
|
||||
252
|
||||
249
|
||||
242
|
||||
228
|
||||
200
|
||||
144
|
||||
33
|
||||
66
|
||||
133
|
||||
10
|
||||
20
|
||||
41
|
||||
83
|
||||
167
|
||||
79
|
||||
159
|
||||
62
|
||||
125
|
||||
250
|
||||
245
|
||||
234
|
||||
213
|
||||
170
|
||||
85
|
||||
171
|
||||
87
|
||||
174
|
||||
92
|
||||
184
|
||||
112
|
||||
224
|
||||
193
|
||||
131
|
||||
6
|
||||
12
|
||||
24
|
||||
49
|
||||
98
|
||||
197
|
||||
138
|
||||
21
|
||||
43
|
||||
86
|
||||
172
|
||||
89
|
||||
179
|
||||
102
|
||||
204
|
||||
153
|
||||
50
|
||||
101
|
||||
203
|
||||
151
|
||||
47
|
||||
95
|
||||
191
|
||||
126
|
||||
253
|
||||
251
|
||||
247
|
||||
239
|
||||
222
|
||||
188
|
||||
121
|
||||
243
|
||||
230
|
||||
205
|
||||
155
|
||||
55
|
||||
110
|
||||
221
|
||||
187
|
||||
119
|
||||
238
|
||||
220
|
||||
185
|
||||
114
|
||||
229
|
||||
202
|
||||
149
|
||||
42
|
||||
84
|
||||
169
|
||||
82
|
||||
165
|
||||
74
|
||||
148
|
||||
40
|
||||
81
|
||||
162
|
||||
68
|
||||
137
|
||||
18
|
||||
37
|
||||
75
|
||||
150
|
||||
45
|
||||
90
|
||||
180
|
||||
104
|
||||
209
|
||||
163
|
||||
70
|
||||
140
|
||||
25
|
||||
51
|
||||
103
|
||||
206
|
||||
156
|
||||
57
|
||||
115
|
||||
231
|
||||
207
|
||||
158
|
||||
60
|
||||
120
|
||||
241
|
||||
227
|
||||
198
|
||||
141
|
||||
27
|
||||
54
|
||||
108
|
||||
216
|
||||
176
|
||||
97
|
||||
194
|
||||
132
|
||||
8
|
||||
17
|
||||
34
|
||||
69
|
||||
139
|
||||
23
|
||||
46
|
||||
93
|
||||
186
|
||||
117
|
||||
235
|
||||
215
|
||||
175
|
||||
94
|
||||
189
|
||||
123
|
||||
246
|
||||
237
|
||||
219
|
||||
183
|
||||
111
|
||||
223
|
||||
190
|
||||
124
|
||||
248
|
||||
240
|
||||
225
|
||||
195
|
||||
134
|
||||
13
|
||||
26
|
||||
52
|
||||
105
|
||||
211
|
||||
166
|
||||
77
|
||||
154
|
||||
53
|
||||
107
|
||||
214
|
||||
173
|
||||
91
|
||||
182
|
||||
109
|
||||
218
|
||||
181
|
||||
106
|
||||
212
|
||||
168
|
||||
80
|
||||
160
|
||||
65
|
||||
130
|
||||
4
|
||||
9
|
||||
19
|
||||
39
|
||||
78
|
||||
157
|
||||
59
|
||||
118
|
||||
236
|
||||
217
|
||||
178
|
||||
100
|
||||
201
|
||||
146
|
||||
36
|
||||
73
|
||||
147
|
||||
38
|
||||
76
|
||||
152
|
||||
48
|
||||
96
|
||||
192
|
||||
129
|
||||
3
|
||||
7
|
||||
14
|
||||
29
|
||||
58
|
||||
116
|
||||
233
|
||||
210
|
||||
164
|
||||
72
|
||||
145
|
||||
35
|
||||
71
|
||||
142
|
||||
28
|
||||
56
|
||||
113
|
||||
226
|
||||
196
|
||||
136
|
||||
16
|
||||
32
|
||||
64
|
||||
128
|
||||
1
|
||||
2
|
BIN
tests/expected-results/parity.png
Normal file
BIN
tests/expected-results/parity.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 164 KiB |
BIN
tests/expected-results/parity_rgba.png
Normal file
BIN
tests/expected-results/parity_rgba.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.4 KiB |
1000
tests/expected-results/shi_tomashi.txt
Normal file
1000
tests/expected-results/shi_tomashi.txt
Normal file
File diff suppressed because it is too large
Load diff
1
tests/expected-results/statistics
Normal file
1
tests/expected-results/statistics
Normal file
|
@ -0,0 +1 @@
|
|||
([58, 56, 54, 57, 60, 59, 62, 61, 63, 65, 64, 66, 67, 254, 68, 69, 255, 70, 253, 71, 72, 74, 252, 73, 251, 250, 75, 249, 76, 77], [(224, 4327), (221, 4138), (223, 4057), (222, 3987), (225, 3713), (220, 3554), (209, 3516), (207, 3476), (208, 3424), (219, 3360)])
|
|
@ -1,8 +1,6 @@
|
|||
#!/usr/bin/env python
|
||||
#-*- coding: utf-8 -*-
|
||||
|
||||
# Stéganô - Stéganô is a basic Python Steganography module.
|
||||
# Copyright (C) 2010-2017 Cédric Bonhomme - https://www.cedricbonhomme.org
|
||||
# Stegano - Stegano is a pure Python steganography module.
|
||||
# Copyright (C) 2010-2025 Cédric Bonhomme - https://www.cedricbonhomme.org
|
||||
#
|
||||
# For more information : https://github.com/cedricbonhomme/Stegano
|
||||
#
|
||||
|
@ -25,20 +23,19 @@ __date__ = "$Date: 2016/05/17 $"
|
|||
__revision__ = "$Date: 2017/01/18 $"
|
||||
__license__ = "GPLv3"
|
||||
|
||||
import io
|
||||
import os
|
||||
import unittest
|
||||
import io
|
||||
|
||||
from stegano import exifHeader
|
||||
|
||||
class TestEXIFHeader(unittest.TestCase):
|
||||
|
||||
class TestEXIFHeader(unittest.TestCase):
|
||||
def test_hide_empty_message(self):
|
||||
"""Test hiding the empty string.
|
||||
"""
|
||||
secret = exifHeader.hide("./tests/sample-files/20160505T130442.jpg",
|
||||
"./image.jpg", secret_message="")
|
||||
#secret.save(""./image.png"")
|
||||
"""Test hiding the empty string."""
|
||||
exifHeader.hide(
|
||||
"./tests/sample-files/20160505T130442.jpg", "./image.jpg", secret_message=""
|
||||
)
|
||||
|
||||
clear_message = exifHeader.reveal("./image.jpg")
|
||||
|
||||
|
@ -48,17 +45,20 @@ class TestEXIFHeader(unittest.TestCase):
|
|||
messages_to_hide = ["a", "foo", "Hello World!", ":Python:"]
|
||||
|
||||
for message in messages_to_hide:
|
||||
secret = exifHeader.hide("./tests/sample-files/20160505T130442.jpg",
|
||||
"./image.jpg", secret_message=message)
|
||||
exifHeader.hide(
|
||||
"./tests/sample-files/20160505T130442.jpg",
|
||||
"./image.jpg",
|
||||
secret_message=message,
|
||||
)
|
||||
|
||||
clear_message = exifHeader.reveal("./image.jpg")
|
||||
|
||||
self.assertEqual(message, message)
|
||||
self.assertEqual(message, clear_message.decode())
|
||||
|
||||
def test_with_image_without_exif_data(self):
|
||||
secret = exifHeader.hide("./tests/sample-files/Lenna.jpg",
|
||||
"./image.jpg", secret_message="")
|
||||
#secret.save(""./image.png"")
|
||||
exifHeader.hide(
|
||||
"./tests/sample-files/Lenna.jpg", "./image.jpg", secret_message=""
|
||||
)
|
||||
|
||||
clear_message = exifHeader.reveal("./image.jpg")
|
||||
|
||||
|
@ -68,40 +68,42 @@ class TestEXIFHeader(unittest.TestCase):
|
|||
text_file_to_hide = "./tests/sample-files/lorem_ipsum.txt"
|
||||
with open(text_file_to_hide, "rb") as f:
|
||||
message = f.read()
|
||||
secret = exifHeader.hide("./tests/sample-files/20160505T130442.jpg",
|
||||
img_enc="./image.jpg",
|
||||
secret_file=text_file_to_hide)
|
||||
exifHeader.hide(
|
||||
"./tests/sample-files/20160505T130442.jpg",
|
||||
img_enc="./image.jpg",
|
||||
secret_file=text_file_to_hide,
|
||||
)
|
||||
|
||||
clear_message = exifHeader.reveal("./image.jpg")
|
||||
self.assertEqual(message, clear_message)
|
||||
|
||||
def test_with_png_image(self):
|
||||
secret = exifHeader.hide("./tests/sample-files/Lenna.png",
|
||||
"./image.png", secret_message="Secret")
|
||||
#secret.save(""./image.png"")
|
||||
exifHeader.hide(
|
||||
"./tests/sample-files/Lenna.png", "./image.png", secret_message="Secret"
|
||||
)
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
clear_message = exifHeader.reveal("./image.png")
|
||||
exifHeader.reveal("./image.png")
|
||||
|
||||
def test_with_bytes(self):
|
||||
outputBytes = io.BytesIO()
|
||||
message = b"Secret"
|
||||
exifHeader.hide(open("./tests/sample-files/20160505T130442.jpg", 'rb'),
|
||||
outputBytes,
|
||||
secret_message=message)
|
||||
with open("./tests/sample-files/20160505T130442.jpg", "rb") as f:
|
||||
exifHeader.hide(f, outputBytes, secret_message=message)
|
||||
|
||||
clear_message = exifHeader.reveal(outputBytes)
|
||||
self.assertEqual(message, clear_message)
|
||||
clear_message = exifHeader.reveal(outputBytes)
|
||||
self.assertEqual(message, clear_message)
|
||||
|
||||
def tearDown(self):
|
||||
try:
|
||||
os.unlink("./image.jpg")
|
||||
except:
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
os.unlink("./image.png")
|
||||
except:
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
#!/usr/bin/env python
|
||||
#-*- coding: utf-8 -*-
|
||||
|
||||
# Stéganô - Stéganô is a basic Python Steganography module.
|
||||
# Copyright (C) 2010-2017 Cédric Bonhomme - https://www.cedricbonhomme.org
|
||||
# Stegano - Stegano is a pure Python steganography module.
|
||||
# Copyright (C) 2010-2025 Cédric Bonhomme - https://www.cedricbonhomme.org
|
||||
#
|
||||
# For more information : https://github.com/cedricbonhomme/Stegano
|
||||
#
|
||||
|
@ -25,83 +23,179 @@ __date__ = "$Date: 2017/03/01 $"
|
|||
__revision__ = "$Date: 2017/03/01 $"
|
||||
__license__ = "GPLv3"
|
||||
|
||||
import unittest
|
||||
import itertools
|
||||
import unittest
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
|
||||
from stegano.lsb import generators
|
||||
|
||||
from stegano.lsbset import generators
|
||||
|
||||
class TestGenerators(unittest.TestCase):
|
||||
|
||||
def test_identity(self):
|
||||
"""Test the identity generator.
|
||||
"""
|
||||
self.assertEqual(tuple(itertools.islice(generators.identity(), 15)),
|
||||
(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14))
|
||||
"""Test the identity generator."""
|
||||
self.assertEqual(
|
||||
tuple(itertools.islice(generators.identity(), 15)),
|
||||
(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14),
|
||||
)
|
||||
|
||||
def test_fibonacci(self):
|
||||
"""Test the Fibonacci generator.
|
||||
"""
|
||||
self.assertEqual(tuple(itertools.islice(generators.fibonacci(), 20)),
|
||||
(1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610,
|
||||
987, 1597, 2584, 4181, 6765, 10946))
|
||||
"""Test the Fibonacci generator."""
|
||||
self.assertEqual(
|
||||
tuple(itertools.islice(generators.fibonacci(), 20)),
|
||||
(
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
5,
|
||||
8,
|
||||
13,
|
||||
21,
|
||||
34,
|
||||
55,
|
||||
89,
|
||||
144,
|
||||
233,
|
||||
377,
|
||||
610,
|
||||
987,
|
||||
1597,
|
||||
2584,
|
||||
4181,
|
||||
6765,
|
||||
10946,
|
||||
),
|
||||
)
|
||||
|
||||
def test_eratosthenes(self):
|
||||
"""Test the Eratosthenes sieve.
|
||||
"""
|
||||
with open('./tests/expected-results/eratosthenes', 'r') as f:
|
||||
self.assertEqual(tuple(itertools.islice(generators.eratosthenes(), 168)),
|
||||
tuple(int(line) for line in f))
|
||||
"""Test the Eratosthenes sieve."""
|
||||
with open("./tests/expected-results/eratosthenes") as f:
|
||||
self.assertEqual(
|
||||
tuple(itertools.islice(generators.eratosthenes(), 168)),
|
||||
tuple(int(line) for line in f),
|
||||
)
|
||||
|
||||
def test_composite(self):
|
||||
"""Test the composite sieve.
|
||||
"""
|
||||
with open('./tests/expected-results/composite', 'r') as f:
|
||||
self.assertEqual(tuple(itertools.islice(generators.composite(), 114)),
|
||||
tuple(int(line) for line in f))
|
||||
"""Test the composite sieve."""
|
||||
with open("./tests/expected-results/composite") as f:
|
||||
self.assertEqual(
|
||||
tuple(itertools.islice(generators.composite(), 114)),
|
||||
tuple(int(line) for line in f),
|
||||
)
|
||||
|
||||
def test_fermat(self):
|
||||
"""Test the Fermat generator.
|
||||
"""
|
||||
with open('./tests/expected-results/fermat', 'r') as f:
|
||||
self.assertEqual(tuple(itertools.islice(generators.fermat(), 9)),
|
||||
tuple(int(line) for line in f))
|
||||
"""Test the Fermat generator."""
|
||||
with open("./tests/expected-results/fermat") as f:
|
||||
self.assertEqual(
|
||||
tuple(itertools.islice(generators.fermat(), 9)),
|
||||
tuple(int(line) for line in f),
|
||||
)
|
||||
|
||||
def test_triangular_numbers(self):
|
||||
"""Test the Triangular numbers generator.
|
||||
"""
|
||||
with open('./tests/expected-results/triangular_numbers', 'r') as f:
|
||||
self.assertEqual(tuple(itertools.islice(generators.triangular_numbers(), 54)),
|
||||
tuple(int(line) for line in f))
|
||||
"""Test the Triangular numbers generator."""
|
||||
with open("./tests/expected-results/triangular_numbers") as f:
|
||||
self.assertEqual(
|
||||
tuple(itertools.islice(generators.triangular_numbers(), 54)),
|
||||
tuple(int(line) for line in f),
|
||||
)
|
||||
|
||||
def test_mersenne(self):
|
||||
"""Test the Mersenne generator.
|
||||
"""
|
||||
with open('./tests/expected-results/mersenne', 'r') as f:
|
||||
self.assertEqual(tuple(itertools.islice(generators.mersenne(), 20)),
|
||||
tuple(int(line) for line in f))
|
||||
"""Test the Mersenne generator."""
|
||||
with open("./tests/expected-results/mersenne") as f:
|
||||
self.assertEqual(
|
||||
tuple(itertools.islice(generators.mersenne(), 20)),
|
||||
tuple(int(line) for line in f),
|
||||
)
|
||||
|
||||
def test_carmichael(self):
|
||||
"""Test the Carmichael generator.
|
||||
"""
|
||||
with open('./tests/expected-results/carmichael', 'r') as f:
|
||||
self.assertEqual(tuple(itertools.islice(generators.carmichael(), 33)),
|
||||
tuple(int(line) for line in f))
|
||||
"""Test the Carmichael generator."""
|
||||
with open("./tests/expected-results/carmichael") as f:
|
||||
self.assertEqual(
|
||||
tuple(itertools.islice(generators.carmichael(), 33)),
|
||||
tuple(int(line) for line in f),
|
||||
)
|
||||
|
||||
def test_ackermann_slow(self):
|
||||
"""Test the Ackermann set."""
|
||||
with open("./tests/expected-results/ackermann") as f:
|
||||
self.assertEqual(generators.ackermann_slow(3, 1), int(f.readline()))
|
||||
self.assertEqual(generators.ackermann_slow(3, 2), int(f.readline()))
|
||||
|
||||
def test_ackermann_naive(self):
|
||||
"""Test the Ackermann set.
|
||||
"""
|
||||
"""Test the Naive Ackermann generator"""
|
||||
gen = generators.ackermann_naive(3)
|
||||
next(gen)
|
||||
with open("./tests/expected-results/ackermann") as f:
|
||||
self.assertEqual(next(gen), int(f.readline()))
|
||||
self.assertEqual(next(gen), int(f.readline()))
|
||||
|
||||
self.assertEqual(generators.ackermann(3, 1), 13)
|
||||
self.assertEqual(generators.ackermann(3, 2), 29)
|
||||
def test_ackermann_fast(self):
|
||||
"""Test the Ackermann set."""
|
||||
with open("./tests/expected-results/ackermann") as f:
|
||||
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(4, 1), int(f.readline()))
|
||||
|
||||
def test_ackermann(self):
|
||||
"""Test the Ackermann set.
|
||||
"""
|
||||
with open('./tests/expected-results/ackermann', 'r') as f:
|
||||
self.assertEqual(generators.ackermann(3, 1), int(f.readline()))
|
||||
self.assertEqual(generators.ackermann(3, 2), int(f.readline()))
|
||||
self.assertEqual(generators.ackermann(4, 1), int(f.readline()))
|
||||
self.assertEqual(generators.ackermann(4, 2), int(f.readline()))
|
||||
"""Test the Ackermann generator"""
|
||||
gen = generators.ackermann(3)
|
||||
next(gen)
|
||||
with open("./tests/expected-results/ackermann") as f:
|
||||
self.assertEqual(next(gen), int(f.readline()))
|
||||
self.assertEqual(next(gen), int(f.readline()))
|
||||
|
||||
if __name__ == '__main__':
|
||||
def test_LFSR(self):
|
||||
"""Test the LFSR generator"""
|
||||
with open("./tests/expected-results/LFSR") as f:
|
||||
self.assertEqual(
|
||||
tuple(itertools.islice(generators.LFSR(2**8), 256)),
|
||||
tuple(int(line) for line in f),
|
||||
)
|
||||
|
||||
def test_shi_tomashi(self):
|
||||
"""Test the Shi Tomashi generator"""
|
||||
|
||||
# The expected results are only for tests/sample-files/Montenach.png file and
|
||||
# the below mentioned shi-tomashi configuration.
|
||||
# If the values below are changed,
|
||||
# please ensure the tests/expected-results/shi_tomashi.txt
|
||||
# is also appropriately modified
|
||||
# Using the shi_tomashi_reconfigure static method
|
||||
|
||||
image = cv2.imread("tests/sample-files/Montenach.png")
|
||||
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
|
||||
corners = cv2.goodFeaturesToTrack(gray, 1000, 0.001, 10)
|
||||
# Commented because min_distance argument of generators.shi_tomashi is now set
|
||||
# to 10.0:
|
||||
# corners = np.int0(corners)
|
||||
corners = corners.reshape(corners.shape[0], -1)
|
||||
test_file = np.loadtxt("tests/expected-results/shi_tomashi.txt")
|
||||
test_file_reshaped = test_file.reshape(
|
||||
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(res)
|
||||
|
||||
@staticmethod
|
||||
def shi_tomashi_reconfigure(
|
||||
file_name: str,
|
||||
max_corners: int = 1000,
|
||||
quality: float = 0.001,
|
||||
min_distance: int = 10,
|
||||
):
|
||||
"""
|
||||
Method to update/reconfigure Shi-Tomashi for various images and configuration
|
||||
"""
|
||||
image = cv2.imread(file_name)
|
||||
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
|
||||
corners = cv2.goodFeaturesToTrack(gray, max_corners, quality, min_distance)
|
||||
# Commented because min_distance argument of generators.shi_tomashi is now set
|
||||
# to 10.0:
|
||||
# corners = np.int0(corners)
|
||||
corners = corners.reshape(corners.shape[0], -1)
|
||||
np.savetxt("tests/expected-results/shi_tomashi.txt", corners)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
#!/usr/bin/env python
|
||||
#-*- coding: utf-8 -*-
|
||||
|
||||
# Stéganô - Stéganô is a basic Python Steganography module.
|
||||
# Copyright (C) 2010-2017 Cédric Bonhomme - https://www.cedricbonhomme.org
|
||||
# Stegano - Stegano is a pure Python steganography module.
|
||||
# Copyright (C) 2010-2024 Cédric Bonhomme - https://www.cedricbonhomme.org
|
||||
#
|
||||
# For more information : https://github.com/cedricbonhomme/Stegano
|
||||
#
|
||||
|
@ -20,134 +18,231 @@
|
|||
# along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
__author__ = "Cedric Bonhomme"
|
||||
__version__ = "$Revision: 0.3 $"
|
||||
__date__ = "$Date: 2016/04/12 $"
|
||||
__revision__ = "$Date: 2017/05/04 $"
|
||||
__version__ = "$Revision: 0.6 $"
|
||||
__date__ = "$Date: 2016/04/13 $"
|
||||
__revision__ = "$Date: 2022/01/04 $"
|
||||
__license__ = "GPLv3"
|
||||
|
||||
import io
|
||||
import os
|
||||
import base64
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
|
||||
from stegano import lsb
|
||||
from stegano.lsb import generators
|
||||
|
||||
|
||||
class TestLSB(unittest.TestCase):
|
||||
|
||||
def test_hide_empty_message(self):
|
||||
"""
|
||||
Test hiding the empty string.
|
||||
"""
|
||||
with self.assertRaises(AssertionError):
|
||||
secret = lsb.hide("./tests/sample-files/Lenna.png", "")
|
||||
lsb.hide("./tests/sample-files/Lenna.png", "", generators.eratosthenes())
|
||||
|
||||
def test_hide_and_reveal(self):
|
||||
messages_to_hide = ['a', 'foo', 'Hello World!', ':Python:']
|
||||
def test_hide_and_reveal_without_generator(self):
|
||||
messages_to_hide = ["a", "foo", "Hello World!", ":Python:"]
|
||||
for message in messages_to_hide:
|
||||
secret = lsb.hide("./tests/sample-files/Lenna.png", message)
|
||||
secret.save("./image.png")
|
||||
|
||||
clear_message = lsb.reveal("./image.png")
|
||||
|
||||
self.assertEqual(message, clear_message)
|
||||
|
||||
def test_hide_and_reveal_with_eratosthenes(self):
|
||||
messages_to_hide = ["a", "foo", "Hello World!", ":Python:"]
|
||||
for message in messages_to_hide:
|
||||
secret = lsb.hide(
|
||||
"./tests/sample-files/Lenna.png", message, generators.eratosthenes()
|
||||
)
|
||||
secret.save("./image.png")
|
||||
|
||||
clear_message = lsb.reveal("./image.png", generators.eratosthenes())
|
||||
|
||||
self.assertEqual(message, clear_message)
|
||||
|
||||
def test_hide_and_reveal_with_ackermann(self):
|
||||
messages_to_hide = ["foo"]
|
||||
for message in messages_to_hide:
|
||||
secret = lsb.hide(
|
||||
"./tests/sample-files/Lenna.png", message, generators.ackermann(m=3)
|
||||
)
|
||||
secret.save("./image.png")
|
||||
|
||||
clear_message = lsb.reveal("./image.png", generators.ackermann(m=3))
|
||||
|
||||
self.assertEqual(message, clear_message)
|
||||
|
||||
def test_hide_and_reveal_with_ackermann_naive(self):
|
||||
messages_to_hide = ["foo"]
|
||||
for message in messages_to_hide:
|
||||
secret = lsb.hide(
|
||||
"./tests/sample-files/Lenna.png",
|
||||
message,
|
||||
generators.ackermann_naive(m=2),
|
||||
)
|
||||
secret.save("./image.png")
|
||||
|
||||
clear_message = lsb.reveal("./image.png", generators.ackermann_naive(m=2))
|
||||
|
||||
self.assertEqual(message, clear_message)
|
||||
|
||||
def test_hide_and_reveal_with_mersenne(self):
|
||||
messages_to_hide = ["f"]
|
||||
for message in messages_to_hide:
|
||||
secret = lsb.hide(
|
||||
"./tests/sample-files/Montenach.png",
|
||||
message,
|
||||
generators.mersenne(),
|
||||
)
|
||||
secret.save("./image.png")
|
||||
|
||||
clear_message = lsb.reveal("./image.png", generators.mersenne())
|
||||
|
||||
self.assertEqual(message, clear_message)
|
||||
|
||||
def test_hide_and_reveal_with_shi_tomashi(self):
|
||||
messages_to_hide = ["foo bar"]
|
||||
for message in messages_to_hide:
|
||||
secret = lsb.hide(
|
||||
"./tests/sample-files/Lenna.png",
|
||||
message,
|
||||
generators.shi_tomashi("./tests/sample-files/Lenna.png"),
|
||||
)
|
||||
secret.save("./image.png")
|
||||
|
||||
clear_message = lsb.reveal(
|
||||
"./image.png", generators.shi_tomashi("./tests/sample-files/Lenna.png")
|
||||
)
|
||||
|
||||
self.assertEqual(message, clear_message)
|
||||
|
||||
def test_hide_and_reveal_with_shift(self):
|
||||
messages_to_hide = ["a", "foo", "Hello World!", ":Python:"]
|
||||
for message in messages_to_hide:
|
||||
secret = lsb.hide(
|
||||
"./tests/sample-files/Lenna.png", message, generators.eratosthenes(), 4
|
||||
)
|
||||
secret.save("./image.png")
|
||||
|
||||
clear_message = lsb.reveal("./image.png", generators.eratosthenes(), 4)
|
||||
|
||||
self.assertEqual(message, clear_message)
|
||||
|
||||
def test_hide_and_reveal_UTF32LE(self):
|
||||
messages_to_hide = 'I love 🍕 and 🍫!'
|
||||
secret = lsb.hide("./tests/sample-files/Lenna.png",
|
||||
messages_to_hide, 'UTF-32LE')
|
||||
messages_to_hide = "I love 🍕 and 🍫!"
|
||||
secret = lsb.hide(
|
||||
"./tests/sample-files/Lenna.png",
|
||||
messages_to_hide,
|
||||
generators.eratosthenes(),
|
||||
encoding="UTF-32LE",
|
||||
)
|
||||
secret.save("./image.png")
|
||||
|
||||
clear_message = lsb.reveal("./image.png", 'UTF-32LE')
|
||||
clear_message = lsb.reveal(
|
||||
"./image.png", generators.eratosthenes(), encoding="UTF-32LE"
|
||||
)
|
||||
self.assertEqual(messages_to_hide, clear_message)
|
||||
|
||||
def test_with_transparent_png(self):
|
||||
messages_to_hide = ['🍕', 'a', 'foo', 'Hello World!', ':Python:']
|
||||
messages_to_hide = ["a", "foo", "Hello World!", ":Python:"]
|
||||
for message in messages_to_hide:
|
||||
secret = lsb.hide("./tests/sample-files/transparent.png",
|
||||
message, 'UTF-32LE')
|
||||
secret = lsb.hide(
|
||||
"./tests/sample-files/transparent.png",
|
||||
message,
|
||||
generators.eratosthenes(),
|
||||
)
|
||||
secret.save("./image.png")
|
||||
|
||||
clear_message = lsb.reveal("./image.png", 'UTF-32LE')
|
||||
clear_message = lsb.reveal("./image.png", generators.eratosthenes())
|
||||
|
||||
self.assertEqual(message, clear_message)
|
||||
|
||||
@patch('builtins.input', return_value='y')
|
||||
@patch("builtins.input", return_value="y")
|
||||
def test_manual_convert_rgb(self, input):
|
||||
message_to_hide = 'I love 🍕 and 🍫!'
|
||||
secret = lsb.hide("./tests/sample-files/Lenna-grayscale.png",
|
||||
message_to_hide, 'UTF-32LE')
|
||||
message_to_hide = "Hello World!"
|
||||
lsb.hide(
|
||||
"./tests/sample-files/Lenna-grayscale.png",
|
||||
message_to_hide,
|
||||
generators.eratosthenes(),
|
||||
)
|
||||
|
||||
@patch('builtins.input', return_value='n')
|
||||
@patch("builtins.input", return_value="n")
|
||||
def test_refuse_convert_rgb(self, input):
|
||||
message_to_hide = 'I love 🍕 and 🍫!'
|
||||
with self.assertRaises(Exception):
|
||||
secret = lsb.hide("./tests/sample-files/Lenna-grayscale.png",
|
||||
message_to_hide, 'UTF-32LE')
|
||||
message_to_hide = "Hello World!"
|
||||
# lsb.hide(
|
||||
# "./tests/sample-files/Lenna-grayscale.png",
|
||||
# message_to_hide,
|
||||
# generators.eratosthenes(),
|
||||
# )
|
||||
with self.assertRaisesRegex(Exception, "Not a RGB image."):
|
||||
lsb.hide(
|
||||
"./tests/sample-files/Lenna-grayscale.png",
|
||||
message_to_hide,
|
||||
generators.eratosthenes(),
|
||||
)
|
||||
|
||||
def test_with_location_of_image_as_argument(self):
|
||||
messages_to_hide = ["Hello World!"]
|
||||
|
||||
for message in messages_to_hide:
|
||||
outputBytes = io.BytesIO()
|
||||
bytes_image = lsb.hide(
|
||||
"./tests/sample-files/20160505T130442.jpg",
|
||||
message,
|
||||
generators.identity(),
|
||||
)
|
||||
bytes_image.save(outputBytes, "PNG")
|
||||
outputBytes.seek(0)
|
||||
|
||||
clear_message = lsb.reveal(outputBytes, generators.identity())
|
||||
|
||||
self.assertEqual(message, clear_message)
|
||||
|
||||
def test_auto_convert_rgb(self):
|
||||
message_to_hide = 'I love 🍕 and 🍫!'
|
||||
secret = lsb.hide("./tests/sample-files/Lenna-grayscale.png",
|
||||
message_to_hide, 'UTF-32LE', True)
|
||||
|
||||
def test_with_text_file(self):
|
||||
text_file_to_hide = './tests/sample-files/lorem_ipsum.txt'
|
||||
with open(text_file_to_hide) as f:
|
||||
message = f.read()
|
||||
secret = lsb.hide("./tests/sample-files/Lenna.png", message)
|
||||
secret.save("./image.png")
|
||||
|
||||
clear_message = lsb.reveal("./image.png")
|
||||
self.assertEqual(message, clear_message)
|
||||
|
||||
def test_with_binary_file(self):
|
||||
binary_file_to_hide = "./tests/sample-files/free-software-song.ogg"
|
||||
with open(binary_file_to_hide, "rb") as bin_file:
|
||||
encoded_string = base64.b64encode(bin_file.read())
|
||||
message = encoded_string.decode()
|
||||
secret = lsb.hide("./tests/sample-files/Montenach.png", message)
|
||||
secret.save("./image.png")
|
||||
|
||||
clear_message = lsb.reveal("./image.png")
|
||||
clear_message += '==='
|
||||
clear_message = base64.b64decode(clear_message)
|
||||
with open('file1', 'wb') as f:
|
||||
f.write(clear_message)
|
||||
with open('file1', 'rb') as bin_file:
|
||||
encoded_string = base64.b64encode(bin_file.read())
|
||||
message1 = encoded_string.decode()
|
||||
self.assertEqual(message, message1)
|
||||
try:
|
||||
os.unlink('./file1')
|
||||
except:
|
||||
pass
|
||||
message_to_hide = "Hello World!"
|
||||
lsb.hide(
|
||||
"./tests/sample-files/Lenna-grayscale.png",
|
||||
message_to_hide,
|
||||
generators.eratosthenes(),
|
||||
auto_convert_rgb=True,
|
||||
)
|
||||
|
||||
def test_with_too_long_message(self):
|
||||
with open("./tests/sample-files/lorem_ipsum.txt") as f:
|
||||
message = f.read()
|
||||
message += message*2
|
||||
with self.assertRaises(Exception):
|
||||
lsb.hide("./tests/sample-files/Lenna.png", message)
|
||||
message += message * 2
|
||||
with self.assertRaisesRegex(
|
||||
Exception, "The message you want to hide is too long:"
|
||||
):
|
||||
lsb.hide("./tests/sample-files/Lenna.png", message, generators.identity())
|
||||
|
||||
def test_with_bytes(self):
|
||||
messages_to_hide = ["a", "foo", "Hello World!", ":Python:"]
|
||||
def test_hide_and_reveal_with_bad_generator(self):
|
||||
message_to_hide = "Hello World!"
|
||||
secret = lsb.hide(
|
||||
"./tests/sample-files/Lenna.png", message_to_hide, generators.eratosthenes()
|
||||
)
|
||||
secret.save("./image.png")
|
||||
|
||||
for message in messages_to_hide:
|
||||
message = "Hello World"
|
||||
outputBytes = io.BytesIO()
|
||||
bytes_image = lsb.hide(open("./tests/sample-files/20160505T130442.jpg", 'rb'), message)
|
||||
bytes_image.save(outputBytes, "PNG")
|
||||
outputBytes.seek(0)
|
||||
with self.assertRaises(IndexError):
|
||||
lsb.reveal("./image.png", generators.identity())
|
||||
|
||||
clear_message = lsb.reveal(outputBytes)
|
||||
|
||||
self.assertEqual(message, clear_message)
|
||||
def test_with_unknown_generator(self):
|
||||
message_to_hide = "Hello World!"
|
||||
with self.assertRaises(AttributeError):
|
||||
lsb.hide(
|
||||
"./tests/sample-files/Lenna.png",
|
||||
message_to_hide,
|
||||
generators.unknown_generator(), # type: ignore
|
||||
)
|
||||
|
||||
def tearDown(self):
|
||||
try:
|
||||
os.unlink("./image.png")
|
||||
except:
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
|
|
@ -1,131 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
#-*- coding: utf-8 -*-
|
||||
|
||||
# Stéganô - Stéganô is a basic Python Steganography module.
|
||||
# Copyright (C) 2010-2017 Cédric Bonhomme - https://www.cedricbonhomme.org
|
||||
#
|
||||
# For more information : https://github.com/cedricbonhomme/Stegano
|
||||
#
|
||||
# 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
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
__author__ = "Cedric Bonhomme"
|
||||
__version__ = "$Revision: 0.4 $"
|
||||
__date__ = "$Date: 2016/04/13 $"
|
||||
__revision__ = "$Date: 2017/05/04 $"
|
||||
__license__ = "GPLv3"
|
||||
|
||||
import os
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
|
||||
from stegano import lsbset
|
||||
from stegano.lsbset import generators
|
||||
|
||||
class TestLSBSet(unittest.TestCase):
|
||||
|
||||
def test_hide_empty_message(self):
|
||||
"""
|
||||
Test hiding the empty string.
|
||||
"""
|
||||
with self.assertRaises(AssertionError):
|
||||
secret = lsbset.hide("./tests/sample-files/Lenna.png", "",
|
||||
generators.eratosthenes())
|
||||
|
||||
def test_hide_and_reveal(self):
|
||||
messages_to_hide = ["a", "foo", "Hello World!", ":Python:"]
|
||||
for message in messages_to_hide:
|
||||
secret = lsbset.hide("./tests/sample-files/Lenna.png", message,
|
||||
generators.eratosthenes())
|
||||
secret.save("./image.png")
|
||||
|
||||
clear_message = lsbset.reveal("./image.png",
|
||||
generators.eratosthenes())
|
||||
|
||||
self.assertEqual(message, clear_message)
|
||||
|
||||
def test_hide_and_reveal_UTF32LE(self):
|
||||
messages_to_hide = 'I love 🍕 and 🍫!'
|
||||
secret = lsbset.hide("./tests/sample-files/Lenna.png",
|
||||
messages_to_hide,
|
||||
generators.eratosthenes(),
|
||||
'UTF-32LE')
|
||||
secret.save("./image.png")
|
||||
|
||||
clear_message = lsbset.reveal("./image.png", generators.eratosthenes(),
|
||||
'UTF-32LE')
|
||||
self.assertEqual(messages_to_hide, clear_message)
|
||||
|
||||
def test_with_transparent_png(self):
|
||||
messages_to_hide = ["a", "foo", "Hello World!", ":Python:"]
|
||||
for message in messages_to_hide:
|
||||
secret = lsbset.hide("./tests/sample-files/transparent.png",
|
||||
message, generators.eratosthenes())
|
||||
secret.save("./image.png")
|
||||
|
||||
clear_message = lsbset.reveal("./image.png",
|
||||
generators.eratosthenes())
|
||||
|
||||
self.assertEqual(message, clear_message)
|
||||
|
||||
@patch('builtins.input', return_value='y')
|
||||
def test_manual_convert_rgb(self, input):
|
||||
message_to_hide = "Hello World!"
|
||||
secret = lsbset.hide("./tests/sample-files/Lenna-grayscale.png",
|
||||
message_to_hide, generators.eratosthenes())
|
||||
|
||||
@patch('builtins.input', return_value='n')
|
||||
def test_refuse_convert_rgb(self, input):
|
||||
message_to_hide = "Hello World!"
|
||||
with self.assertRaises(Exception):
|
||||
secret = lsbset.hide("./tests/sample-files/Lenna-grayscale.png",
|
||||
message_to_hide, generators.eratosthenes())
|
||||
|
||||
def test_auto_convert_rgb(self):
|
||||
message_to_hide = "Hello World!"
|
||||
secret = lsbset.hide("./tests/sample-files/Lenna-grayscale.png",
|
||||
message_to_hide, generators.eratosthenes(),
|
||||
auto_convert_rgb=True)
|
||||
|
||||
def test_with_too_long_message(self):
|
||||
with open("./tests/sample-files/lorem_ipsum.txt") as f:
|
||||
message = f.read()
|
||||
message += message*2
|
||||
with self.assertRaises(Exception):
|
||||
lsbset.hide("./tests/sample-files/Lenna.png", message,
|
||||
generators.identity())
|
||||
|
||||
def test_hide_and_reveal_with_bad_generator(self):
|
||||
message_to_hide = "Hello World!"
|
||||
secret = lsbset.hide("./tests/sample-files/Lenna.png", message_to_hide,
|
||||
generators.eratosthenes())
|
||||
secret.save("./image.png")
|
||||
|
||||
with self.assertRaises(IndexError):
|
||||
clear_message = lsbset.reveal("./image.png", generators.identity())
|
||||
|
||||
def test_with_unknown_generator(self):
|
||||
message_to_hide = "Hello World!"
|
||||
with self.assertRaises(AttributeError):
|
||||
secret = lsbset.hide("./tests/sample-files/Lenna.png",
|
||||
message_to_hide, generators.eratosthene())
|
||||
|
||||
def tearDown(self):
|
||||
try:
|
||||
os.unlink("./image.png")
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
|
@ -1,8 +1,6 @@
|
|||
#!/usr/bin/env python
|
||||
#-*- coding: utf-8 -*-
|
||||
|
||||
# Stéganô - Stéganô is a basic Python Steganography module.
|
||||
# Copyright (C) 2010-2017 Cédric Bonhomme - https://www.cedricbonhomme.org
|
||||
# Stegano - Stegano is a pure Python steganography module.
|
||||
# Copyright (C) 2010-2025 Cédric Bonhomme - https://www.cedricbonhomme.org
|
||||
#
|
||||
# For more information : https://github.com/cedricbonhomme/Stegano
|
||||
#
|
||||
|
@ -29,14 +27,14 @@ import unittest
|
|||
|
||||
from stegano import red
|
||||
|
||||
class TestRed(unittest.TestCase):
|
||||
|
||||
class TestRed(unittest.TestCase):
|
||||
def test_hide_empty_message(self):
|
||||
"""
|
||||
Test hiding the empty string.
|
||||
"""
|
||||
with self.assertRaises(AssertionError):
|
||||
secret = red.hide("./tests/sample-files/Lenna.png", "")
|
||||
red.hide("./tests/sample-files/Lenna.png", "")
|
||||
|
||||
def test_hide_and_reveal(self):
|
||||
messages_to_hide = ["a", "foo", "Hello World!", ":Python:"]
|
||||
|
@ -47,7 +45,7 @@ class TestRed(unittest.TestCase):
|
|||
|
||||
clear_message = red.reveal("./image.png")
|
||||
|
||||
self.assertEqual(message, message)
|
||||
self.assertEqual(message, clear_message)
|
||||
|
||||
def test_with_too_long_message(self):
|
||||
with open("./tests/sample-files/lorem_ipsum.txt") as f:
|
||||
|
@ -58,9 +56,9 @@ class TestRed(unittest.TestCase):
|
|||
def tearDown(self):
|
||||
try:
|
||||
os.unlink("./image.png")
|
||||
except:
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
|
66
tests/test_steganalysis.py
Normal file
66
tests/test_steganalysis.py
Normal file
|
@ -0,0 +1,66 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
# Stegano - Stegano is a pure Python steganography module.
|
||||
# Copyright (C) 2010-2017 Cédric Bonhomme - https://www.cedricbonhomme.org
|
||||
#
|
||||
# For more information : https://github.com/cedricbonhomme/Stegano
|
||||
#
|
||||
# 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
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
__author__ = "Cedric Bonhomme"
|
||||
__version__ = "$Revision: 0.9.4 $"
|
||||
__date__ = "$Date: 2019/06/06 $"
|
||||
__revision__ = "$Date: 2019/06/06 $"
|
||||
__license__ = "GPLv3"
|
||||
|
||||
import unittest
|
||||
|
||||
from PIL import Image, ImageChops
|
||||
|
||||
from stegano import lsb
|
||||
from stegano.steganalysis import parity, statistics
|
||||
|
||||
|
||||
class TestSteganalysis(unittest.TestCase):
|
||||
def test_parity(self):
|
||||
"""Test stegano.steganalysis.parity"""
|
||||
text_file_to_hide = "./tests/sample-files/lorem_ipsum.txt"
|
||||
with open(text_file_to_hide) as f:
|
||||
message = f.read()
|
||||
secret = lsb.hide("./tests/sample-files/Lenna.png", message)
|
||||
analysis = parity.steganalyse(secret)
|
||||
target = Image.open("./tests/expected-results/parity.png")
|
||||
diff = ImageChops.difference(target, analysis).getbbox()
|
||||
self.assertTrue(diff is None)
|
||||
|
||||
def test_parity_rgba(self):
|
||||
"""Test that stegano.steganalysis.parity works with RGBA images"""
|
||||
img = Image.open("./tests/sample-files/transparent.png")
|
||||
analysis = parity.steganalyse(img)
|
||||
target = Image.open("./tests/expected-results/parity_rgba.png")
|
||||
diff = ImageChops.difference(target, analysis).getbbox()
|
||||
self.assertTrue(diff is None)
|
||||
|
||||
def test_statistics(self):
|
||||
"""Test stegano.steganalysis.statistics"""
|
||||
image = Image.open("./tests/sample-files/Lenna.png")
|
||||
stats = str(statistics.steganalyse(image)) + "\n"
|
||||
file = open("./tests/expected-results/statistics")
|
||||
target = file.read()
|
||||
file.close()
|
||||
self.assertEqual(stats, target)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
|
@ -1,8 +1,6 @@
|
|||
#!/usr/bin/env python
|
||||
#-*- coding: utf-8 -*-
|
||||
|
||||
# Stéganô - Stéganô is a basic Python Steganography module.
|
||||
# Copyright (C) 2010-2017 Cédric Bonhomme - https://www.cedricbonhomme.org
|
||||
# Stegano - Stegano is a pure Python steganography module.
|
||||
# Copyright (C) 2010-2025 Cédric Bonhomme - https://www.cedricbonhomme.org
|
||||
#
|
||||
# For more information : https://github.com/cedricbonhomme/Stegano
|
||||
#
|
||||
|
@ -25,54 +23,65 @@ __date__ = "$Date: 2017/02/22 $"
|
|||
__revision__ = "$Date: 2017/02/22 $"
|
||||
__license__ = "GPLv3"
|
||||
|
||||
import os
|
||||
import unittest
|
||||
import io
|
||||
|
||||
from stegano import tools
|
||||
|
||||
class TestTools(unittest.TestCase):
|
||||
|
||||
class TestTools(unittest.TestCase):
|
||||
def test_a2bits(self):
|
||||
bits = tools.a2bits("Hello World!")
|
||||
self.assertEqual(bits, '010010000110010101101100011011000110111100100000010101110110111101110010011011000110010000100001')
|
||||
self.assertEqual(
|
||||
bits,
|
||||
"010010000110010101101100011011000110111100100000010101110110111101110010011011000110010000100001",
|
||||
)
|
||||
|
||||
def test_a2bits_list_UTF8(self):
|
||||
list_of_bits = tools.a2bits_list("Hello World!")
|
||||
self.assertEqual(list_of_bits, ['01001000',
|
||||
'01100101',
|
||||
'01101100',
|
||||
'01101100',
|
||||
'01101111',
|
||||
'00100000',
|
||||
'01010111',
|
||||
'01101111',
|
||||
'01110010',
|
||||
'01101100',
|
||||
'01100100',
|
||||
'00100001'])
|
||||
self.assertEqual(
|
||||
list_of_bits,
|
||||
[
|
||||
"01001000",
|
||||
"01100101",
|
||||
"01101100",
|
||||
"01101100",
|
||||
"01101111",
|
||||
"00100000",
|
||||
"01010111",
|
||||
"01101111",
|
||||
"01110010",
|
||||
"01101100",
|
||||
"01100100",
|
||||
"00100001",
|
||||
],
|
||||
)
|
||||
|
||||
def test_a2bits_list_UTF32LE(self):
|
||||
list_of_bits = tools.a2bits_list("Hello World!", 'UTF-32LE')
|
||||
self.assertEqual(list_of_bits, ['00000000000000000000000001001000',
|
||||
'00000000000000000000000001100101',
|
||||
'00000000000000000000000001101100',
|
||||
'00000000000000000000000001101100',
|
||||
'00000000000000000000000001101111',
|
||||
'00000000000000000000000000100000',
|
||||
'00000000000000000000000001010111',
|
||||
'00000000000000000000000001101111',
|
||||
'00000000000000000000000001110010',
|
||||
'00000000000000000000000001101100',
|
||||
'00000000000000000000000001100100',
|
||||
'00000000000000000000000000100001'])
|
||||
list_of_bits = tools.a2bits_list("Hello World!", "UTF-32LE")
|
||||
self.assertEqual(
|
||||
list_of_bits,
|
||||
[
|
||||
"00000000000000000000000001001000",
|
||||
"00000000000000000000000001100101",
|
||||
"00000000000000000000000001101100",
|
||||
"00000000000000000000000001101100",
|
||||
"00000000000000000000000001101111",
|
||||
"00000000000000000000000000100000",
|
||||
"00000000000000000000000001010111",
|
||||
"00000000000000000000000001101111",
|
||||
"00000000000000000000000001110010",
|
||||
"00000000000000000000000001101100",
|
||||
"00000000000000000000000001100100",
|
||||
"00000000000000000000000000100001",
|
||||
],
|
||||
)
|
||||
|
||||
def test_n_at_a_time(self):
|
||||
result = tools.n_at_a_time([1, 2, 3, 4, 5], 2, 'X')
|
||||
self.assertEqual(list(result), [(1, 2), (3, 4), (5, 'X')])
|
||||
result = tools.n_at_a_time([1, 2, 3, 4, 5], 2, "X")
|
||||
self.assertEqual(list(result), [(1, 2), (3, 4), (5, "X")])
|
||||
|
||||
def test_binary2base64(self):
|
||||
with open('./tests/expected-results/binary2base64', 'r') as f:
|
||||
expected_value = f.read()
|
||||
value = tools.binary2base64('tests/sample-files/free-software-song.ogg')
|
||||
with open("./tests/expected-results/binary2base64") as f:
|
||||
expected_value = f.read()
|
||||
value = tools.binary2base64("tests/sample-files/free-software-song.ogg")
|
||||
self.assertEqual(expected_value, value)
|
||||
|
|
|
@ -1,18 +0,0 @@
|
|||
import subprocess
|
||||
import sys
|
||||
|
||||
modules = ["stegano/tools.py",
|
||||
"stegano/lsb/lsb.py",
|
||||
"stegano/lsbset/lsbset.py",
|
||||
"stegano/lsbset/generators.py",
|
||||
"stegano/red/red.py",
|
||||
"stegano/exifHeader/exifHeader.py",
|
||||
"stegano/steganalysis/parity.py",
|
||||
"stegano/steganalysis/statistics.py"]
|
||||
|
||||
exit_codes = []
|
||||
for module in modules:
|
||||
rc = subprocess.call(["mypy", "--ignore-missing-imports",
|
||||
"--follow-imports", "skip", module])
|
||||
exit_codes.append(rc)
|
||||
sys.exit(max(exit_codes))
|
Loading…
Add table
Reference in a new issue