Compare commits

..

535 commits

Author SHA1 Message Date
Cédric Bonhomme
7fa6517830
chg: [RELEASE] Updated CHANGELOG.
Some checks failed
Python application / build (3.10) (push) Has been cancelled
Python application / build (3.11) (push) Has been cancelled
Python application / build (3.12) (push) Has been cancelled
2025-06-22 12:08:43 +02:00
Cédric Bonhomme
ea91d5d63d
chg: [flake8] Ignore E203 errors. 2025-06-22 12:06:09 +02:00
Cédric Bonhomme
18fd8a8a49
chg: [typing] Make Mypy Happy Again. 2025-06-22 12:03:30 +02:00
Cédric Bonhomme
21004219fa
chg: Updated pip-audit. 2025-06-22 10:44:06 +02:00
Cédric Bonhomme
ea7648858a
chg: [dependencies] Updated dependencies 2025-06-22 10:42:05 +02:00
Cédric Bonhomme
e653151040
chg: Updated CONTRIBUTORS.md file. 2025-06-22 10:39:26 +02:00
Cédric Bonhomme
e8ed79ae9e
chg: [style] Reformat with black. 2025-06-22 10:36:57 +02:00
Cédric Bonhomme
6fd80429a4
fix: [console] Fixed parameter name when calling wav.reveal(). 2025-06-22 10:36:16 +02:00
Cédric Bonhomme
5b75db7dab
chg: [dependencies] Updated dependencies and CHANGELOG. 2025-06-22 10:33:24 +02:00
Cédric Bonhomme
1bcb60d7c3
Merge pull request #53 from cedricbonhomme/dependabot/pip/urllib3-2.5.0
Some checks are pending
Python application / build (3.10) (push) Waiting to run
Python application / build (3.11) (push) Waiting to run
Python application / build (3.12) (push) Waiting to run
build(deps-dev): bump urllib3 from 2.4.0 to 2.5.0
2025-06-21 16:10:51 +02:00
Cédric Bonhomme
e74dcbe220
Merge pull request #54 from AlexanderTreml/master
Some checks are pending
Python application / build (3.10) (push) Waiting to run
Python application / build (3.11) (push) Waiting to run
Python application / build (3.12) (push) Waiting to run
.wav feature
2025-06-21 08:59:21 +02:00
Alexander Treml
de319d11c3 console 2025-06-20 10:55:05 +02:00
Alexander Treml
cb2f9daeca wav functionality 2025-06-20 10:34:17 +02:00
Alexander Treml
53a82724ae read and write .wav 2025-06-20 00:52:28 +02:00
Alexander Treml
35d03bc0c6 wav scaffolding and tests 2025-06-19 19:16:39 +02:00
dependabot[bot]
23ca821e80
build(deps-dev): bump urllib3 from 2.4.0 to 2.5.0
Bumps [urllib3](https://github.com/urllib3/urllib3) from 2.4.0 to 2.5.0.
- [Release notes](https://github.com/urllib3/urllib3/releases)
- [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst)
- [Commits](https://github.com/urllib3/urllib3/compare/2.4.0...2.5.0)

---
updated-dependencies:
- dependency-name: urllib3
  dependency-version: 2.5.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-19 05:30:25 +00:00
Alexander Treml
19c5dcad5c fix handling of platform specific line endings in exifHeader.hide 2025-06-16 16:08:33 +02:00
Cédric Bonhomme
0fa3bdf420
Merge pull request #52 from cedricbonhomme/dependabot/pip/requests-2.32.4
Some checks failed
Python application / build (3.10) (push) Has been cancelled
Python application / build (3.11) (push) Has been cancelled
Python application / build (3.12) (push) Has been cancelled
build(deps-dev): bump requests from 2.32.3 to 2.32.4
2025-06-10 11:40:15 +02:00
dependabot[bot]
f0164ad71c
build(deps-dev): bump requests from 2.32.3 to 2.32.4
Bumps [requests](https://github.com/psf/requests) from 2.32.3 to 2.32.4.
- [Release notes](https://github.com/psf/requests/releases)
- [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md)
- [Commits](https://github.com/psf/requests/compare/v2.32.3...v2.32.4)

---
updated-dependencies:
- dependency-name: requests
  dependency-version: 2.32.4
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-10 09:12:33 +00:00
Cédric Bonhomme
bd3238acfa
chg: [dependencies] Updated dependencies.
Some checks failed
Python application / build (3.10) (push) Has been cancelled
Python application / build (3.11) (push) Has been cancelled
Python application / build (3.12) (push) Has been cancelled
2025-05-16 11:53:20 +02:00
Cédric Bonhomme
eb28c60d27
chg: [RELEASE] Bumped version number, improved packaging for the command line.
Some checks failed
Python application / build (3.10) (push) Has been cancelled
Python application / build (3.11) (push) Has been cancelled
Python application / build (3.12) (push) Has been cancelled
2025-05-03 23:15:52 +02:00
Cédric Bonhomme
7ba387ecdc
chg: [RELEASE] Updated documentation, updated dependencies, and bumped version number.
Some checks failed
Python application / build (3.10) (push) Has been cancelled
Python application / build (3.11) (push) Has been cancelled
Python application / build (3.12) (push) Has been cancelled
2025-04-26 23:28:17 +02:00
Cédric Bonhomme
ac66655ad8
chg: [dependencies] Updated dependencies.
Some checks failed
Python application / build (3.10) (push) Has been cancelled
Python application / build (3.11) (push) Has been cancelled
Python application / build (3.12) (push) Has been cancelled
2025-03-14 06:59:13 +01:00
Cédric Bonhomme
2e8b0fdec9
chg: [RELEASE] Bumped version number and aligned pyproject.toml file with standard specification.
Some checks failed
Python application / build (3.10) (push) Has been cancelled
Python application / build (3.11) (push) Has been cancelled
Python application / build (3.12) (push) Has been cancelled
2025-02-13 18:14:40 +01:00
Cédric Bonhomme
7849955cdb
chg: Updated pyproject.toml and dependencies.
Some checks failed
Python application / build (3.10) (push) Has been cancelled
Python application / build (3.11) (push) Has been cancelled
Python application / build (3.12) (push) Has been cancelled
2025-02-05 09:33:44 +01:00
Cédric Bonhomme
d6015157bf
chg: [packaging] Minor changes in the pryproject.toml file.
Some checks failed
Python application / build (3.10) (push) Has been cancelled
Python application / build (3.11) (push) Has been cancelled
Python application / build (3.12) (push) Has been cancelled
2025-01-30 14:55:46 +01:00
Cédric Bonhomme
43f02702f9
chg: Updated pyproject.toml for Poetry 2.0.
Some checks are pending
Python application / build (3.10) (push) Waiting to run
Python application / build (3.11) (push) Waiting to run
Python application / build (3.12) (push) Waiting to run
2025-01-30 08:02:09 +01:00
Cédric Bonhomme
9c69075d34
chg: [dependencies] Updated dependencies.
Some checks failed
Python application / build (3.10) (push) Has been cancelled
Python application / build (3.11) (push) Has been cancelled
Python application / build (3.12) (push) Has been cancelled
2025-01-03 13:18:47 +01:00
Cédric Bonhomme
10256ba28b
chg: Updated copyright years. 2025-01-03 13:17:58 +01:00
Cédric Bonhomme
70572bf047
chg: [dependencies] Updated dependencies.
Some checks failed
Python application / build (3.10) (push) Has been cancelled
Python application / build (3.11) (push) Has been cancelled
Python application / build (3.12) (push) Has been cancelled
2024-12-28 00:29:45 +01:00
Cédric Bonhomme
8ebd760f7e
chg: Updated README.
Some checks failed
Python application / build (3.10) (push) Has been cancelled
Python application / build (3.11) (push) Has been cancelled
Python application / build (3.12) (push) Has been cancelled
2024-10-05 16:47:41 +02:00
Cédric Bonhomme
0d0d328230
chg: Updated README.
Some checks are pending
Python application / build (3.10) (push) Waiting to run
Python application / build (3.11) (push) Waiting to run
Python application / build (3.12) (push) Waiting to run
2024-10-04 23:44:57 +02:00
Cédric Bonhomme
5a6e08232f
chg: [release] Bumped release number. 2024-09-07 23:55:55 +02:00
Cédric Bonhomme
6fac279580
chg: [dependencies] Updated dependencies. 2024-09-07 23:54:15 +02:00
Cédric Bonhomme
a6140fef36
chg: [lsb] Added a parameter, close_file, to lsb.reveal in order to specify if the file must be closed at the end of the processing. 2024-09-07 23:51:47 +02:00
Cédric Bonhomme
80d7e6c88a
chg: [dependencies] Updated dependencies. 2024-08-08 09:43:38 +02:00
Cédric Bonhomme
d078b7a734
chg: [dependencies] Updated dependencies. 2024-08-08 09:42:44 +02:00
Cédric Bonhomme
b4114bca00
Merge pull request #50 from cedricbonhomme/dependabot/pip/setuptools-70.0.0
build(deps-dev): bump setuptools from 69.5.1 to 70.0.0
2024-07-16 11:36:53 +02:00
dependabot[bot]
ada72ecb47
build(deps-dev): bump setuptools from 69.5.1 to 70.0.0
Bumps [setuptools](https://github.com/pypa/setuptools) from 69.5.1 to 70.0.0.
- [Release notes](https://github.com/pypa/setuptools/releases)
- [Changelog](https://github.com/pypa/setuptools/blob/main/NEWS.rst)
- [Commits](https://github.com/pypa/setuptools/compare/v69.5.1...v70.0.0)

---
updated-dependencies:
- dependency-name: setuptools
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-15 19:34:32 +00:00
Cédric Bonhomme
d8685270ff
Merge pull request #49 from cedricbonhomme/dependabot/pip/certifi-2024.7.4
build(deps-dev): bump certifi from 2024.2.2 to 2024.7.4
2024-07-08 07:07:24 +02:00
dependabot[bot]
924d8e4979
build(deps-dev): bump certifi from 2024.2.2 to 2024.7.4
Bumps [certifi](https://github.com/certifi/python-certifi) from 2024.2.2 to 2024.7.4.
- [Commits](https://github.com/certifi/python-certifi/compare/2024.02.02...2024.07.04)

---
updated-dependencies:
- dependency-name: certifi
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-06 01:46:39 +00:00
Cédric Bonhomme
40c0e83c88
Merge pull request #47 from cedricbonhomme/dependabot/pip/urllib3-2.2.2
build(deps-dev): bump urllib3 from 2.2.1 to 2.2.2
2024-06-18 07:06:37 +02:00
dependabot[bot]
20bb3dc6fc
build(deps-dev): bump urllib3 from 2.2.1 to 2.2.2
Bumps [urllib3](https://github.com/urllib3/urllib3) from 2.2.1 to 2.2.2.
- [Release notes](https://github.com/urllib3/urllib3/releases)
- [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst)
- [Commits](https://github.com/urllib3/urllib3/compare/2.2.1...2.2.2)

---
updated-dependencies:
- dependency-name: urllib3
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-17 23:07:27 +00:00
Cédric Bonhomme
68b56779b1
Merge pull request #46 from cedricbonhomme/dependabot/pip/requests-2.32.0
build(deps-dev): bump requests from 2.31.0 to 2.32.0
2024-05-21 07:28:03 +02:00
dependabot[bot]
e41c5b7cb0
---
updated-dependencies:
- dependency-name: requests
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-21 05:26:24 +00:00
Cédric Bonhomme
00415e364d
chg: [dependencies] Updated Python dependencies. 2024-05-17 15:55:19 +02:00
Cédric Bonhomme
bd1a82f397
Merge pull request #45 from cedricbonhomme/dependabot/pip/jinja2-3.1.4
build(deps-dev): bump jinja2 from 3.1.3 to 3.1.4
2024-05-07 07:26:15 +02:00
dependabot[bot]
dfce98008c
build(deps-dev): bump jinja2 from 3.1.3 to 3.1.4
Bumps [jinja2](https://github.com/pallets/jinja) from 3.1.3 to 3.1.4.
- [Release notes](https://github.com/pallets/jinja/releases)
- [Changelog](https://github.com/pallets/jinja/blob/main/CHANGES.rst)
- [Commits](https://github.com/pallets/jinja/compare/3.1.3...3.1.4)

---
updated-dependencies:
- dependency-name: jinja2
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-06 20:44:24 +00:00
Cédric Bonhomme
e0360062b5
chg: [dependencies] Updated dependencies. 2024-04-14 22:09:22 +02:00
Cédric Bonhomme
a660ff8628
Merge pull request #44 from cedricbonhomme/dependabot/pip/idna-3.7
build(deps-dev): bump idna from 3.6 to 3.7
2024-04-12 22:55:11 +02:00
dependabot[bot]
53d897b808
build(deps-dev): bump idna from 3.6 to 3.7
Bumps [idna](https://github.com/kjd/idna) from 3.6 to 3.7.
- [Release notes](https://github.com/kjd/idna/releases)
- [Changelog](https://github.com/kjd/idna/blob/master/HISTORY.rst)
- [Commits](https://github.com/kjd/idna/compare/v3.6...v3.7)

---
updated-dependencies:
- dependency-name: idna
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-12 04:23:27 +00:00
Cédric Bonhomme
fd2a784df3
Merge pull request #43 from cedricbonhomme/dependabot/pip/pillow-10.3.0
build(deps): bump pillow from 10.2.0 to 10.3.0
2024-04-04 07:21:18 +02:00
dependabot[bot]
136ce42ae9
build(deps): bump pillow from 10.2.0 to 10.3.0
Bumps [pillow](https://github.com/python-pillow/Pillow) from 10.2.0 to 10.3.0.
- [Release notes](https://github.com/python-pillow/Pillow/releases)
- [Changelog](https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst)
- [Commits](https://github.com/python-pillow/Pillow/compare/10.2.0...10.3.0)

---
updated-dependencies:
- dependency-name: pillow
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-03 16:04:32 +00:00
Cédric Bonhomme
c66a54875c
chg: [dependencies] Updated dependencies. 2024-03-24 11:37:33 +01:00
Cédric Bonhomme
b1133d8e35
chg: [pre-commit] Removed useless django-upgrade hook. 2024-03-12 08:22:40 +01:00
Cédric Bonhomme
a78c520058
Updated README. 2024-03-02 14:27:08 +01:00
Cédric Bonhomme
b592ce4bcb
chg: Updated repository default URL. 2024-03-01 10:14:10 +01:00
Cédric Bonhomme
0054c89b7d
chg: [dependencies] Fixed conflict. 2024-01-21 21:48:19 +01:00
Cédric Bonhomme
ab046d24f2
chg: [dependencies] Updated dependencies. 2024-01-21 21:27:50 +01:00
Cédric Bonhomme
1de253dd0a
Merge pull request #40 from cedricbonhomme/dependabot/pip/jinja2-3.1.3
build(deps-dev): bump jinja2 from 3.1.2 to 3.1.3
2024-01-11 22:23:44 +00:00
dependabot[bot]
a70b552cb8
build(deps-dev): bump jinja2 from 3.1.2 to 3.1.3
Bumps [jinja2](https://github.com/pallets/jinja) from 3.1.2 to 3.1.3.
- [Release notes](https://github.com/pallets/jinja/releases)
- [Changelog](https://github.com/pallets/jinja/blob/main/CHANGES.rst)
- [Commits](https://github.com/pallets/jinja/compare/3.1.2...3.1.3)

---
updated-dependencies:
- dependency-name: jinja2
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-11 19:48:14 +00:00
Cédric Bonhomme
eae8ab29fc
Removed support of Python 3.9. 2024-01-04 02:22:33 +01:00
Cédric Bonhomme
0d20be7d82
chg: [sourcehut] Restored export of PATH. 2024-01-04 02:16:19 +01:00
Cédric Bonhomme
33c9cd55b1
chg: [sourcehut] Removed useless export of PATH. 2024-01-04 02:13:24 +01:00
Cédric Bonhomme
d02f8b8b14
install pipx from Debian packages. 2024-01-04 02:09:39 +01:00
Cédric Bonhomme
d0ca9322cc
install poetry with pipx. 2024-01-04 02:06:32 +01:00
Cédric Bonhomme
91fef6a178
chg: [sourcehut] Updated debian image to bookworm. 2024-01-04 02:00:18 +01:00
Cédric Bonhomme
178347f61f
chg: [GitHub workflox] Added Python 3.10 and 3.11. 2024-01-04 01:51:44 +01:00
Cédric Bonhomme
ed66aaa159
chg: [GitHub workflox] Removed Python 3.9. 2024-01-04 01:48:24 +01:00
Cédric Bonhomme
1293a3575b
chg: [GitHub workflox] Updated Python versions in the matrix strategy. 2024-01-02 23:48:04 +01:00
Cédric Bonhomme
1ef59ac80b
chg: [dependencies] Updated Numpy and Pillow. 2024-01-02 23:30:57 +01:00
Cédric Bonhomme
0ca92d1d3a
Exclude the directory tests from the pre-commit hook end-of-line-fixer. 2024-01-02 23:26:00 +01:00
Cédric Bonhomme
46da77ef1c
chg: [release] Bumped release number. 2024-01-02 10:12:21 +01:00
Cédric Bonhomme
5c94a790bc
Fixed mypy issue. 2024-01-02 10:06:04 +01:00
Cédric Bonhomme
220501adbc
Updated version of flake8. 2024-01-01 20:40:49 +01:00
Cédric Bonhomme
e253929ad6
Updated dependencies. 2024-01-01 20:32:36 +01:00
Cédric Bonhomme
d863dbfddb
Updated copyright years. 2024-01-01 20:32:02 +01:00
Cédric Bonhomme
0f550178ff
Moved the requirements file dedicated to the documentation. 2023-12-31 00:45:28 +01:00
Cédric Bonhomme
a3dd6be618
Dropped Python 3.8 support. 2023-12-31 00:38:32 +01:00
Cédric Bonhomme
29322e759a
Ensures compability with Python 3.12. 2023-12-31 00:28:36 +01:00
Cédric Bonhomme
c6437af0d1
chg: [dependencies] Updated Python dependencies. 2023-11-21 22:48:09 +01:00
Cédric Bonhomme
25447f88e9 Merge branch 'master' of github.com:cedricbonhomme/Stegano 2023-10-23 07:27:33 +02:00
Cédric Bonhomme
ad916563da
chg: [dependencies] Updated dependencies. 2023-10-23 07:27:02 +02:00
Cédric Bonhomme
88ce965e7c
Merge pull request #38 from cedricbonhomme/dependabot/pip/urllib3-2.0.7
build(deps-dev): bump urllib3 from 2.0.6 to 2.0.7
2023-10-17 21:15:13 +00:00
dependabot[bot]
25a7105487
build(deps-dev): bump urllib3 from 2.0.6 to 2.0.7
Bumps [urllib3](https://github.com/urllib3/urllib3) from 2.0.6 to 2.0.7.
- [Release notes](https://github.com/urllib3/urllib3/releases)
- [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst)
- [Commits](https://github.com/urllib3/urllib3/compare/2.0.6...2.0.7)

---
updated-dependencies:
- dependency-name: urllib3
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-17 21:02:56 +00:00
Cédric Bonhomme
29d427afef Merge branch 'master' of github.com:cedricbonhomme/Stegano 2023-10-17 09:36:09 +02:00
Cédric Bonhomme
7434ed9a1e
Updated opencv-python. 2023-10-17 09:35:08 +02:00
Cédric Bonhomme
e3c1312d14
Merge pull request #37 from cedricbonhomme/dependabot/pip/pillow-10.0.1
build(deps): bump pillow from 9.5.0 to 10.0.1
2023-10-04 05:54:43 +00:00
dependabot[bot]
166d9d1291
build(deps): bump pillow from 9.5.0 to 10.0.1
Bumps [pillow](https://github.com/python-pillow/Pillow) from 9.5.0 to 10.0.1.
- [Release notes](https://github.com/python-pillow/Pillow/releases)
- [Changelog](https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst)
- [Commits](https://github.com/python-pillow/Pillow/compare/9.5.0...10.0.1)

---
updated-dependencies:
- dependency-name: pillow
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-04 01:32:15 +00:00
Cédric Bonhomme
556473c81d
Merge pull request #36 from cedricbonhomme/dependabot/pip/urllib3-2.0.6
build(deps-dev): bump urllib3 from 2.0.4 to 2.0.6
2023-10-03 00:31:42 +00:00
dependabot[bot]
84b1855130
build(deps-dev): bump urllib3 from 2.0.4 to 2.0.6
Bumps [urllib3](https://github.com/urllib3/urllib3) from 2.0.4 to 2.0.6.
- [Release notes](https://github.com/urllib3/urllib3/releases)
- [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst)
- [Commits](https://github.com/urllib3/urllib3/compare/2.0.4...2.0.6)

---
updated-dependencies:
- dependency-name: urllib3
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-03 00:00:24 +00:00
Cédric Bonhomme
11348dda9a
Updated dependencies. 2023-09-10 23:56:36 +02:00
Cédric Bonhomme
c2168422df
Updated numpy and opencv. 2023-08-13 09:23:29 +02:00
Cédric Bonhomme
bd2835d7d0
Added new Read the Docs configuration file. 2023-08-10 09:52:22 +02:00
Cédric Bonhomme
116e011d37
Updated certifi. 2023-07-28 23:53:59 +02:00
Cédric Bonhomme
84a34d0bfa
Updated urllib3 dependency. 2023-07-20 00:42:12 +02:00
Cédric Bonhomme
8c14555adb
chg: [dependencies] updated dependencies. 2023-07-12 15:08:00 +02:00
Cédric Bonhomme
e33bebad46
chg: [dependencies] Updated Python dependencies. 2023-07-01 00:54:34 +02:00
Cédric Bonhomme
2a3b92e62a
chg: [dependencies] Updated Python dependencies. 2023-06-25 22:01:13 +02:00
Cédric Bonhomme
0537c13a08
Updated CHANGELOG. 2023-05-23 10:04:06 +02:00
Cédric Bonhomme
e490e32791
chg: [security] fix CVE-2023-32681 (GHSA-j8r2-6x86-q33q). 2023-05-23 07:29:49 +02:00
Cédric Bonhomme
91789f065e
chg: [dependencies] Updated Python dependencies. 2023-05-21 11:17:02 +02:00
Cédric Bonhomme
bc9b91e983
chg: [type] Changed the way variables in some funtions are typed. 2023-05-21 10:30:11 +02:00
Cédric Bonhomme
c12b371e7d
chg: [type] Changed the way the dict in erotosthenes funtion is typed. 2023-05-21 10:26:33 +02:00
Cédric Bonhomme
3fd55e2e79
fix: [type] Removed the return type of the function n_at_a_time. 2023-05-21 10:21:48 +02:00
Cédric Bonhomme
7abce7d5d9
fixed few mypy related issues. 2023-04-26 14:17:49 +02:00
Cédric Bonhomme
6d87ba2921
chg: [dependencies] Updated Python dependencies. 2023-04-26 13:58:40 +02:00
Cédric Bonhomme
eb639510ce
Updated dependencies and README. 2023-03-29 08:22:29 +02:00
Cédric Bonhomme
d10805427a
Fixed conflicts. 2023-03-15 07:55:28 +01:00
Cédric Bonhomme
bfc5d332be
Updated opencv-python dependency. 2023-03-15 07:54:39 +01:00
Cédric Bonhomme
e2fe642e3d
chg: [dependencies] Fixed conflict in poetry.lock 2023-02-25 10:29:14 +01:00
Cédric Bonhomme
92d3e0697d
chg: [dependencies] Updated dependencies. 2023-02-25 10:28:12 +01:00
Cédric Bonhomme
4575319307
added .pre-commit-config.yaml file. 2023-01-16 15:13:17 +01:00
Cédric Bonhomme
0080ec06f7
upgraded pyproject.toml file 2023-01-16 15:12:01 +01:00
Cédric Bonhomme
d6a18a71e4
fix: [numpy] is a deprecated alias for . (Deprecated NumPy 1.24) 2023-01-09 13:46:51 +01:00
Cédric Bonhomme
fa741328f3
chg: [security] Updated certifi. 2023-01-09 09:42:01 +01:00
Cédric Bonhomme
2abe01660d
chg: [dependencies] Updated various Python dependencies. 2023-01-01 19:51:47 +01:00
Cédric Bonhomme
3dd5dd2be3
chg: [security] GHSA-43fp-rhv2-5gv8 Upgraded certifi (2022.9.24 => 2022.12.7). 2022-12-09 09:54:07 +01:00
Cédric Bonhomme
857d311a79
chg: [workflows] Updated python version in matrix workflow strategy. 2022-12-02 21:13:34 +01:00
Cédric Bonhomme
0daa5208c7
chg: [workflows] Updated python version in matrix workflow strategy. 2022-12-02 21:07:37 +01:00
Cédric Bonhomme
ea2f1a6eda
chg: [workflows] Updated python version in matrix workflow strategy. 2022-12-02 21:01:45 +01:00
Cédric Bonhomme
03ebf467a2
chg: [workflows] Updated python version in matrix workflow strategy. 2022-12-02 21:00:11 +01:00
Cédric Bonhomme
7f9c34c6da
chg: [workflows] Updated python version in matrix workflow strategy. 2022-12-02 20:58:49 +01:00
Cédric Bonhomme
dd16c1cb61
chg: [workflows] Updated python version in matrix workflow strategy. 2022-12-02 20:56:12 +01:00
Cédric Bonhomme
257fa03c9f
chg: [dependencies] Updated numpy and other dependencies. 2022-12-02 20:45:22 +01:00
Cédric Bonhomme
e68404176a
chg: [workflows] Updated build workflows. 2022-11-20 21:57:39 +01:00
Cédric Bonhomme
e988e9e02a
chg: [release] Updated CHANGELOG. 2022-11-20 21:50:45 +01:00
Cédric Bonhomme
fb4b596726
fix: [flake8] Addressed flake8 warnings. 2022-11-20 21:49:02 +01:00
Cédric Bonhomme
0f8b0cef1f
chg: [command] The selection of a sub-command is mandatory. 2022-11-20 21:37:29 +01:00
Cédric Bonhomme
3b619afa17
Updated README. !minor. 2022-11-20 00:54:02 +01:00
Cédric Bonhomme
4c37a41e77
chg: [workflows] On GitHub removed the mypy check for Python 3.11. 2022-11-20 00:45:40 +01:00
Cédric Bonhomme
a67793e7a2
chg: [workflows] For GitHub Actions unit tests are done with nose2. 2022-11-20 00:39:02 +01:00
Cédric Bonhomme
298acb7d57
chg: [flake] Added exit-zero parameter. 2022-11-20 00:33:17 +01:00
Cédric Bonhomme
3eaa93e36c
new: [workflow] Added unit tests and mypy checks to the GitHub workflow. 2022-11-20 00:31:15 +01:00
Cédric Bonhomme
18d1176ae8
chg [release]: Updated changelog and documentation. 2022-11-20 00:27:14 +01:00
Cédric Bonhomme
2e4f1629fd
fix: [mypy] Addressed mypy errors. 2022-11-20 00:15:25 +01:00
Cédric Bonhomme
bee5f819d9
chg: [contributors] Added a new contributor. 2022-11-20 00:11:16 +01:00
Cédric Bonhomme
1f684966cc
chg: [tests] Removed test which exceded the limit of int that can be parsed. 2022-11-20 00:06:46 +01:00
Cédric Bonhomme
a610da148e
chg: Set Python version to >=3.8,<3.12. 2022-11-20 00:03:41 +01:00
Cédric Bonhomme
cf40cb26b0
Updated dependencies 2022-11-19 23:59:35 +01:00
Cédric Bonhomme
e290d1260e
chg: [flake] Addressed some flake and mypy issues. 2022-11-13 23:28:32 +01:00
Cédric Bonhomme
fc53f2a6b9
chg: [style] Fixed style with black. 2022-11-13 23:05:10 +01:00
Cédric Bonhomme
e6e0f4f80f
chg: [documentation] Removed dead link from the documentation. 2022-11-13 23:04:35 +01:00
Cédric Bonhomme
053c27ad4f
chg: [tests] Use a bigger picture with the Mersenne generator. 2022-11-13 20:34:40 +01:00
Cedric
43b963d4f1
Merge pull request #34 from FlavienRx/refacto
Refacto & merge lsb with lsbset
2022-11-13 14:08:40 +00:00
Flavien
076a5d447f Update documentation 2022-11-12 17:13:12 +01:00
Flavien
82b59f73db Add test with mersenne generator 2022-11-11 14:19:34 +01:00
Flavien
54938159d4 Add test with ackermann_naive generator 2022-11-11 14:07:40 +01:00
Flavien
f6aa2207f4 Merge lsb and lsbset bin 2022-11-11 14:06:48 +01:00
Flavien
eed1f08526 Merge lsb and lsbset tests 2022-11-11 12:17:16 +01:00
Flavien
3103a3ae9b Merge lsb and lsbset modules 2022-11-11 12:15:25 +01:00
Flavien
0d98e1834c Create Hider and Revealer class 2022-11-09 17:15:21 +01:00
Flavien
58dbb94c5e Refacto lsbset.reveal to use getpixel 2022-11-08 21:44:43 +01:00
Flavien
257d2c2f68 Refacto lsbset.hide to use less memory 2022-11-08 21:41:10 +01:00
Cédric Bonhomme
cf7209f94c
chg: [dependencies] Updated all core dependencies. 2022-10-17 13:37:40 +02:00
Cédric Bonhomme
ee1044e627
Updated dependencies. 2022-09-09 23:13:32 +02:00
Cédric Bonhomme
afb095c5ae
chg: updated dependencies. 2022-07-30 23:33:45 +02:00
Cédric Bonhomme
12410c2548
updated Python dependencies. 2022-07-05 14:18:05 +02:00
Cédric Bonhomme
ce472f9dd9
chg: [dependencies] Updated pillow. 2022-05-23 08:49:15 +02:00
Cédric Bonhomme
fea529d84d
Updated Pillow and other dependencies. 2022-05-11 09:34:08 +02:00
Cédric Bonhomme
cc255e2e55
chg: [dependencies] Updated numpy. 2022-03-12 00:49:47 +01:00
Cédric Bonhomme
35a4c20d8d
Updated dependencies. 2022-02-27 23:08:03 +01:00
Cédric Bonhomme
1c53b1d01b
updated CHANGELOG.md file 2022-01-13 07:56:25 +01:00
Cédric Bonhomme
a18efe6ab8
fix: [security] CVE-2022-22816, CVE-2022-22817, CVE-2022-22815. 2022-01-13 07:50:14 +01:00
Cédric Bonhomme
1b8d8042b7
fix: [core] various minor bug fixes. 2022-01-05 00:29:13 +01:00
Cédric Bonhomme
0a17b29121
chg: bumped dependencies. 2021-11-30 09:16:41 +01:00
Cédric Bonhomme
9c981005e4
updated CHANGELOG.md file 2021-11-29 23:02:10 +01:00
Cédric Bonhomme
555cf1acc2
chg: updated releases note. 2021-11-29 22:58:41 +01:00
Cédric Bonhomme
d7ea51eed7
chg: [style] !minor reformat 2021-11-24 13:38:04 +01:00
Cédric Bonhomme
b6c8cc3d35
chg: [mypy] fixed mypy issue 2021-11-24 13:31:37 +01:00
Cédric Bonhomme
871f1f60b4
chg: [mypy] fixed mypy issue 2021-11-24 13:09:24 +01:00
Cédric Bonhomme
0460acd7f3
chg: [mypy] fixed mypy issue 2021-11-24 12:11:26 +01:00
Cédric Bonhomme
27083934d0
chg: [test] decrease relative tolerance. 2021-11-24 12:00:06 +01:00
Cédric Bonhomme
1033933f85
chg: [test] decrease relative tolerance. 2021-11-24 09:40:13 +01:00
Cédric Bonhomme
c25cdc09f7
chg: [test] trying assert_allclose instead of assert_array_equal. 2021-11-24 09:29:14 +01:00
Cédric Bonhomme
a1213a9163
chg: [style] Reformated with black. 2021-11-24 09:16:05 +01:00
Cédric Bonhomme
891051f77b
chg: [shi-tomashi corner generator] fixed the test of the generator 2021-11-24 09:15:35 +01:00
Cédric Bonhomme
a5912ba3f1
chg: added libraries for opencvn in debian.yml build configuration file. 2021-11-22 23:45:57 +01:00
Cedric
4a657625f5
Merge pull request #32 from thundersparkf/feat/shi_tomashi_generator
added: shi-tomashi corner generator
2021-11-22 22:40:24 +00:00
thundersparkf
c774e9e91c added: test cases for shi-tomashi generator
Signed-off-by: thundersparkf <devagastya0@gmail.com>
2021-11-23 03:08:12 +05:30
thundersparkf
8ea1553538 added: shi-tomashi corner generator
Signed-off-by: thundersparkf <devagastya0@gmail.com>
2021-11-23 02:32:02 +05:30
Cédric Bonhomme
b00dc73fd4
updated README. 2021-11-01 15:37:19 +01:00
Cédric Bonhomme
4aac55ec77
chg: [core ] various minor fixes. 2021-11-01 13:52:16 +01:00
Cédric Bonhomme
b64d039389
new: added .editorconfig 2021-11-01 13:51:47 +01:00
Cédric Bonhomme
be3e32ef6c
chg: [test] Updated mypy configuration. 2021-11-01 13:42:55 +01:00
Cédric Bonhomme
415afc3ac8
use debian/bullseye in the Debian build. 2021-11-01 12:10:48 +01:00
Cédric Bonhomme
7681cee3d3
chg: added python3-setuptools to the Debian build configuration file. 2021-11-01 12:01:35 +01:00
Cédric Bonhomme
b9e01071f0
chg: updated dependencies 2021-11-01 12:00:50 +01:00
Cédric Bonhomme
0602d8328a
chg: [core] updated Python dependencies. 2021-10-23 23:58:51 +02:00
Cedric
44fb07ab69
Merge pull request #31 from kapkic/patch-1
Update index.rst
2021-07-25 21:34:56 +00:00
Cédric Bonhomme
317eb0d889
updated changelog for the release 0.9.9 2021-07-02 23:55:07 +02:00
kapkic
b7d26d15ee
Update index.rst
Fixed a typo in documentation/readthedocs.io.

Removed/Added:
-Turorial
+Tutorial
2021-05-25 23:21:08 -04:00
Cédric Bonhomme
16e12e8edf
chg: [documentation] fix the name of Stegano. 2021-04-10 11:49:21 +02:00
Cédric Bonhomme
96ff4ae620
chg: [documentation] use the Sphinx ReadTheDocs theme. 2021-04-10 11:44:25 +02:00
Cédric Bonhomme
d29d42509e
chg: [documentation] updated index page. 2021-04-10 11:40:32 +02:00
Cédric Bonhomme
3ef0239b8c
chg: [tests] added libssl-dev package in debian.yaml file 2021-04-10 11:37:49 +02:00
Cédric Bonhomme
ff09bfb267
chg: [tests] added cargo package in debian.yaml file 2021-04-10 11:30:05 +02:00
Cédric Bonhomme
d765fee7cf
chg: [tests] added go package in debian.yaml file 2021-04-10 11:29:08 +02:00
Cédric Bonhomme
99971ff14f
chg: [tests] added rust packages in debian.yaml file 2021-04-10 11:27:48 +02:00
Cédric Bonhomme
d030b8db7d
chg: [tests] added rust packages in debian.yaml file 2021-04-10 11:24:04 +02:00
Cédric Bonhomme
b7ca9a9704
chg: [documentation] updated configuration for the documentation 2021-04-10 11:13:00 +02:00
Cédric Bonhomme
0290b2836b
chg: [documentation] updated configuration for the documentation 2021-04-10 10:57:34 +02:00
Cédric Bonhomme
183daf3155
chg: [documentation] updated configuration for the documentation 2021-04-10 10:53:50 +02:00
Cédric Bonhomme
50320b0cd2
chg: [documentation] updated requirements for the documentation 2021-04-10 10:37:23 +02:00
Cédric Bonhomme
048322532b
chg: [documentation] Small changes to the Sphynx conf.py file. 2021-04-10 10:30:20 +02:00
Cédric Bonhomme
0fac8cb22c
chg: updated CONTRIBUTORS.md file. 2021-04-10 10:23:37 +02:00
Cédric Bonhomme
1b484563f5
chg: updated Pillow dependency. 2021-04-10 10:07:33 +02:00
Cédric Bonhomme
6d3818ceca
chg: updated dependencies. 2021-04-10 10:04:58 +02:00
Cédric Bonhomme
c1ec5b9ab1
updated copyright years 2021-02-06 23:03:35 +01:00
Cédric Bonhomme
e9a7721ccc
Updated README and pyproject.toml with correct link to the home page of the project. 2021-02-04 17:16:58 +01:00
Cédric Bonhomme
45de0524d6
updated required libraries for Pillow. 2021-02-03 15:52:44 +01:00
Cédric Bonhomme
c2047f569e
added required libraries for Pillow. 2021-02-03 15:49:25 +01:00
Cédric Bonhomme
ecc858b9c3
updated dependencies 2021-02-03 00:34:33 +01:00
Cédric Bonhomme
3aeb032623
Updated README. 2020-08-20 09:57:25 +02:00
Cédric Bonhomme
e5c7e003d6
Updated .gitignore. 2020-08-20 09:56:34 +02:00
Cédric Bonhomme
6e24ec6374
updated dependencies. 2020-08-20 09:56:17 +02:00
Cédric Bonhomme
e9605589b6
Added mising command line tools. 2019-12-22 10:46:33 +01:00
Cédric Bonhomme
3dfebda205
fixed case of the module name 2019-12-21 17:23:28 +01:00
Cédric Bonhomme
13693ab3b5
fixed includes in pyproject.toml 2019-12-20 13:20:26 +01:00
Cédric Bonhomme
8260dd6444
Update CHANGELOG. 2019-12-20 09:48:28 +01:00
Cédric Bonhomme
d160c4f1cf
redoing cli 2019-12-20 00:17:10 +01:00
Cédric Bonhomme
f72afd2f12
simplying project root 2019-12-20 00:02:37 +01:00
Cédric Bonhomme
04531488ca
nose2 replaces nose for the unit tests 2019-12-19 23:03:49 +01:00
Cédric Bonhomme
71f6c08c28
Improved code style. 2019-12-17 09:18:37 +01:00
Cédric Bonhomme
9b216d9d59
Updated README. 2019-12-15 21:53:13 +01:00
Cédric Bonhomme
a61a7916a3
Improved mypy checks 2019-12-15 21:51:39 +01:00
Cédric Bonhomme
c0abda31bc
fixed case 2019-12-15 15:58:14 +01:00
Cédric Bonhomme
e5cfceb261
Updated URLs. 2019-12-15 15:53:47 +01:00
Cédric Bonhomme
5f2dd1dad8
change dir in each tasks 2019-12-14 15:36:29 +01:00
Cédric Bonhomme
0f5a251398
updated documentation 2019-12-14 15:32:52 +01:00
Cédric Bonhomme
13b06ee04a
restored export PATH commands 2019-12-14 15:29:55 +01:00
Cédric Bonhomme
6e5a0a3523
Removed probably useless commands for build.sr.ht 2019-12-14 13:42:45 +01:00
Cédric Bonhomme
9acc9afa99
Removed .travis configuration. 2019-12-14 10:48:38 +01:00
Cédric Bonhomme
a03c59db5c
renamed a task 2019-12-14 10:41:39 +01:00
Cédric Bonhomme
481b315605
installs missing python3.venv 2019-12-14 10:40:32 +01:00
Cédric Bonhomme
cc626f8954
Updated requirements on python version. 2019-12-14 10:32:15 +01:00
Cédric Bonhomme
15b62a8034
fixed poetry install 2019-12-14 09:53:27 +01:00
Cédric Bonhomme
6e9dff3c79
Using poetry to generate test builds. 2019-12-14 09:36:21 +01:00
Cédric Bonhomme
cfe3a1325c
Fixed link to contributors file. 2019-10-29 22:23:27 +01:00
Cédric Bonhomme
213531f63d Merge branch 'master' of github.com:cedricbonhomme/Stegano 2019-10-29 22:12:15 +01:00
Cédric Bonhomme
23ad27f267
Add the link to the tracker. 2019-10-29 22:12:05 +01:00
Cédric Bonhomme
3c4042ac91
Fixed bad link to the project home page. 2019-10-28 07:21:56 +01:00
Cédric Bonhomme
37a0bc2fa2
Bumped version number. 2019-10-27 22:49:38 +01:00
Cédric Bonhomme
54277960d9
Bumped version number. 2019-10-27 22:19:15 +01:00
Cédric Bonhomme
a265cbaa18
Updated links to the home page of Stegano. 2019-10-27 22:14:09 +01:00
Cédric Bonhomme
6645bd62c8
Updated README. 2019-10-27 22:07:30 +01:00
Cédric Bonhomme
ab19ea12f2
Updated url in setup.py 2019-10-27 22:05:50 +01:00
Cédric Bonhomme
c5298a8411
typo 2019-10-27 22:04:57 +01:00
Cédric Bonhomme
a9b9136141
Everything in Markdown. 2019-10-27 22:03:36 +01:00
Cédric Bonhomme
3de95517e9
chg dir 2019-10-27 21:53:36 +01:00
Cédric Bonhomme
b116202adb
set the env 2019-10-27 21:46:09 +01:00
Cédric Bonhomme
c6481938de
added type check task 2019-10-27 21:36:59 +01:00
Cédric Bonhomme
dbe54238b4
one more test 2019-10-27 20:01:08 +01:00
Cédric Bonhomme
0d5a8a1362
one more test 2019-10-27 19:47:12 +01:00
Cédric Bonhomme
207ac6c4f8
one more test 2019-10-27 19:44:55 +01:00
Cédric Bonhomme
fbe9f1b6e1
one more test 2019-10-27 19:43:29 +01:00
Cédric Bonhomme
ea165fe1b1
one more test 2019-10-27 19:40:54 +01:00
Cédric Bonhomme
649fd542d7
fix pipenv path 2019-10-27 18:12:48 +01:00
Cédric Bonhomme
4019e792d4
Install node 2019-10-27 18:10:10 +01:00
Cédric Bonhomme
5e909bfc24
added pipenv to the path 2019-10-27 18:03:28 +01:00
Cédric Bonhomme
d3b8a65318
It's pip3... 2019-10-27 17:56:35 +01:00
Cédric Bonhomme
2dc6430373
switch to Debian 2019-10-27 17:54:37 +01:00
Cédric Bonhomme
e74662cc90
Install pip the correct way on Alpine... 2019-10-27 17:48:14 +01:00
Cédric Bonhomme
44fc336c8a
test with alpine 2019-10-27 17:40:47 +01:00
Cédric Bonhomme
508834f7ea
Added pip to the packages for the build. 2019-10-27 17:35:54 +01:00
Cédric Bonhomme
027d3a57f4
Added build manifest for SourceHut. 2019-10-27 17:30:56 +01:00
Cédric Bonhomme
f8cc0f4cb3
Updated version of Pillow. 2019-10-22 22:44:00 +02:00
Cédric Bonhomme
5122f204f6
Cleaned headers info 2019-06-07 22:56:18 +02:00
Cédric Bonhomme
e7017e61a6
Arguments are required here. 2019-06-07 22:43:34 +02:00
Cédric
d47a105ac7
Merge pull request #28 from AdrienCos/master
Improved steganalysis.parity
2019-06-07 20:36:05 +00:00
AdrienCos
a060757f76
Added steganalysis.statistics unit test 2019-06-06 22:36:06 -04:00
AdrienCos
b054fc7a0c
Updated metadata 2019-06-06 22:05:56 -04:00
AdrienCos
2d74094dc3
Fixed steganalysis parity to be compatible with RGBA images 2019-06-06 21:59:25 -04:00
AdrienCos
064456e2a3
Added a unit test for steganlysis-parity 2019-06-06 21:45:15 -04:00
Cédric Bonhomme
e4784ad04d
Updated dependencies. 2019-06-05 00:39:17 +02:00
Cédric Bonhomme
f72cfec815
Updated setup.py 2019-06-05 00:36:27 +02:00
Cédric Bonhomme
6a691c7927
Updated CONTRIBUTORS.rst file 2019-06-05 00:34:47 +02:00
Cédric Bonhomme
a3019f6c1a
Updated changelog 2019-06-05 00:31:30 +02:00
Cédric Bonhomme
ffd9fca5f1
Updated changelog 2019-06-05 00:26:08 +02:00
Cédric
3780379f91
Merge pull request #27 from AdrienCos/master
Implemented LFSR generator (with tests and CLI)
2019-06-04 22:12:07 +00:00
AdrienCos
097692900a Fixed type error 2019-06-04 17:30:55 -04:00
AdrienCos
9f032fffbc Implemented LFSR generator with tests and CLI integration 2019-06-04 17:30:55 -04:00
Cédric
bf094c0361
Merge pull request #26 from AdrienCos/master
Implemented Ackemann generators CLI interface
2019-06-04 21:21:31 +00:00
AdrienCos
1533db0425
Added Ackermann tests for better coverage 2019-06-04 12:29:04 -04:00
AdrienCos
c6603e276b
Fixed unit test for ackermann naive 2019-06-04 12:12:37 -04:00
AdrienCos
3f350c3569
Encapsulated the ackermann_naive generator, updated revision dates 2019-06-04 11:40:50 -04:00
AdrienCos
8f8cd483fd
Adde CLI support for the fast Ackermann generator 2019-06-04 11:36:40 -04:00
AdrienCos
45cdb9a3a5
Merge remote-tracking branch 'upstream/master' 2019-06-01 15:40:36 -04:00
Cédric Bonhomme
5df01ea737
Cosmethic changes. 2019-06-01 12:01:15 +02:00
Cédric Bonhomme
c3f6f392f3
added a shift parameter for the lsb module. closes #25 2019-06-01 11:53:07 +02:00
Cédric Bonhomme
37aeb39167
fixed ackermann tests 2019-06-01 01:39:53 +02:00
Cédric Bonhomme
f3cbe09b21
mypy tests were failing 2019-06-01 01:20:26 +02:00
Cédric Bonhomme
5694e5c806
Ackermann is now encapsulated in a generator. 2019-06-01 00:58:00 +02:00
Cédric Bonhomme
1704aec690
Updated revision date. 2019-05-31 22:30:14 +02:00
Cédric Bonhomme
0e18b2f95a
Ignore the alpha component when revealing a message. 2019-05-31 22:21:35 +02:00
Cédric Bonhomme
3b6be169b2 Merge branch 'master' of github.com:cedricbonhomme/Stegano 2019-05-31 22:15:49 +02:00
Cédric Bonhomme
48ff22476f
fixes #23: lsbset.hide cause .png transparent area lost. 2019-05-31 22:15:04 +02:00
AdrienCos
316d3eea08
First draft 2019-05-30 01:04:24 -04:00
Cédric Bonhomme
47f70cc820
Use pipx instead of using pipsi. 2019-05-08 08:21:56 +02:00
Cédric Bonhomme
502e630379
Updated urllib3 2019-04-24 08:31:25 +02:00
Cédric Bonhomme
5bb91d2ff0
Bumped version number. 2019-04-10 15:17:45 +02:00
Cédric Bonhomme
ba3cde6112
Updated tests. 2019-04-10 15:09:07 +02:00
Cédric
c5b7df87f5
Merge pull request #22 from sh4nks/master
Pass Image.Image instance to 'hide' and 'reveal' methods
2019-04-08 09:26:48 +00:00
Peter Justin
d8fe34e5bc black + remove unused imports 2019-04-08 10:03:24 +02:00
Peter Justin
93de151025 Add option to either pass a path or an Image instance 2019-04-08 09:58:06 +02:00
Cédric Bonhomme
538316c722
Bumped version number. 2019-04-04 15:39:48 +02:00
Cédric Bonhomme
8d1985dfae
bumped version number 2019-03-06 22:32:47 +01:00
Cédric Bonhomme
3e8b6cdfd0
Updated dependencies. 2019-03-06 22:28:45 +01:00
Cédric Bonhomme
fa18ab4fef
Fixed a bad link. 2019-01-26 17:24:55 +01:00
Cédric Bonhomme
a1db2997a9
Updated copyright years. 2019-01-22 21:29:36 +01:00
Cédric Bonhomme
07b44e8606
Added documentation about the new optional argument of the lsb-set module. 2019-01-22 00:26:29 +01:00
Cédric Bonhomme
5b297efc0e
Updated dependencies in Pipfile. 2018-12-18 23:39:02 +01:00
Cédric Bonhomme
ea2de8142c
added a shift parameter for the lsbset module. 2018-12-18 21:38:25 +01:00
Cédric Bonhomme
a86361dd27
Minor updates to the documentation. 2018-11-22 10:23:54 +01:00
Cédric Bonhomme
bd5b156453
Updated Python version in the requirements. 2018-11-05 22:19:18 +01:00
Cédric Bonhomme
adcc3cf9a5
Updated CHANGELOG. 2018-11-05 21:51:46 +01:00
Cédric Bonhomme
c4b1fdac56
Updated Pipfile.lock due to CVE-2018-18074. 2018-11-05 11:39:36 +01:00
Cédric Bonhomme
9f2f73973a
Merge pull request #21 from BoboTiG/fix-resource-warnings
Fix ResourceWarning: unclosed file, in tests
2018-08-18 11:52:02 +02:00
Mickaël Schoentgen
f9619a8e1c Fix ResourceWarning: unclosed file, in tests 2018-08-18 11:43:38 +02:00
Cédric Bonhomme
041a4ee05b
Prepare the new release. 2018-04-18 14:26:45 +02:00
Cédric Bonhomme
d863dd71a1
Updated Pipfile. 2018-04-18 14:03:19 +02:00
Cédric Bonhomme
a3f02db66c
fix for the UTF-8 characters in the README. 2018-04-18 13:56:12 +02:00
Cédric Bonhomme
3752660742
Bumped version number. 2018-02-28 22:48:52 +01:00
Cédric Bonhomme
29b99f5db9
Updated README. 2018-02-28 22:45:21 +01:00
Cédric Bonhomme
9967ba300e
Updated Pipfile. 2018-02-28 09:41:57 +01:00
Cédric Bonhomme
e7a5a9aac9
Updated README. 2018-02-28 09:41:22 +01:00
Cédric Bonhomme
d5490006d5
Updated README. 2018-02-27 23:04:03 +01:00
Cédric Bonhomme
ea406bf4d1
Added requirements to Pyhon 3.6.3 2018-02-27 22:36:09 +01:00
Cédric Bonhomme
3c3ab16ebc
Added twine to the dev packages. 2018-02-24 00:10:46 +01:00
Cédric Bonhomme
1dced550af
Bump version number. 2018-02-23 21:44:24 +01:00
Cédric Bonhomme
09bc6cbaea
Removed env PIPENV_IGNORE_VIRTUALENVS=1 2018-02-23 21:35:27 +01:00
Cédric Bonhomme
3fb30f06f3
Update travils.yml file 2018-02-23 21:31:43 +01:00
Cédric Bonhomme
08f4dd9357
Fix error in travis.yml with the new Pipfile. 2018-02-23 21:25:41 +01:00
Cédric Bonhomme
894a361340
Updated setup.py 2018-02-23 15:39:48 +01:00
Cédric Bonhomme
0891b92b12
Updated Python full version with pipenv 2018-02-23 15:21:06 +01:00
Cédric Bonhomme
9de4643458
Updated READNE and changelog. 2018-02-23 15:04:24 +01:00
Cédric Bonhomme
4d381a56fb
Updated README.rst 2018-02-23 15:02:02 +01:00
Cédric Bonhomme
db76cfeb73
Switch to pipenv. 2018-02-23 14:46:34 +01:00
Cédric Bonhomme
73245e9d2a
Switch to pipenv. 2018-02-23 14:44:32 +01:00
Cédric Bonhomme
5f458a7f0d
Updated README. 2018-01-12 21:25:20 +01:00
Cédric Bonhomme
8137489c7a
Updated README. 2017-12-23 00:09:19 +01:00
Cédric Bonhomme
3f67ff8ac6
Updated CHANGELOG. 2017-12-20 22:19:33 +01:00
Cédric Bonhomme
5ec1f338b2
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. 2017-12-20 22:17:03 +01:00
Cédric Bonhomme
d942f8bdfb Merge branch 'master' of github.com:cedricbonhomme/Stegano 2017-07-23 10:52:08 +02:00
Cédric Bonhomme
29e3dcfe75
Added a precision about the optional type checker. 2017-07-23 10:51:30 +02:00
Cédric Bonhomme
00567718a0
How to contribute with pew. 2017-06-28 18:42:02 +02:00
Cédric Bonhomme
5322ae7839
Change the generator used for the test. 2017-06-11 23:40:15 +02:00
Cédric Bonhomme
26f38f98de
Updated README. 2017-05-16 14:40:19 +02:00
Cédric Bonhomme
e0a172f868
Updated documentation. 2017-05-16 08:27:53 +02:00
Cédric Bonhomme
8f73d9e733
list available choices for the arguments when necessary 2017-05-16 07:38:33 +02:00
Cédric Bonhomme
61b6ec7175 it is now possible to specify the encoding (UTF-8 or UTF-32LE) of the message to hide/reveal through the command line (for the LSb module). 2017-05-15 22:57:13 +02:00
Cédric Bonhomme
b907985886
Updated setup.py. 2017-05-13 17:38:11 +02:00
Cédric Bonhomme
349f776aad
Updated setup.py. 2017-05-13 17:37:07 +02:00
Cédric Bonhomme
4136f047ab
Updated .gitignore. 2017-05-13 17:34:50 +02:00
Cédric Bonhomme
0b7ba6a998
Add proper exit code check to mypy script. 2017-05-07 15:13:18 +02:00
Cédric Bonhomme
c5c2d34591
Updated CONTRIBUTORS.rst 2017-05-07 14:36:32 +02:00
Cédric Bonhomme
39ed2d493e Merge pull request #16 from maxwellgerber/cleanup-unittest
Finish cleaning up unit tests per cedric's approval
2017-05-07 13:52:03 +02:00
Maxwell Gerber
e47f55af7e Finish cleaning up unit tests per cedric's approval 2017-05-07 03:55:18 -07:00
Cédric Bonhomme
fad3631cdc Merge pull request #15 from maxwellgerber/cleanup-unittest
Cleanup unittest
2017-05-07 12:39:27 +02:00
Maxwell Gerber
49c2986584 Move mersene to own file 2017-05-07 03:20:38 -07:00
Maxwell Gerber
74c4ff25a1 Move base64 string constant to own file, move ackermann to own file, create expected-results directory 2017-05-07 03:11:57 -07:00
Cédric Bonhomme
cbb9df7675
Version 0.8. 2017-05-06 23:20:54 +02:00
Cédric Bonhomme
a770f402cb
Updated README. 2017-05-06 22:20:07 +02:00
Cédric Bonhomme
58bec3b7c7
Updated CHANGELOG. 2017-05-06 09:25:48 +02:00
Cédric Bonhomme
f5c22be82b
Updated documentation. 2017-05-06 00:00:50 +02:00
Cédric Bonhomme
9b2dae3a99
Problem with the return code of mypr :-( 2017-05-05 23:50:24 +02:00
Cédric Bonhomme
8e24845b59
Forgot code block 2017-05-05 23:39:55 +02:00
Cédric Bonhomme
1ce140d638
Updated command line and README. 2017-05-05 23:35:27 +02:00
Cédric Bonhomme
2814f04f80
Updated README with description about Steganography. Closes #13. 2017-05-05 21:17:33 +02:00
Cédric Bonhomme
b6c53415f3
Release 0.7.1. 2017-05-05 11:28:37 +02:00
Cédric Bonhomme
4122358571
Improved type check in statistics.py. mypy check added in travis configuration file. 2017-05-05 11:07:09 +02:00
Cédric Bonhomme
98682932b5
Improved generators and the coverage of the tests. 2017-05-05 09:46:12 +02:00
Cédric Bonhomme
385dc39753
Updated README. 2017-05-04 21:20:51 +02:00
Cédric Bonhomme
6895f96398
Prepare the new release v0.7. 2017-05-04 21:16:03 +02:00
Cédric Bonhomme
bcb58fab36
Added Python 3.6 in the setup.py file. 2017-05-04 17:44:55 +02:00
Cédric Bonhomme
d00d6b12d7
updated CHANGELOG 2017-05-04 15:13:51 +02:00
Cédric Bonhomme
6c362447fa
updated CHANGELOG 2017-05-04 14:58:05 +02:00
Cédric Bonhomme
a279b21a70
Solve mypy warning. 2017-05-04 13:25:14 +02:00
Cédric Bonhomme
13f0fb8086 Merge branch 'unicode' 2017-05-04 13:08:33 +02:00
Cédric Bonhomme
6be5c32fe7
Updated revision date of changed Python files. 2017-05-04 13:08:20 +02:00
Cédric Bonhomme
5f5c07493c
Introduce a new argument in a2bits_list in order to specify the encoding of the string (unicode) 2017-05-04 13:05:56 +02:00
Cédric Bonhomme
293346a2ee
why not test with Python 3.6? 2017-03-22 08:47:12 +01:00
Cédric Bonhomme
49964d4f16 Merge branch 'unicode' of github.com:cedricbonhomme/Stegano into unicode 2017-03-21 07:39:00 +01:00
Cédric Bonhomme
3e549bd543 Merge branch 'master' of github.com:cedricbonhomme/Stegano into unicode 2017-03-21 07:38:49 +01:00
Cédric Bonhomme
a14adf70e0
Updated CONTRIBUTORS.rst 2017-03-20 23:17:02 +01:00
Cédric Bonhomme
4fc8eac172
a2bits_list now return a list of 32 bits characters 2017-03-20 22:53:55 +01:00
Cédric Bonhomme
0fd6fc51fb
Display the CI status for the master branch. 2017-03-13 09:39:09 +01:00
Cédric Bonhomme
6ad140bdfb
Management of unicode. It would be perfect to manage ASCII and unicode in the same time (so 8 bits to 32 bits caracters). 2017-03-13 09:34:43 +01:00
Cédric Bonhomme
e803386361
Prepare the new release. 2017-03-10 07:59:39 +01:00
Cédric Bonhomme
a94128e6c6
Improved display of generators 2017-03-10 07:58:12 +01:00
Cédric Bonhomme
9feb1aa72b
Better typing. Fixed a bug with a generator that has been renamed. 2017-03-10 07:22:16 +01:00
Cédric Bonhomme
53cdd28fee
Test the function which transforms a binary file to a printable string. 2017-03-09 10:33:58 +01:00
Cédric Bonhomme
b31d830c76
Remove dead man walking set generator. 2017-03-09 10:02:52 +01:00
Cédric Bonhomme
7c9530aa9d
More tests for the generators. 2017-03-09 08:54:13 +01:00
Cédric Bonhomme
0da9d9882e
Introduces some type hints. 2017-03-09 08:11:12 +01:00
Cédric Bonhomme
3e923e49a8
Prepare new release. 2017-03-08 23:10:11 +01:00
Cédric Bonhomme
adca3f701a
Updated tests. 2017-03-08 23:08:18 +01:00
Cédric Bonhomme
7a5c23ef2f
Fixed #12. More tests to come. 2017-03-08 22:26:12 +01:00
Cédric Bonhomme
091f33c521
Test and improved the generators. 2017-03-01 07:53:10 +01:00
Cédric Bonhomme
b9fad5f044
Various improvements in lsb and lsbset modules. 2017-02-28 07:52:44 +01:00
Cédric Bonhomme
119aecf3a6
Updated .gitignore. 2017-02-28 07:36:09 +01:00
Cédric Bonhomme
c068357153
cut and paste error 2017-02-22 11:46:49 +01:00
Cédric Bonhomme
b71c866cf2
More tests. 2017-02-22 11:45:04 +01:00
Cédric Bonhomme
c65e83b62e
typo 2017-02-22 11:02:00 +01:00
Cédric Bonhomme
4b1b28f5f9
Updated README. 2017-02-22 11:01:02 +01:00
Cédric Bonhomme
e0ed23a225
Improved tests for lsb and lsbset module. 2017-02-22 10:50:03 +01:00
Cédric Bonhomme
7bee922746
added missing file 2017-02-22 09:03:22 +01:00
Cédric Bonhomme
ce28039b1a
install dev requirements for Travis. 2017-02-22 09:01:38 +01:00
Cédric Bonhomme
3bc6afde81
added coveralls badge in the README. 2017-02-22 08:58:56 +01:00
Cédric Bonhomme
997002eded
link travis with coveralls 2017-02-22 08:57:06 +01:00
Cédric Bonhomme
c0669e0f7a
Removed useless write functions. 2017-02-22 08:41:20 +01:00
Cédric Bonhomme
39855a2008
Updated README. 2017-02-22 08:37:53 +01:00
Cédric Bonhomme
07fa54e7d0
Clean comments. 2017-02-22 07:18:46 +01:00
Cédric Bonhomme
316d50b96f
Stop support of Python 2. 2017-02-22 07:16:46 +01:00
Cédric Bonhomme
f922905d10
Clean comments. 2017-02-22 07:15:58 +01:00
Cédric Bonhomme
518729cd31
Typo. 2017-02-21 07:45:00 +01:00
Cédric Bonhomme
42571faaaa
Added a missing dependency in the setup.py file. 2017-02-21 07:41:53 +01:00
Cédric Bonhomme
76fd4a0cbb
Prepare the new release. 2017-02-20 14:19:51 +01:00
Cédric Bonhomme
52290110cf
added crayons in requirements.txt 2017-02-20 14:17:13 +01:00
Cédric Bonhomme
6215c03939
the command line now uses the crayons Python library in order to have colored strings in the terminal. 2017-02-20 14:16:41 +01:00
Cédric Bonhomme
263e287559
clean code related to tools.binary2base64 and tools.base642binary functions. 2017-02-20 12:16:24 +01:00
Cédric Bonhomme
190f790e50
clean... 2017-02-19 23:59:12 +01:00
Cédric Bonhomme
3ff8245729
Updated documentation. 2017-02-18 00:18:47 +01:00
Cédric Bonhomme
6b49f9bd8c
Simply point to the latest release. 2017-02-18 00:06:42 +01:00
Cédric Bonhomme
90230fcc90
Updated some links. 2017-02-18 00:01:47 +01:00
Cédric Bonhomme
3db46a3d8b
Updated README. 2017-02-17 23:05:09 +01:00
Cédric Bonhomme
04e13e0982
Updated the documentation. 2017-02-17 22:36:17 +01:00
Cédric Bonhomme
b96f917c71
Added more spaces between the end of the README and the CHANGELOG file. 2017-02-16 10:39:49 +01:00
Cédric Bonhomme
2011f197cd
New minor release (v0.6.5). 2017-02-16 10:18:43 +01:00
Cédric Bonhomme
f8f0ba22aa
Typo. 2017-02-16 09:26:18 +01:00
Cédric Bonhomme
36476283a4
Test when the data image is coming via byte stream. 2017-02-16 09:25:16 +01:00
Cédric Bonhomme
cf89273c57
Removed useless code. 2017-02-06 10:18:52 +01:00
Cédric Bonhomme
c5ee22e1af
Bumped documentation version number. 2017-02-06 09:33:42 +01:00
Cédric Bonhomme
086c3898b7
Added a command line for the 'red' module. 2017-02-06 09:31:26 +01:00
Cédric Bonhomme
4a45689298
Rearrange a little the sections. 2017-01-30 08:26:04 +01:00
Cédric Bonhomme
f4ebda51d2
Typo. 2017-01-30 08:23:57 +01:00
Cédric Bonhomme
4c2570769d
Typo. 2017-01-30 08:23:38 +01:00
Cédric Bonhomme
a65bce640f
Fix titles in CONTRIBUTING.rst 2017-01-30 08:18:58 +01:00
Cédric Bonhomme
966b4921d1
First draft for the contributions guide. 2017-01-30 08:17:17 +01:00
Cédric Bonhomme
91e41b04ac
Added link to saythanks.io in the README and in the documentation. 2017-01-30 07:42:07 +01:00
Cédric Bonhomme
5890a6ba72
Updated README. 2017-01-29 21:37:40 +01:00
Cédric Bonhomme
3da354f8f1
Updated README. 2017-01-29 21:35:18 +01:00
Cédric Bonhomme
a55bf4bd09
Prepare the new release. 2017-01-29 21:20:02 +01:00
Cédric Bonhomme
be9ea0f76d
Updated README. 2017-01-28 00:07:59 +01:00
Cédric Bonhomme
1b6f19f304
Added CONTRIBUTORS.rst. 2017-01-28 00:05:46 +01:00
Cédric Bonhomme
3e31b38294
Added support for transparent PNGs in the lsbset module. 2017-01-27 23:47:34 +01:00
Cédric Bonhomme
556a24341e Merge pull request #10 from andyroberts/support-lsb-rgba
Add support for transparent PNGs
2017-01-27 23:15:08 +01:00
Andy Roberts
eea3f7ad6c Add support for transparent PNGs 2017-01-27 17:19:21 +00:00
Cédric Bonhomme
673f7b5bc4
Prepare the new minor release (bug fix). 2017-01-19 07:22:51 +01:00
Cédric Bonhomme
b8c8237792 Merge pull request #9 from andyroberts/exif-image-format-on-save
Exif image format on save
2017-01-18 22:40:29 +01:00
Andy Roberts
03a3835e70 Change test input into binrary IO stream 2017-01-18 20:51:26 +00:00
Andy Roberts
486358de69 Support hide/reveal for image byte objects 2017-01-18 20:39:37 +00:00
Cédric Bonhomme
5b362403db
Typo. 2016-08-26 10:07:47 +02:00
Cédric Bonhomme
cef74dd57e
New command line interface for the steganalysis by statistics. 2016-08-26 08:28:06 +02:00
Cédric Bonhomme
22a260f5b4
Updated README. 2016-08-25 10:37:42 +02:00
Cédric Bonhomme
0e0a5e10d2
Updated README. 2016-08-25 10:35:05 +02:00
Cédric Bonhomme
3531769483
Updated module version number. 2016-08-25 08:31:40 +02:00
Cédric Bonhomme
c6eb521be5
Removed no more used sample files in the tests folder. 2016-08-25 08:15:36 +02:00
Cédric Bonhomme
66c6f2b3a3
Reorganization of the steganalysis sub-module. 2016-08-25 08:13:05 +02:00
Cédric Bonhomme
bc741a1c4e
Updated README. 2016-08-04 22:24:40 +02:00
Cédric Bonhomme
e440ffff14
Updated CHANGELOG. 2016-08-04 22:06:38 +02:00
Cédric Bonhomme
f61e885adb
Updated CHANGELOG. 2016-08-04 22:04:02 +02:00
Cédric Bonhomme
46ee52f8e1
Updated documentation appropriately. 2016-08-04 21:50:32 +02:00
Cédric Bonhomme
0d1c5cc4c1
Update steganalysis functions. 2016-08-04 21:30:51 +02:00
Cédric Bonhomme
d3d03c2f74
'hide' and 'reveal' are now sub-parameter of the command line. 2016-08-04 21:24:39 +02:00
Cédric Bonhomme
1879197624
Starting to improve the command line. 2016-08-04 13:22:23 +02:00
Cédric Bonhomme
a54909f4ad
Prepare the new release 0.5.5 (bugfix). 2016-08-03 23:05:13 +02:00
Cédric Bonhomme
b080f778be Merge pull request #7 from nejdetckenobi/master
Padding for `base64.b64decodestring` changed into `b'==='`.
2016-08-03 17:54:40 +02:00
nejdet
d3fbe07c90 Padding for base64.b64decodestring changed into b'==='.
Fixes #6
2016-08-03 17:23:59 -04:00
Cédric Bonhomme
3279b0c52a
Updated tests for lsbset. 2016-05-26 07:35:23 +02:00
Cédric Bonhomme
031e1ca84c
Updated tests for lsb 2016-05-26 07:32:35 +02:00
Cédric Bonhomme
fef2040fca
Python 2 compatibility for exifHeader. 2016-05-26 07:18:56 +02:00
Cédric Bonhomme
1def5672ab
Updated documentation. 2016-05-22 22:30:32 +02:00
Cédric Bonhomme
f7d354a9c4
Bumped version numberd and updated the CHANGELOG. 2016-05-22 15:38:36 +02:00
Cédric Bonhomme
4c2fb50c68
Displays a message when the generator is unknown. 2016-05-22 15:28:13 +02:00
Cédric Bonhomme
f8668a978e
The generator provided to lsbset.hide() lsbset.reveal() is now a function. Closes #5. 2016-05-22 15:23:54 +02:00
Cédric Bonhomme
a0f6f24d7c
Check if the generator is implemented in Stegano. 2016-05-22 00:13:29 +02:00
Cédric Bonhomme
d850bbd8a0
Misc improvements. 2016-05-21 22:20:05 +02:00
Cédric Bonhomme
6cd22dfe72
Closes #4. 2016-05-21 16:53:43 +02:00
Cédric Bonhomme
eeb8132b4b
Bumped version number. 2016-05-19 22:38:07 +02:00
Cédric Bonhomme
e4f9a08cd7
Moved sample files and updated documentation. 2016-05-19 22:14:47 +02:00
Cédric Bonhomme
872a5546fc
Reorganization of all modules. 2016-05-19 21:32:15 +02:00
Cédric Bonhomme
e0bed8ba52
Updated README. 2016-05-19 07:33:24 +02:00
Cédric Bonhomme
c9dda3da00
Improved 'red' module and added unit tests for the 'red' module. 2016-05-19 07:30:50 +02:00
Cédric Bonhomme
68611ad03d
Removed useless example. 2016-05-19 07:08:38 +02:00
Cédric Bonhomme
4749adc5f7
Renamed basic.py to red.py. 2016-05-19 07:08:07 +02:00
Cédric Bonhomme
46c3f6d072
Updated CHANGELOG. 2016-05-18 09:18:00 +02:00
Cédric Bonhomme
bee4aa3bc8
Updated ReadTheDocs url. 2016-05-18 09:07:04 +02:00
Cédric Bonhomme
ac66a9b471
Updated README. 2016-05-18 09:05:18 +02:00
Cédric Bonhomme
a54a8774bd
Reorganization of the documentation. 2016-05-18 08:44:47 +02:00
Cédric Bonhomme
6fd797896d
Updated setup.py according to the recent changes. 2016-05-18 07:49:33 +02:00
Cédric Bonhomme
fd202bf42c
Added test image. 2016-05-18 07:47:37 +02:00
Cédric Bonhomme
f9b33b33ef
Updated tests of the exifHeader module and some improvements for this module. 2016-05-18 07:47:13 +02:00
Cédric Bonhomme
064c87146c
Typo. 2016-05-18 07:17:58 +02:00
Cédric Bonhomme
e84ddc4da7
Updated .gitignore. 2016-05-18 07:17:16 +02:00
Cédric Bonhomme
ed3e8560a5
exifHeader is now using another exif Python module. 2016-05-17 15:27:49 +02:00
Cédric Bonhomme
23abfcf2d7
Minor updates in the documentation 2016-05-17 11:20:09 +02:00
Cédric Bonhomme
f5d4a4ca41
Added tests for exifHeader module. The reveal function of the exifHeader module now returns the result instead of simply printing the clear message. 2016-05-17 09:59:12 +02:00
Cédric Bonhomme
38587c7839
The user now has the possibility to not store the copyright in the image. 2016-05-17 08:37:35 +02:00
Cédric Bonhomme
d869dadcc0 Merge pull request #3 from papercapp/master
add optional auto_convert_rgb parameter to slsb.hide/slsbset.hide; ad…
2016-04-21 22:11:15 +02:00
panni
a38f23509b add optional auto_convert_rgb parameter to slsb.hide/slsbset.hide; add .idea to .gitignore 2016-04-21 15:23:04 +02:00
Cédric Bonhomme
75b41a2e2e
Bumped version number 2016-04-16 14:15:32 +02:00
Cédric Bonhomme
7a40af8f1b
Updated CHANGELOG. 2016-04-16 14:13:14 +02:00
Cédric Bonhomme
1a25273a9f
Tests with coverage. 2016-04-13 22:52:23 +02:00
Cédric Bonhomme
75769e5bf7
Renamed test class. 2016-04-13 22:31:58 +02:00
Cédric Bonhomme
dc3cdd8f62
Added link to the Travis result in the readme. 2016-04-13 08:41:54 +02:00
Cédric Bonhomme
290e679ea0
Added Travis file. 2016-04-13 08:37:15 +02:00
Cédric Bonhomme
0d6748e737
Added some tests for slsbset module. 2016-04-13 08:25:27 +02:00
Cédric Bonhomme
655d211dc0
Bug fix: forgot to write the results in a file. 2016-04-13 08:11:12 +02:00
Cédric Bonhomme
f47cf3ae51
Added some unit tests for the 'slsb' module. 2016-04-12 23:21:26 +02:00
Cédric Bonhomme
96d01439ea
Prepare the release 0.5. 2016-03-18 07:07:22 +01:00
Cédric Bonhomme
40c1293df4
Default answer is 'Y'. 2016-03-14 08:19:30 +01:00
Cédric Bonhomme
eea4c4de8e
fix the compatibility problem between 'input' and 'raw_input' for Python 2 and 3. 2016-03-13 18:19:43 +01:00
Cédric Bonhomme
1ff2f9c7c0
Closes #2. 2016-03-13 00:34:46 +01:00
Cédric Bonhomme
f582d47e6c
Updated header informations. 2016-03-12 23:58:19 +01:00
Cédric Bonhomme
ed7877ec73
Closes #1 2016-03-12 23:57:08 +01:00
Cédric Bonhomme
b5217d4f0e
Typo. 2015-12-24 09:59:34 +01:00
Cédric Bonhomme
438c7b5189
typo in the CHANGELOG. 2015-12-23 22:35:30 +01:00
Cédric Bonhomme
9158e6e7e7
releases 0.4.5 2015-12-23 22:21:09 +01:00
Cédric Bonhomme
5e00dde69f
Updated CHANGELOG. 2015-12-23 21:42:30 +01:00
Cédric Bonhomme
07f50a8fc0
Updated CHANGELOG 2015-12-23 21:36:12 +01:00
Cédric Bonhomme
4aadc6bbce
renamed 2015-12-23 21:35:17 +01:00
Cédric Bonhomme
5f4a8b4d34
Updated CHANGELOG 2015-12-23 21:34:58 +01:00
Cédric Bonhomme
ff394da9f8
Removed .hgignore 2015-12-23 21:33:25 +01:00
Cédric Bonhomme
979c6277b0
Updated Python headers files. Added .gitignore. 2015-12-23 21:33:05 +01:00
Cédric Bonhomme
173d65f8aa
Updated README. 2015-12-23 21:02:05 +01:00
Cédric Bonhomme
ff379c256b
Updated README. 2015-12-23 21:01:57 +01:00
Cédric Bonhomme
1297fa84ec
Updated README. 2015-12-23 20:59:39 +01:00
Cédric Bonhomme
9617f8c2fd
Updated link to the HomePage in setup.py. 2015-12-23 13:30:39 +01:00
Cédric Bonhomme
a0aea7f573
Updated link to the documentation. 2015-12-23 13:23:32 +01:00
Cédric Bonhomme
8ea19492c9
Updated documentation and README. 2015-12-23 13:21:46 +01:00
Cédric Bonhomme
fd44b9aca9 Updated CHANGELOG. 2015-10-06 09:29:29 +02:00
Cédric Bonhomme
1fa158e806 Cosmethic changes for PyPI. 2015-10-06 08:11:01 +02:00
Cédric Bonhomme
c9a3c7605c Updated MANIFEST file. 2015-10-06 08:01:30 +02:00
Cédric Bonhomme
8a152e8e88 Added tag v0.4.3 for changeset 8b82ab4fd0d6 2015-10-06 07:55:47 +02:00
94 changed files with 6382 additions and 2219 deletions

16
.editorconfig Normal file
View 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
View 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=18 --ignore=E203 --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
View 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

38
.gitignore vendored Normal file
View file

@ -0,0 +1,38 @@
# use glob syntax
syntax: glob
*.elc
*.pyc
*~
*.db
# Virtualenv
venv
build
# setuptools
build/*
stegano.egg-info/
dist/*
# tests
.coverage
.mypy_cache/
.cache/
# sphinx
docs/_build
# Emacs
eproject.cfg
# Temporary files (vim backups)
*.swp
.idea/
# Log files:
*.log
# Vagrant:
.vagrant/

View file

@ -1,16 +0,0 @@
# use glob syntax
syntax: glob
*.elc
*.pyc
*~
*.png
*.db
# Temporary files (vim backups)
*.swp
build/*
Stegano.egg-info/*
dist/*

36
.pre-commit-config.yaml Normal file
View 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", "--ignore=E203"]
- 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.9.0
hooks:
- id: pip-audit

22
.readthedocs.yaml Normal file
View 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

View file

@ -1,14 +1,349 @@
=============== ## Release History
Release History
===============
0.4.3 (2015-10-06) ### 2.0.0 (2025-06-22)
------------------
* bug fixes for Python 3; - Added functions for hiding/revealing messages in PCM encoded .wav files.
* bug fixes in the scripts installed in */usr/local/bin*. ([#54](https://github.com/cedricbonhomme/Stegano/pull/54))
- Improved typing.
- Updated dependencies.
0.4.2 (2015-10-05)
------------------
* first stable release on PypI. ### 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.

23
CONTRIBUTORS.md Normal file
View file

@ -0,0 +1,23 @@
## Owner
- Cédric Bonhomme <cedric@cedricbonhomme.org>
## Contributors
- Alexander Treml - https://github.com/AlexanderTreml
- 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
- Mickaël Schoentgen <mschoentgen@nuxeo.com>
- Nejdet Çağdaş Yücesoy <nejdetyucesoy@gmail.com>
- panni <panni@fragstore.net>
- Peter Justin <peter@peterjustin.me>
- thundersparkf - https://github.com/thundersparkf
And thank you to the testers!

View file

@ -1,13 +0,0 @@
#documentation
recursive-include docs *
#example files
recursive-include examples *
# binary files
recursive-include bin *
#Misc
include COPYING
include README.md
include requirements.txt

125
README.md
View file

@ -1,54 +1,115 @@
Stéganô # Stegano
=======
A Python Steganography module. [![Workflow](https://github.com/cedricbonhomme/Stegano/workflows/Python%20application/badge.svg?style=flat-square)](https://github.com/cedricbonhomme/Stegano/actions?query=workflow%3A%22Python+application%22)
[Stegano](https://github.com/cedricbonhomme/Stegano), a pure Python Steganography
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 ## Installation
------------
$ sudo pip install Stegano
Use Stéganô as a library in your Python program ```bash
----------------------------------------------- $ poetry install stegano
```
If you want to use Stéganô in your Python program you just have to import the 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: appropriate steganography technique. For example:
```python
>>> from stegano import slsb >>> from stegano import lsb
>>> secret = slsb.hide("./pictures/Lenna.png", "Hello Workd") >>> secret = lsb.hide("./tests/sample-files/Lenna.png", "Hello World")
>>> secret.save("./Lenna-secret.png") >>> secret.save("./Lenna-secret.png")
>>>
>>> clear_message = lsb.reveal("./Lenna-secret.png")
```
Use Stéganô as a program ## Use Stegano as a command line tool
------------------------
In addition you can use Stéganô as a program. ### Hide and reveal a message
Example: ```bash
$ stegano-lsb hide -i ./tests/sample-files/Lenna.png -m "Secret Message" -o Lena1.png
$ slsb --hide -i ../examples/pictures/Lenna.png -o Lena1.png -m "Secret Message" $ stegano-lsb reveal -i Lena1.png
Secret Message
Another example (hide the message with Sieve of Eratosthenes): ```
$ slsb-set --hide -i ../examples/pictures/Lenna.png -o Lena2.png --generator eratosthenes -m 'Secret Message'
Examples ### Hide the message with the Sieve of Eratosthenes
--------
There are some examples in the folder *examples*. ```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.
Turorial ## Running the tests
--------
A [tutorial](https://stegano.readthedocs.org/en/latest/tutorial) is available. ```bash
$ python -m unittest discover -v
```
Running the static type checker:
```bash
$ mypy stegano
```
Contact ## Contributions
-------
[My home page](https://www.cedricbonhomme.org). 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:
[![GitHub Sponsors](https://img.shields.io/github/sponsors/cedricbonhomme)](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.

View file

@ -1,85 +0,0 @@
#!/usr/bin/env python
#-*- coding: utf-8 -*-
# Stéganô - Stéganô is a basic Python Steganography module.
# Copyright (C) 2010-2011 Cédric Bonhomme - http://cedricbonhomme.org/
#
# For more information : http://bitbucket.org/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: 2011/04/06 $"
__license__ = "GPLv3"
try:
from stegano import slsb
except:
print("Install Stegano: sudo pip install Stegano")
from stegano import tools
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.")
# Original image
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.")
# Non binary secret message to hide
parser.add_option("-m", "--secret-message", dest="secret_message",
help="Your secret message to hide (non binary).")
# Binary secret to hide (OGG, executable, etc.)
parser.add_option("-f", "--secret-file", dest="secret_file",
help="Your secret to hide (Text or any binary file).")
# Output for the binary binary secret.
parser.add_option("-b", "--binary", dest="secret_binary",
help="Output for the binary secret (Text or any binary file).")
parser.set_defaults(input_image_file = './pictures/Lenna.png',
output_image_file = './pictures/Lenna_enc.png',
secret_message = '', secret_file = '', secret_binary = "")
(options, args) = parser.parse_args()
if options.hide:
if options.secret_message != "" and options.secret_file == "":
secret = options.secret_message
elif options.secret_message == "" and options.secret_file != "":
secret = tools.binary2base64(options.secret_file)
img_encoded = slsb.hide(options.input_image_file, secret)
try:
img_encoded.save(options.output_image_file)
except Exception as e:
# If hide() returns an error (Too long message).
print(e)
elif options.reveal:
secret = slsb.reveal(options.input_image_file)
if options.secret_binary != "":
data = tools.base642binary(secret)
with open(options.secret_binary, "w") as f:
f.write(data)
else:
print(secret)

View file

@ -1,95 +0,0 @@
#!/usr/bin/env python
#-*- coding: utf-8 -*-
# Stéganô - Stéganô is a basic Python Steganography module.
# Copyright (C) 2010-2011 Cédric Bonhomme - http://cedricbonhomme.org/
#
# For more information : http://bitbucket.org/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: 2011/12/29 $"
__license__ = "GPLv3"
try:
from stegano import slsbset
except:
print("Install stegano: sudo pip install Stegano")
from stegano import tools
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.")
# Original image
parser.add_option("-i", "--input", dest="input_image_file",
help="Input image file.")
# Generator
parser.add_option("-g", "--generator", dest="generator_function",
help="Generator")
# Image containing the secret
parser.add_option("-o", "--output", dest="output_image_file",
help="Output image containing the secret.")
# Non binary secret message to hide
parser.add_option("-m", "--secret-message", dest="secret_message",
help="Your secret message to hide (non binary).")
# Binary secret to hide (OGG, executable, etc.)
parser.add_option("-f", "--secret-file", dest="secret_file",
help="Your secret to hide (Text or any binary file).")
# Output for the binary binary secret.
parser.add_option("-b", "--binary", dest="secret_binary",
help="Output for the binary secret (Text or any binary file).")
parser.set_defaults(input_image_file = './pictures/Lenna.png',
generator_function = 'fermat',
output_image_file = './pictures/Lenna_enc.png',
secret_message = '', secret_file = '', secret_binary = "")
(options, args) = parser.parse_args()
if options.hide:
if options.secret_message != "" and options.secret_file == "":
secret = options.secret_message
elif options.secret_message == "" and options.secret_file != "":
secret = tools.binary2base64(options.secret_file)
img_encoded = slsbset.hide(options.input_image_file, secret, options.generator_function)
try:
img_encoded.save(options.output_image_file)
except Exception as e:
# If hide() returns an error (Too long message).
print(e)
elif options.reveal:
try:
secret = slsbset.reveal(options.input_image_file, options.generator_function)
except IndexError:
print("Impossible to detect message.")
exit(0)
if options.secret_binary != "":
data = tools.base642binary(secret)
with open(options.secret_binary, "w") as f:
f.write(data)
else:
print(secret)

View file

@ -1,46 +0,0 @@
#!/usr/bin/env python
#-*- coding: utf-8 -*-
# Stéganô - Stéganô is a basic Python Steganography module.
# Copyright (C) 2010-2011 Cédric Bonhomme - http://cedricbonhomme.org/
#
# For more information : http://bitbucket.org/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: 2011/12/29 $"
__license__ = "GPLv3"
try:
from stegano import steganalysisParity
except:
print("Install Stegano: sudo pip install Stegano")
from PIL import Image
from optparse import OptionParser
parser = OptionParser()
parser.add_option("-i", "--input", dest="input_image_file",
help="Image file")
parser.add_option("-o", "--output", dest="output_image_file",
help="Image file")
parser.set_defaults(input_image_file = './pictures/Lenna.png',
output_image_file = './pictures/Lenna_steganalysed.png')
(options, args) = parser.parse_args()
input_image_file = Image.open(options.input_image_file)
output_image = steganalysisParity.steganalyse(input_image_file)
output_image.save(options.output_image_file)

View file

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# #
# Stéganô documentation build configuration file, created by # Stéganô documentation build configuration file, created by
# sphinx-quickstart on Wed Jul 25 13:33:39 2012. # sphinx-quickstart on Wed Jul 25 13:33:39 2012.
@ -10,47 +9,42 @@
# #
# All configuration values have a default; values that are commented out # All configuration values have a default; values that are commented out
# serve to show the default. # serve to show the default.
import sys, os
# If extensions (or modules to document with autodoc) are in another directory, # If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the # add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here. # documentation root, use os.path.abspath to make it absolute, like shown here.
# sys.path.insert(0, os.path.abspath('.')) # sys.path.insert(0, os.path.abspath('.'))
# -- General configuration ----------------------------------------------------- # -- General configuration -----------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here. # If your documentation needs a minimal Sphinx version, state it here.
# needs_sphinx = '1.0' # needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be extensions # Add any Sphinx extension module names here, as strings. They can be extensions
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. # coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = [] extensions = []
# Add any paths that contain templates here, relative to this directory. # Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates'] templates_path = ["_templates"]
# The suffix of source filenames. # The suffix of source filenames.
source_suffix = '.rst' source_suffix = ".rst"
# The encoding of source files. # The encoding of source files.
# source_encoding = 'utf-8-sig' # source_encoding = 'utf-8-sig'
# The master toctree document. # The master toctree document.
master_doc = 'index' master_doc = "index"
# General information about the project. # General information about the project.
project = u'Stéganô' project = "Stegano"
copyright = u'2012, Cédric Bonhomme' 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 # The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the # |version| and |release|, also used in various other places throughout the
# built documents. # built documents.
# #
# The short X.Y version. # The short X.Y version.
version = '0.4' version = "0.11"
# The full version, including alpha/beta/rc tags. # The full version, including alpha/beta/rc tags.
release = '0.4' release = "0.11.0"
# The language for content autogenerated by Sphinx. Refer to documentation # The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages. # for a list of supported languages.
@ -64,7 +58,7 @@ release = '0.4'
# List of patterns, relative to source directory, that match files and # List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files. # 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. # The reST default role (used for this markup: `text`) to use for all documents.
# default_role = None # default_role = None
@ -80,9 +74,6 @@ exclude_patterns = ['_build']
# output. They are ignored by default. # output. They are ignored by default.
# show_authors = False # show_authors = False
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
# A list of ignored prefixes for module index sorting. # A list of ignored prefixes for module index sorting.
# modindex_common_prefix = [] # modindex_common_prefix = []
@ -91,7 +82,7 @@ pygments_style = 'sphinx'
# The theme to use for HTML and HTML Help pages. See the documentation for # The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes. # a list of builtin themes.
html_theme = 'default' html_theme = "sphinx_rtd_theme"
# Theme options are theme-specific and customize the look and feel of a 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 # further. For a list of options available for each theme, see the
@ -120,7 +111,7 @@ html_theme = 'default'
# Add any paths that contain custom static files (such as style sheets) here, # 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, # relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css". # 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, # If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format. # using the given strftime format.
@ -140,9 +131,6 @@ html_static_path = ['_static']
# If false, no module index is generated. # If false, no module index is generated.
# html_domain_indices = True # html_domain_indices = True
# If false, no index is generated.
#html_use_index = True
# If true, the index is split into individual pages for each letter. # If true, the index is split into individual pages for each letter.
# html_split_index = False # html_split_index = False
@ -163,80 +151,20 @@ html_static_path = ['_static']
# This is the file name suffix for HTML files (e.g. ".xhtml"). # This is the file name suffix for HTML files (e.g. ".xhtml").
# html_file_suffix = None # html_file_suffix = None
# Output file base name for HTML help builder.
htmlhelp_basename = 'Stgandoc'
# -- Options for LaTeX output -------------------------------------------------- # -- Options for LaTeX output --------------------------------------------------
latex_elements = { latex_engine = "pdflatex"
# The paper size ('letterpaper' or 'a4paper').
#'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
#'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
#'preamble': '',
}
# Grouping the document tree into LaTeX files. List of tuples # Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass [howto/manual]). # (source start file, target name, title, author, documentclass [howto/manual]).
latex_documents = [ latex_documents = [
('index', 'Stgan.tex', u'Stéganô Documentation', ("index", "Stgan.tex", "Stegano Documentation", "Cédric Bonhomme", "howto"),
u'Cédric Bonhomme', 'manual'),
] ]
# The name of an image file (relative to this directory) to place at the top of latex_show_urls = True
# the title page. latex_show_pagerefs = True
#latex_logo = None
# For "manual" documents, if this is true, then toplevel headings are parts, ADDITIONAL_PREAMBLE = r"""
# not chapters. \setcounter{tocdepth}{3}
#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'

View file

@ -3,84 +3,78 @@
You can adapt this file completely to your liking, but it should at least You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive. contain the root `toctree` directive.
Welcome to Stéganô's documentation! Presentation
===================================
.. toctree::
:maxdepth: 2
Stéganô is a 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 message,
without encryption. Indeed steganography is often used with cryptography.
The advantage of steganography, over cryptography alone, is that messages do not attract
attention to themselves. If you are interested in cryptography have a look at my project pySecret.
Download Stéganô
================
You can clone the source code of Stéganô_ :
.. code-block:: bash
$ hg clone https://bitbucket.org/cedricbonhomme/stegano/
More information about how to install Stéganô in the :doc:`tutorial </tutorial>`.
Requirements
============ ============
- Python_ >= 3.2 (tested with Python 3.3.1); Stegano_ is a pure Python steganography_ module.
- `Pillow`_ (friendly fork of Python Imaging Library).
Methods of hiding 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.
For the moment, 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 red portion of a pixel to hide ASCII messages;
- using the `Least Significant Bit <http://en.wikipedia.org/wiki/Least_significant_bit>`_ (LSB) technique; - using the `Least Significant Bit <http://en.wikipedia.org/wiki/Least_significant_bit>`_ (LSB) technique;
- using the LSB technique with sets based on generators (Sieve for Eratosthenes, Fermat, Mersenne numbers, etc.); - using the LSB technique with sets based on generators (Sieve for Eratosthenes, Fermat, Mersenne numbers, etc.);
- using the description field of the image (JPEG). - using the description field of the image (JPEG and TIFF).
Moreover some methods of steganalysis_ are provided: Moreover some methods of steganalysis_ are provided:
- steganalysis of LSB encoding in color images; - steganalysis of LSB encoding in color images;
- statistical steganalysis. - statistical steganalysis.
You can also use Stegano through a `Web service <https://github.com/cedricbonhomme/stegano-web>`_.
Not all functionalities of Stegano are covered.
Turorial Requirements
============
- Python_ 3;
- `Pillow`_;
- `piexif`_.
Tutorial
======== ========
More information available at the :doc:`tutorial </tutorial>` page .. toctree::
:maxdepth: 2
installation
module
software
steganalysis
You can have a look at the
`unit tests <https://github.com/cedricbonhomme/Stegano/tree/master/tests>`_.
License License
======= =======
Stéganô is under GPL v3 license. Stegano_ is under GPL v3 license.
Donation 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>`_.
Contact Contact
======= =======
`My home page <http://cedricbonhomme.org>`_ `My home page <https://www.cedricbonhomme.org>`_
Indices and tables
==================
* :ref:`genindex` .. _Python: https://www.python.org
* :ref:`modindex` .. _Stegano: https://github.com/cedricbonhomme/Stegano
* :ref:`search`
.. _Python: http://python.org/
.. _Stéganô: https://bitbucket.org/cedricbonhomme/stegano/
.. _`Pillow`: https://pypi.python.org/pypi/Pillow .. _`Pillow`: https://pypi.python.org/pypi/Pillow
.. _`piexif`: https://pypi.python.org/pypi/piexif
.. _steganography: http://en.wikipedia.org/wiki/Steganography .. _steganography: http://en.wikipedia.org/wiki/Steganography
.. _steganalysis: http://en.wikipedia.org/wiki/Steganalysis .. _steganalysis: http://en.wikipedia.org/wiki/Steganalysis

15
docs/installation.rst Normal file
View file

@ -0,0 +1,15 @@
Installation
============
.. code-block:: bash
$ poetry install Stegano
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

120
docs/module.rst Normal file
View file

@ -0,0 +1,120 @@
Using Stegano as a Python module
================================
You can find more examples in the
`unit tests directory <https://github.com/cedricbonhomme/Stegano/tree/master/tests>`_.
LSB method
----------
.. code-block:: python
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!")
>>> secret.save("./Lenna-secret.png")
>>> print(lsb.reveal("./Lenna-secret.png"))
Hello world!
LSB method with sets
--------------------
Sets are used in order to select the pixels where the message will be hidden.
.. code-block:: python
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
>>> from stegano.lsb import generators
# Hide a secret with the Sieve of Eratosthenes
>>> secret_message = "Hello World!"
>>> 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 = lsb.reveal("./image.png", generators.fibonacci())
Traceback (most recent call last):
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 = lsb.reveal("./image.png", generators.eratosthenes())
>>> message
'Hello World!'
>>> # Generators available
>>> import inspect
>>> all_generators = inspect.getmembers(generators, inspect.isfunction)
>>> for generator in all_generators:
... print(generator[0], generator[1].__doc__)
...
Dead_Man_Walking None
OEIS_A000217
http://oeis.org/A000217
Triangular numbers: a(n) = C(n+1,2) = n(n+1)/2 = 0+1+2+...+n.
ackermann
Ackermann number.
carmichael None
eratosthenes
Generate the prime numbers with the sieve of Eratosthenes.
eratosthenes_composite
Generate the composite numbers with the sieve of Eratosthenes.
fermat
Generate the n-th Fermat Number.
fibonacci
A generator for Fibonacci numbers, goes to next number in series on each call.
This generator start at 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765, 10946, ...
See: http://oeis.org/A000045
identity
f(x) = x
log_gen
Logarithmic generator.
mersenne
Generate 2^n-1.
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
------------------------------
For JPEG and TIFF images.
.. code-block:: python
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",
"./image.jpg", secret_message="Hello world!")
>>> print(exifHeader.reveal("./image.jpg"))

2
docs/requirements.txt Normal file
View file

@ -0,0 +1,2 @@
sphinx
sphinx_rtd_theme

239
docs/software.rst Normal file
View file

@ -0,0 +1,239 @@
Using Stegano in command line
=============================
The command ``stegano-lsb``
^^^^^^^^^^^^^^^^^^^^^^^^^^^
Hide and reveal a message with the LSB method.
Display help
------------
.. code-block:: bash
$ stegano-lsb --help
usage: stegano-lsb [-h] {hide,reveal,list-generators} ...
positional arguments:
{hide,reveal,list-generators}
sub-command help
hide hide help
reveal reveal help
list-generators list-generators help
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}] [-g [GENERATOR_FUNCTION ...]] [-s SHIFT] (-m SECRET_MESSAGE | -f SECRET_FILE) -o OUTPUT_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.
-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}] [-g [GENERATOR_FUNCTION ...]] [-s SHIFT] [-o SECRET_BINARY]
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.
-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
------------------------------
.. code-block:: bash
$ stegano-lsb hide -i ./tests/sample-files/Lenna.png -m 'Hello World!' -o ./Lenna_enc.png
$ stegano-lsb reveal -i ./Lenna_enc.png
Hello World!
Specify an encoding
-------------------
.. code-block:: bash
$ stegano-lsb hide -i ./tests/sample-files/Lenna.png -m 'I love 🍕 and 🍫.' -e UTF-32LE -o ./Lenna_enc.png
$ stegano-lsb reveal -i ./Lenna_enc.png
I love 🍕 and 🍫.
The default encoding is UTF-8.
Hide and reveal a binary file
-----------------------------
.. code-block:: bash
$ wget http://www.gnu.org/music/free-software-song.ogg
$ stegano-lsb hide -i ./tests/sample-files/Montenach.png -f ./free-software-song.ogg -o ./Montenach_enc.png
$ rm free-software-song.ogg
$ stegano-lsb reveal -i ./Montenach_enc.png -o ./song.ogg
Sets are used in order to select the pixels where the message will be hidden.
Hide and reveal a text message with set
---------------------------------------
.. code-block:: bash
# Hide the message with the Sieve of Eratosthenes
$ 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 reveal --generator mersenne -i ./surprise.png
# Try to reveal with fermat numbers
$ stegano-lsb reveal --generator fermat -i ./surprise.png
# Try to reveal with carmichael numbers
$ stegano-lsb reveal --generator carmichael -i ./surprise.png
# Try to reveal with Sieve of Eratosthenes
$ 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``:
.. code-block:: bash
$ 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
------------------------------
.. code-block:: bash
$ stegano-lsb list-generators
Generator id:
ackermann
Desciption:
Ackermann number.
Generator id:
ackermann_naive
Desciption:
Ackermann number.
Generator id:
carmichael
Desciption:
Composite numbers n such that a^(n-1) == 1 (mod n) for every a coprime
to n.
https://oeis.org/A002997
Generator id:
composite
Desciption:
Generate the composite numbers using the sieve of Eratosthenes.
https://oeis.org/A002808
Generator id:
eratosthenes
Desciption:
Generate the prime numbers with the sieve of Eratosthenes.
https://oeis.org/A000040
Generator id:
fermat
Desciption:
Generate the n-th Fermat Number.
https://oeis.org/A000215
Generator id:
fibonacci
Desciption:
Generate the sequence of Fibonacci.
https://oeis.org/A000045
Generator id:
identity
Desciption:
f(x) = x
Generator id:
log_gen
Desciption:
Logarithmic generator.
Generator id:
mersenne
Desciption:
Generate 2^p - 1, where p is prime.
https://oeis.org/A001348
Generator id:
triangular_numbers
Desciption:
Triangular numbers: a(n) = C(n+1,2) = n(n+1)/2 = 0+1+2+...+n.
http://oeis.org/A000217
The command ``stegano-red``
^^^^^^^^^^^^^^^^^^^^^^^^^^^
Hide and reveal a text message with the red portion of a pixel.
Display help
------------
.. code-block:: bash
$ stegano-red hide --help
usage: stegano-red hide [-h] [-i INPUT_IMAGE_FILE] [-m SECRET_MESSAGE]
[-o OUTPUT_IMAGE_FILE]
optional arguments:
-h, --help show this help message and exit
-i INPUT_IMAGE_FILE, --input INPUT_IMAGE_FILE
Image file
-m SECRET_MESSAGE Your secret message to hide (non binary).
-o OUTPUT_IMAGE_FILE, --output OUTPUT_IMAGE_FILE
Image file
Hide and reveal a text message
------------------------------
.. code-block:: bash
$ stegano-red hide -i ./tests/sample-files/Lenna.png -m 'Basic steganography technique.' -o ~/Lenna1.png
$ stegano-red reveal -i ~/Lenna1.png
Basic steganography technique.

19
docs/steganalysis.rst Normal file
View file

@ -0,0 +1,19 @@
Steganalysis
============
Parity
------
.. code-block:: bash
# Hide the message with Sieve of Eratosthenes
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
# Steganalysis of the secret photo
stegano-steganalysis-parity -i ./surprise.png -o ./surprise_st_secret.png
# Reveal with Sieve of Eratosthenes
stegano-lsb reveal -i ./surprise.png --generator eratosthenes

View file

@ -1,95 +0,0 @@
Getting Stéganô
===============
.. code-block:: bash
$ hg clone https://bitbucket.org/cedricbonhomme/stegano
$ cd stegano/
$ chmod u+x *.py # if you want to use Stéganô in command line
Installation
============
.. code-block:: bash
$ python setup.py install
Now you will be able to use Stéganô in your Python program.
Using Stéganô as a Python module
================================
.. code-block:: python
Python 2.7 (r27:82500, Jul 5 2010, 10:14:47)
[GCC 4.3.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> from stegano import slsb
>>> secret = slsb.hide("./pictures/Lenna.png", "Hello world!")
>>> secret.save("./Lenna-secret.png")
>>> slsb.reveal("./Lenna-secret.png")
Hello world!
Using Stéganô in command line for your scripts
==============================================
Display help
------------
.. code-block:: bash
$ ./slsb.py --help
Usage: slsb.py [options]
Options:
--version show program's version number and exit
-h, --help show this help message and exit
--hide Hides a message in an image.
--reveal Reveals the message hided in an image.
-i INPUT_IMAGE_FILE, --input=INPUT_IMAGE_FILE
Input image file.
-o OUTPUT_IMAGE_FILE, --output=OUTPUT_IMAGE_FILE
Output image containing the secret.
-m SECRET_MESSAGE, --secret-message=SECRET_MESSAGE
Your secret message to hide (non binary).
-f SECRET_FILE, --secret-file=SECRET_FILE
Your secret to hide (Text or any binary file).
-b SECRET_BINARY, --binary=SECRET_BINARY
Output for the binary secret (Text or any binary
file).
Hide and reveal a text message
------------------------------
.. code-block:: bash
$ ./slsb.py --hide -i ./pictures/Lenna.png -o ./pictures/Lenna_enc.png -m HelloWorld!
$ ./slsb.py --reveal -i ./pictures/Lenna_enc.png
HelloWorld!
Hide and reveal a binary file
-----------------------------
.. code-block:: bash
$ wget http://www.gnu.org/music/free-software-song.ogg
$ ./slsb.py --hide -i ./pictures/Montenach.png -o ./pictures/Montenach_enc.png -f ./free-software-song.ogg
$ rm free-software-song.ogg
$ ./slsb.py --reveal -i ./pictures/Montenach_enc.png -b ./song.ogg
Hide and reveal a message by using the description field of the image
---------------------------------------------------------------------
.. code-block:: bash
$ ./exif-header.py --hide -i ./Elisha-Cuthbert.jpg -o ./Elisha-Cuthbert_enc.jpg -f ./fileToHide.txt
$ ./exif-header.py --reveal -i ./Elisha-Cuthbert_enc.jpg
Steganalysis
------------
.. code-block:: bash
$ ./steganalysis-parity.py -i ./pictures./Lenna_enc.png -o ./pictures/Lenna_enc_st.png

View file

@ -1,4 +0,0 @@
from stegano import slsb
secret = slsb.hide("./pictures/Lenna.png", "Bonjour tout le monde")
secret.save("./Lenna-secret.png")

View file

@ -1,6 +0,0 @@
#!/bin/sh
wget http://www.gnu.org/music/free-software-song.ogg
slsb --hide -i ./pictures/Montenach.png -o ./pictures/Montenach_enc.png -f ./free-software-song.ogg
rm free-software-song.ogg
slsb --reveal -i ./pictures/Montenach_enc.png -b ./zik.ogg

View file

@ -1,26 +0,0 @@
#!/bin/sh
#
# Test the LSB method with sets.
#
echo "We're going to test a little Stéganô..."
echo "Hide the message with the Sieve of Eratosthenes..."
slsb-set --hide -i ./pictures/Montenach.png -o ./surprise.png --generator eratosthenes -m 'Joyeux Noël!'
echo ""
echo "Try to reveal with Mersenne numbers..."
slsb-set --reveal --generator mersenne -i ./surprise.png
echo ""
echo "Try to reveal with fermat numbers..."
slsb-set --reveal --generator fermat -i ./surprise.png
echo ""
echo "Try to reveal with carmichael numbers..."
slsb-set --reveal --generator carmichael -i ./surprise.png
echo ""
echo "Try to reveal with Sieve of Eratosthenes..."
slsb-set --reveal --generator eratosthenes -i ./surprise.png

View file

@ -1,22 +0,0 @@
#!/bin/sh
# Some tests of the LSB method which uses sets (slsb-set). Sets are used in order to select the pixels where the
# message will be hidden.
# Hide the message - LSB with a set defined by the identity function (f(x) = x).
slsb-set --hide -i examples/pictures/Montenach.png -o ~/enc-identity.png --generator identity -m 'I like steganography.'
# Hide the message - LSB only.
slsb --hide -i examples/pictures/Montenach.png -o ~/enc.png -m 'I like steganography.'
# Check if the two generated files are the same.
sha1sum ~/enc-identity.png ~/enc.png
# The output of slsb is given to slsb-set.
slsb-set --reveal -i ~/enc.png --generator identity
# The output of slsb-set is given to slsb.
slsb --reveal -i ~/enc-identity.png

View file

@ -1,21 +0,0 @@
#!/bin/sh
#
# Test the LSB method with sets.
#
echo "Hide the message with Sieve of Eratosthenes..."
slsb-set --hide -i ./pictures/Ginnifer-Goodwin.png -o ./surprise.png --generator eratosthenes -m 'Probably the most beautiful woman in the world.'
echo ""
echo "Steganalysis of the original photo..."
steganalysis-parity -i ./pictures/Ginnifer-Goodwin.png -o ./surprise_st_original.png
echo "Steganalysis of the secret photo..."
steganalysis-parity -i ./surprise.png -o ./surprise_st_secret.png
echo ""
echo "Reveal with Sieve of Eratosthenes..."
echo "The secret is:"
slsb-set --reveal --generator eratosthenes -i ./surprise.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 373 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 MiB

1173
poetry.lock generated Normal file

File diff suppressed because it is too large Load diff

98
pyproject.toml Normal file
View file

@ -0,0 +1,98 @@
[build-system]
requires = ["poetry-core>=2.0"]
build-backend = "poetry.core.masonry.api"
[project]
name = "stegano"
version = "2.0.0"
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,<12.0)",
"piexif (>=1.1.3)",
"crayons (>=0.4.0)",
"opencv-python (>=4.11.0.86)"
]
[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.13"
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"
[tool.isort]
profile = "black"
[tool.flake8]
ignore = ["E203"]

View file

@ -1 +0,0 @@
pillow

View file

@ -1,2 +0,0 @@
[metadata]
description-file = README.md

View file

@ -1,49 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
import os
import sys
import shutil
try:
from setuptools import setup
except ImportError:
from distutils.core import setup
packages = [
'stegano',
'stegano.exif'
]
requires = ['pillow']
with open('README.md', 'r') as f:
readme = f.read()
with open('CHANGELOG.md', 'r') as f:
changelog = f.read()
setup(
name='Stegano',
version='0.4.3',
author='Cédric Bonhomme',
author_email='cedric@cedricbonhomme.org',
packages=packages,
include_package_data=True,
scripts=['bin/slsb', 'bin/slsb-set', 'bin/steganalysis-parity'],
url='https://bitbucket.org/cedricbonhomme/stegano',
description='A Python Steganography module.',
long_description=readme + changelog,
platforms = ['Linux'],
license='GPLv3',
install_requires=requires,
zip_safe=False,
classifiers=[
"Development Status :: 4 - Beta",
"Environment :: Console",
"Topic :: Utilities",
"Operating System :: OS Independent",
"Programming Language :: Python :: 2.7",
"Programming Language :: Python :: 3.4",
"License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)"
]
)

View file

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

View file

@ -1,108 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Stéganô - Stéganô is a basic Python Steganography module.
# Copyright (C) 2010-2013 Cédric Bonhomme - http://cedricbonhomme.org/
#
# For more information : http://bitbucket.org/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: 2010/10/01 $"
__license__ = "GPLv3"
import sys
from PIL import Image
def hide(img, message):
"""
Hide a message (string) in an image.
Use the red portion of a pixel (r, g, b) tuple to
hide the message string characters as ASCII values.
The red value of the first pixel is used for length of string.
"""
length = len(message)
# Limit length of message to 255
if length > 255:
return False
# Use a copy of image to hide the text in
encoded = img.copy()
width, height = img.size
index = 0
for row in range(height):
for col in range(width):
(r, g, b) = img.getpixel((col, row))
# first value is length of message
if row == 0 and col == 0 and index < length:
asc = length
elif index <= length:
c = message[index -1]
asc = ord(c)
else:
asc = r
encoded.putpixel((col, row), (asc, g , b))
index += 1
return encoded
def reveal(img):
"""
Find a message in an image.
Check the red portion of an pixel (r, g, b) tuple for
hidden message characters (ASCII values).
The red value of the first pixel is used for length of string.
"""
width, height = img.size
message = ""
index = 0
for row in range(height):
for col in range(width):
r, g, b = img.getpixel((col, row))
# First pixel r value is length of message
if row == 0 and col == 0:
length = r
elif index <= length:
message += chr(r)
index += 1
return message
if __name__ == '__main__':
# Point of entry in execution mode.
from optparse import OptionParser
usage = "usage: %prog hide|reveal [options]"
parser = OptionParser(usage)
parser.add_option("-i", "--input", dest="input_image_file",
help="Image file.")
parser.add_option("-o", "--output", dest="output_image_file",
help="Image file.")
parser.add_option("-s", "--secret", dest="secret",
help="Your secret (Message, Image, Music or any binary file).")
parser.set_defaults(input_image_file = './pictures/Lenna.png',
output_image_file = './pictures/Lenna_enc.png',
secret = 'Hello World!')
(options, args) = parser.parse_args()
if sys.argv[1] == "hide":
img = Image.open(options.input_image_file)
img_encoded = hide(img, options.secret)
img_encoded.save(options.output_image_file)
elif sys.argv[1] == "reveal":
img = Image.open(options.input_image_file)
print(reveal(img))

View file

227
stegano/console/lsb.py Executable file
View 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__}")

55
stegano/console/parity.py Normal file
View file

@ -0,0 +1,55 @@
#!/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/08/25 $"
__license__ = "GPLv3"
import argparse
from PIL import Image
try:
from stegano.steganalysis import parity
except Exception:
print("Install Stegano: pipx install Stegano")
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
View 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)

View file

@ -0,0 +1,44 @@
#!/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__ = "Cédric Bonhomme"
__version__ = "$Revision: 0.1 $"
__date__ = "$Date: 2016/08/26 $"
__revision__ = "$Date: 2016/08/26 $"
__license__ = "GPLv3"
import argparse
from PIL import Image
try:
from stegano.steganalysis import statistics
except Exception:
print("Install Stegano: sudo pip install Stegano")
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)

128
stegano/console/wav.py Normal file
View file

@ -0,0 +1,128 @@
#!/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"
try:
from stegano import wav
except Exception:
print("Install stegano: pipx install Stegano")
import argparse
from stegano import tools
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 audio
parser_hide.add_argument(
"-i",
"--input",
dest="input_audio_file",
required=True,
help="Input audio 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)."
)
# Audio containing the secret
parser_hide.add_argument(
"-o",
"--output",
dest="output_audio_file",
required=True,
help="Output audio containing the secret.",
)
# Subparser: Reveal
parser_reveal = subparsers.add_parser("reveal", help="reveal help")
parser_reveal.add_argument(
"-i",
"--input",
dest="input_audio_file",
required=True,
help="Input audio 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.",
)
arguments = parser.parse_args()
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)
wav.hide(
input_file=arguments.input_audio_file,
message=secret,
encoding=arguments.encoding,
output_file=arguments.output_audio_file,
)
elif arguments.command == "reveal":
try:
secret = wav.reveal(
input_file=arguments.input_audio_file, 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)

View file

@ -1 +0,0 @@

View file

@ -1,197 +0,0 @@
"""
This module offers one class, MinimalExifReader. Pass jpg filename
to the constructor. Will read minimal exif info from the file. Three
"public" functions available:
imageDescription()--returns Exif ImageDescription tag (0x010e) contents,
or '' if not found.
copyright()--returns Exif copyright tag (0x8298) contents, or '' if not
found.
dateTimeOriginal()--returns Exif DateTimeOriginal tag (0x9003) contents,
or '' if not found. If found, the trailing nul char
is stripped. This function also takes an optional
format string to apply time.strftime-style formatting
to the date time.
Brought to you by Megabyte Rodeo Software.
"""
# Written by Chris Stromberger, 10/2004. Public Domain.
# Much is owed to Thierry Bousch's exifdump.py:
# http://topo.math.u-psud.fr/~bousch/exifdump.py
#---------------------------------------------------------------------
class ExifFormatException(Exception):
pass
#---------------------------------------------------------------------
class MinimalExifReader:
IMAGE_DESCRIPTION_TAG = 0x010e
COPYRIGHT_TAG = 0x8298
EXIF_SUBIFD_TAG = 0x8769
DATE_TIME_ORIGINAL_TAG = 0x9003
#---------------------------------------
def __init__(self, filename):
"""Pass in jpg exif file name to process. Will attempt to find tags
of interest."""
self.tagsToFind = {self.IMAGE_DESCRIPTION_TAG:'',
self.COPYRIGHT_TAG:'',
self.DATE_TIME_ORIGINAL_TAG:''}
# Read first bit of file to see if exif file.
f = open(filename, 'rb')
firstTwoBytes = f.read(2)
if firstTwoBytes != '\xff\xd8':
f.close()
raise ExifFormatException("Missing SOI marker")
appMarker = f.read(2)
# See if there's an APP0 section, which sometimes appears.
if appMarker == '\xff\xe0':
#print "Skipping app0"
# Yes, we have app0. Skip over it.
app0DataLength = ord(f.read(1)) * 256 + ord(f.read(1))
app0 = f.read(app0DataLength - 2)
appMarker = f.read(2)
if appMarker != '\xff\xe1':
raise ExifFormatException("Can't find APP1 marker")
exifHeader = f.read(8)
#import binascii
#print binascii.hexlify(exifHeader)
if (exifHeader[2:6] != 'Exif' or
exifHeader[6:8] != '\x00\x00'):
f.close()
raise ExifFormatException("Malformed APP1")
app1DataLength = ord(exifHeader[0]) * 256 + ord(exifHeader[1])
#print app1DataLength
# Read exif info starting at the beginning of the self.tiff section.
# This is 8 bytes into the app1 section, so subtract 8 from
# app1 length.
self.tiff = f.read(app1DataLength - 8)
f.close()
self.endian = self.tiff[0]
if self.endian not in ('I', 'M'):
raise ExifFormatException("Invalid endianess found: %s" % self.endian)
# Now navigate to the items of interest and get them.
ifdStart = self.getValueAtLocation(4, 4)
self.ifdSearch(ifdStart)
#---------------------------------------
def imageDescription(self):
"""Return image description tag contents or '' if not found."""
return self.tagsToFind[self.IMAGE_DESCRIPTION_TAG].strip('\x20\x00')
#---------------------------------------
def copyright(self):
"""Return copyright tag contents or '' if not found."""
return self.tagsToFind[self.COPYRIGHT_TAG].strip('\x20\x00')
#---------------------------------------
def dateTimeOriginal(self, formatString = None):
"""Pass in optional format string to get time.strftime style formatting,
else get default exif format for date time string (without trailing nul).
Returns '' if tag not found."""
# The datetime should end in nul, get rid of it.
if formatString is None or not self.tagsToFind[self.DATE_TIME_ORIGINAL_TAG]:
return self.tagsToFind[self.DATE_TIME_ORIGINAL_TAG].strip('\x20\x00')
else:
# This will only work if the datetime string is in the standard exif format (i.e., hasn't been altered).
try:
import time
return time.strftime(formatString, time.strptime(self.tagsToFind[self.DATE_TIME_ORIGINAL_TAG].strip('\x20\x00'), '%Y:%m:%d %H:%M:%S'))
except:
return self.tagsToFind[self.DATE_TIME_ORIGINAL_TAG].strip('\x20\x00')
#---------------------------------------
def ifdSearch(self, ifdStart):
numIfdEntries = self.getValueAtLocation(ifdStart, 2)
tagsStart = ifdStart + 2
for entryNum in range(numIfdEntries):
# For my purposes, all files will have either no tags, or
# only our tags of interest, so no need to waste time trying to
# break out of the loop early.
thisTagStart = tagsStart + 12 * entryNum
tagId = self.getValueAtLocation(thisTagStart, 2)
if tagId == self.EXIF_SUBIFD_TAG:
# This is a special tag that points to another ifd. Our
# date time original tag is in the sub ifd.
self.ifdSearch(self.getTagValue(thisTagStart))
elif tagId in self.tagsToFind:
assert(not self.tagsToFind[tagId])
self.tagsToFind[tagId] = self.getTagValue(thisTagStart)
#---------------------------------------
def getValueAtLocation(self, offset, length):
slice = self.tiff[offset:offset + length]
if self.endian == 'I':
val = self.s2n_intel(slice)
else:
val = self.s2n_motorola(slice)
return val
#---------------------------------------
def s2n_motorola(self, str):
x = 0
for c in str:
x = (x << 8) | ord(c)
return x
#---------------------------------------
def s2n_intel(self, str):
x = 0
y = 0
for c in str:
x = x | (ord(c) << y)
y = y + 8
return x
#---------------------------------------
def getTagValue(self, thisTagStart):
datatype = self.getValueAtLocation(thisTagStart + 2, 2)
numBytes = [ 1, 1, 2, 4, 8, 1, 1, 2, 4, 8 ] [datatype-1] * self.getValueAtLocation(thisTagStart + 4, 4)
if numBytes > 4:
offsetToValue = self.getValueAtLocation(thisTagStart + 8, 4)
return self.tiff[offsetToValue:offsetToValue + numBytes]
else:
if datatype == 2 or datatype == 1 or datatype == 7:
return self.tiff[thisTagStart + 8:thisTagStart + 8 + numBytes]
else:
return self.getValueAtLocation(thisTagStart + 8, numBytes)
#---------------------------------------
def __str__(self):
return str(self.tagsToFind)
#---------------------------------------------------------------------
if __name__ == '__main__':
import sys
if len(sys.argv) == 1:
print("Pass jpgs to process.")
sys.exit(1)
for filename in sys.argv[1:]:
try:
f = MinimalExifReader(filename)
print(filename)
print("description: '%s'" % f.imageDescription())
print("copyright: '%s'" % f.copyright())
print("dateTimeOriginal: '%s'" % f.dateTimeOriginal())
print("dateTimeOriginal: '%s'" % f.dateTimeOriginal('%B %d, %Y %I:%M:%S %p'))
print()
except ExifFormatException as ex:
sys.stderr.write("Exif format error: %s\n" % ex)
except:
sys.stderr.write("Unable to process %s\n" % filename)

View file

@ -1,457 +0,0 @@
"""
Offers one class, MinimalExifWriter, which takes a jpg filename
in the constructor. Allows you to: remove exif section, add
image description, add copyright. Typical usage:
f = MinimalExifWriter('xyz.jpg')
f.newImageDescription('This is a photo of something very interesting!')
f.newCopyright('Jose Blow, All Rights Reserved', addCopyrightYear = 1)
f.process()
Class methods:
newImageDescription(description)--will add Exif ImageDescription to file.
newCopyright(copyright, addSymbol = 0, addYear = 0)--will add Exif Copyright to file.
Will optionally prepend copyright symbol, or copyright symbol and current year.
removeExif()--will obliterate existing exif section.
process()--call after calling one or more of the above. Will remove existing exif
section, optionally saving some existing tags (see below), and insert a new exif
section with only three tags at most: description, copyright and date time original.
If removeExif() not called, existing description (or new description if newDescription()
called), existing copyright (or new copyright if newCopyright() called) and existing
"DateTimeOriginal" (date/time picture taken) tags will be rewritten to the new
minimal exif section.
Run at comand line with no args to see command line usage.
Does not work on unix due to differences in mmap. Not sure what's up there--
don't need it on unix!
Brought to you by Megabyte Rodeo Software.
http://www.fetidcascade.com/pyexif.html
"""
# Written by Chris Stromberger, 10/2004. Public Domain.
# Last updated: 12/3/2004.
DUMP_TIFF = 0
VERBOSE = 0
if VERBOSE:
import binascii
import mmap
import sys
from . import minimal_exif_reader
#---------------------------------------------------------------------
class ExifFormatException(Exception):
pass
#---------------------------------------------------------------------------
class MinimalExifWriter:
SOI_MARKER = '\xff\xd8'
APP0_MARKER = '\xff\xe0'
APP1_MARKER = '\xff\xe1'
# Standard app0 segment that will work for all files. We hope.
# Based on http://www.funducode.com/freec/Fileformats/format3/format3b.htm.
APP0 = '\xff\xe0\x00\x10\x4a\x46\x49\x46\x00\x01\x01\x00\x00\x01\x00\x01\x00\x00'
def __init__(self, filename):
self.filename = filename
self.removeExifSection = 0
self.description = None
self.copyright = None
self.dateTimeOriginal = None
#---------------------------------------------
def newImageDescription(self, description):
self.description = description
#---------------------------------------------
def newCopyright(self, copyright, addSymbol = 0, addYear = 0):
if addYear:
import time
year = time.localtime()[0]
self.copyright = "\xa9 %s %s" % (year, copyright)
elif addSymbol:
self.copyright = "\xa9 %s" % copyright
else:
self.copyright = copyright
#---------------------------------------------
def removeExif(self):
self.removeExifSection = 1
#---------------------------------------------
def process(self):
if not self.removeExifSection:
self.getExistingExifInfo()
if VERBOSE:
print(self)
import os
try:
fd = os.open(self.filename, os.O_RDWR)
except:
sys.stderr.write('Unable to open "%s"\n' % filename)
return
self.m = mmap.mmap(fd, 0)
os.close(fd)
# We only add app0 if all we're doing is removing the exif section.
justRemovingExif = self.description is None and self.copyright is None and self.removeExifSection
if VERBOSE: print('justRemovingExif=%s' % justRemovingExif)
self.removeExifInfo(addApp0 = justRemovingExif)
if justRemovingExif:
self.m.close()
return
# Get here means we are adding new description and/or copyright.
self.removeApp0()
totalTagsToBeAdded = len([_f for _f in (self.description, self.copyright, self.dateTimeOriginal) if _f])
assert(totalTagsToBeAdded > 0)
# Layout will be: firstifd|description|copyright|exififd|datetime.
# First ifd will have tags: desc|copyright|subifd tag.
ifd = [self.twoBytesHexIntel(totalTagsToBeAdded)]
ifdEnd = ['\x00\x00\x00\x00']
NUM_TAGS_LEN = 2
TAG_LEN = 12
NEXT_IFD_OFFSET_LEN = 4
TIFF_HEADER_LENGTH = 8
ifdLength = NUM_TAGS_LEN + TAG_LEN * totalTagsToBeAdded + NEXT_IFD_OFFSET_LEN
# Subifd only has one tag.
SUBIFD_LENGTH = NUM_TAGS_LEN + TAG_LEN + NEXT_IFD_OFFSET_LEN
offsetToEndOfData = ifdLength + TIFF_HEADER_LENGTH
if self.description:
ifd.append(self.descriptionTag(len(self.description), offsetToEndOfData))
ifdEnd.append(self.description)
offsetToEndOfData += len(self.description)
if self.copyright:
ifd.append(self.copyrightTag(len(self.copyright), offsetToEndOfData))
ifdEnd.append(self.copyright)
offsetToEndOfData += len(self.copyright)
if self.dateTimeOriginal:
ifd.append(self.subIfdTag(offsetToEndOfData))
offsetToEndOfData += SUBIFD_LENGTH
ifdEnd.append(self.buildSubIfd(len(self.dateTimeOriginal), offsetToEndOfData))
ifdEnd.append(self.dateTimeOriginal)
app1 = self.buildApp1Section(ifd, ifdEnd)
self.addApp1(app1)
self.m.close()
#---------------------------------------------
# Build exif subifd with one tag for datetime (0x9003).
# Type is ascii (0x0002).
def buildSubIfd(self, lenDateTime, offsetToEndOfData):
return '\x01\x00\x03\x90\x02\x00%s%s\x00\x00\x00\x00' % (self.fourBytesHexIntel(lenDateTime), self.fourBytesHexIntel(offsetToEndOfData))
#---------------------------------------------
def getExistingExifInfo(self):
# Save off the old stuff.
try:
f = minimal_exif_reader.MinimalExifReader(self.filename)
except:
# Assume no existing exif info in the file. We
# don't care.
return
if not self.description:
self.description = f.imageDescription()
if not self.copyright:
self.copyright = f.copyright()
self.dateTimeOriginal = f.dateTimeOriginal()
if self.dateTimeOriginal:
# Restore ending nul.
if self.dateTimeOriginal[-1] != '\x00':
self.dateTimeOriginal += '\x00'
#---------------------------------------------------------------------------
def removeExifInfo(self, addApp0 = 1):
"""Remove the app1 section of the jpg. This removes all exif info and the exif
thumbnail. addApp0 should be 1 to add a minimal app0 section right after soi
to make it a legitimate jpg, I think (various image programs can read the file
without app0, but I think the standard requires one).
"""
# Read first bit of file to see if exif file.
self.m.seek(0)
if self.m.read(2) != self.SOI_MARKER:
self.m.close()
raise ExifFormatException("Missing SOI marker")
app0DataLength = 0
appMarker = self.m.read(2)
# See if there's an APP0 section, which sometimes appears.
if appMarker == self.APP0_MARKER:
if VERBOSE: print('app0 found')
app0DataLength = ord(self.m.read(1)) * 256 + ord(self.m.read(1))
if VERBOSE: print('app0DataLength: %s' % app0DataLength)
# Back up 2 bytes to get the length bytes.
self.m.seek(-2, 1)
existingApp0 = self.m.read(app0DataLength)
appMarker = self.m.read(2)
if appMarker != self.APP1_MARKER:
# We don't care, we'll add our minimal app1 later.
return
exifHeader = self.m.read(8)
if VERBOSE: print('exif header: %s' % binascii.hexlify(exifHeader))
if (exifHeader[2:6] != 'Exif' or
exifHeader[6:8] != '\x00\x00'):
self.m.close()
raise ExifFormatException("Malformed APP1")
app1Length = ord(exifHeader[0]) * 256 + ord(exifHeader[1])
if VERBOSE: print('app1Length: %s' % app1Length)
originalFileSize = self.m.size()
# Shift stuff just past app1 to overwrite app1.
# Start at app1 length bytes in + other bytes not incl in app1 length.
src = app1Length + len(self.SOI_MARKER) + len(self.APP1_MARKER)
if app0DataLength:
src += app0DataLength + len(self.APP0_MARKER)
dest = len(self.SOI_MARKER)
if addApp0:
if app0DataLength != 0:
# We'll re-add the existing app0.
dest += app0DataLength + len(self.APP0_MARKER)
else:
# Add our generic app0.
dest += len(self.APP0)
count = originalFileSize - app1Length - len(self.SOI_MARKER) - len(self.APP1_MARKER)
if app0DataLength:
count -= app0DataLength + len(self.APP0_MARKER)
if VERBOSE: print('self.m.move(%s, %s, %s)' % (dest, src, count))
self.m.move(dest, src, count)
if addApp0:
if app0DataLength != 0:
self.m.resize(originalFileSize - app1Length - len(self.APP1_MARKER))
else:
self.m.seek(len(self.SOI_MARKER))
self.m.write(self.APP0)
self.m.resize(originalFileSize - app1Length - len(self.APP1_MARKER) + len(self.APP0))
else:
self.m.resize(originalFileSize - app1Length - len(self.APP1_MARKER))
#---------------------------------------------------------------------------
def removeApp0(self):
self.m.seek(0)
header = self.m.read(6)
if (header[0:2] != self.SOI_MARKER or
header[2:4] != self.APP0_MARKER):
if VERBOSE: print('no app0 found: %s' % binascii.hexlify(header))
return
originalFileSize = self.m.size()
app0Length = ord(header[4]) * 256 + ord(header[5])
if VERBOSE: print('app0Length:', app0Length)
# Shift stuff to overwrite app0.
# Start at app0 length bytes in + other bytes not incl in app0 length.
src = app0Length + len(self.SOI_MARKER) + len(self.APP0_MARKER)
dest = len(self.SOI_MARKER)
count = originalFileSize - app0Length - len(self.SOI_MARKER) - len(self.APP0_MARKER)
self.m.move(dest, src, count)
if VERBOSE: print('m.move(%s, %s, %s)' % (dest, src, count))
self.m.resize(originalFileSize - app0Length - len(self.APP0_MARKER))
#---------------------------------------------------------------------------
def addApp1(self, app1):
originalFileSize = self.m.size()
# Insert app1 section.
self.m.resize(originalFileSize + len(app1))
src = len(self.SOI_MARKER)
dest = len(app1) + len(self.SOI_MARKER)
count = originalFileSize - len(self.SOI_MARKER)
self.m.move(dest, src, count)
self.m.seek(len(self.SOI_MARKER))
self.m.write(app1)
#---------------------------------------------------------------------------
def fourBytesHexIntel(self, number):
return '%s%s%s%s' % (chr(number & 0x000000ff),
chr((number >> 8) & 0x000000ff),
chr((number >> 16) & 0x000000ff),
chr((number >> 24) & 0x000000ff))
#---------------------------------------------------------------------------
def twoBytesHexIntel(self, number):
return '%s%s' % (chr(number & 0x00ff),
chr((number >> 8) & 0x00ff))
#---------------------------------------------------------------------------
def descriptionTag(self, numChars, loc):
return self.asciiTag('\x0e\x01', numChars, loc)
#---------------------------------------------------------------------------
def copyrightTag(self, numChars, loc):
return self.asciiTag('\x98\x82', numChars, loc)
#---------------------------------------------------------------------------
def subIfdTag(self, loc):
return '\x69\x87\x04\x00\x01\x00\x00\x00%s' % self.fourBytesHexIntel(loc)
#---------------------------------------------------------------------------
def asciiTag(self, tag, numChars, loc):
"""Create ascii tag. Assumes description > 4 chars long."""
return '%s\x02\x00%s%s' % (tag, self.fourBytesHexIntel(numChars), self.fourBytesHexIntel(loc))
#---------------------------------------------------------------------------
def buildApp1Section(self, ifdPieces, ifdEndPieces):
"""Create the APP1 section of an exif jpg. Consists of exif header plus
tiff header + ifd and associated data."""
# Intel byte order, offset to first ifd will be 8.
tiff = 'II\x2a\x00\x08\x00\x00\x00%s%s' % (''.join(ifdPieces), ''.join(ifdEndPieces))
if DUMP_TIFF:
f = open('tiff.dump', 'wb')
f.write(tiff)
f.close()
app1Length = len(tiff) + 8
return '\xff\xe1%s%sExif\x00\x00%s' % (chr((app1Length >> 8) & 0x00ff), chr(app1Length & 0x00ff), tiff)
#---------------------------------------------------------------------------
def __str__(self):
return """filename: %(filename)s
removeExifSection: %(removeExifSection)s
description: %(description)s
copyright: %(copyright)s
dateTimeOriginal: %(dateTimeOriginal)s
""" % self.__dict__
#---------------------------------------------------------------------------
def usage(error = None):
"""Print command line usage and exit"""
if error:
print(error)
print()
print("""This program will remove exif info from an exif jpg, and can optionally
add the ImageDescription exif tag and/or the Copyright tag. But it will always remove
some or all existing exif info (depending on options--see below)!
So don't run this on your original images without a backup.
Options:
-h: shows this message.
-f <file>: jpg to process (required).
-x: remove exif info (including thumbnail).
-d <description or file>: remove exif info (including thumbnail) and then add exif
ImageDescription. Will save the existing copyright tag if present,
as well as the date time original tag (date & time photo taken),
unless -x also passed (-x always means remove all exif info).
It will attempt to open whatever is passed on the
command line as a file; if successful, the contents of the file
are added as the description, else the literal text on the
command line is used as the description.
-c <copyright or file>: remove exif info (including thumbnail) and then add exif
Copyright tag. Will save the existing image description tag if present,
as well as the date time original tag (date & time photo taken),
unless -x also passed (-x always means remove all exif info).
It will attempt to open whatever is passed on the command line as a file;
if successful, the contents of the file are added as the copyright,
else the literal text on the command line is used as the copyright.
-s: prepend copyright symbol to copyright.
-y: prepend copyright symbol and current year to copyright.
The image description and copyright must be > 4 characters long.
This software courtesy of Megabyte Rodeo Software.""")
sys.exit(1)
#---------------------------------------------------------------------------
def parseArgs(args_):
import getopt
try:
opts, args = getopt.getopt(args_, "yshxd:f:c:")
except getopt.GetoptError:
usage()
filename = None
description = ''
copyright = ''
addCopyrightSymbol = 0
addCopyrightYear = 0
removeExif = 0
for o, a in opts:
if o == "-h":
usage()
if o == "-f":
filename = a
if o == "-d":
try:
f = open(a)
description = f.read()
f.close()
except:
description = a
if o == "-c":
try:
f = open(a)
copyright = f.read()
f.close()
except:
copyright = a
if o == '-x':
removeExif = 1
if o == '-s':
addCopyrightSymbol = 1
if o == '-y':
addCopyrightYear = 1
if filename is None:
usage('Missing jpg filename')
if description and (len(description) <= 4 or len(description) > 60000):
usage('Description too short or too long')
if copyright and (len(copyright) <= 4 or len(copyright) > 60000):
usage('Copyright too short or too long')
if not description and not copyright and not removeExif:
usage('Nothing to do!')
return filename, description, copyright, removeExif, addCopyrightSymbol, addCopyrightYear
#---------------------------------------------------------------------------
if __name__ == '__main__':
try:
filename, description, copyright, removeExif, addCopyrightSymbol, addCopyrightYear = parseArgs(sys.argv[1:])
f = MinimalExifWriter(filename)
if description:
f.newImageDescription(description)
if copyright:
f.newCopyright(copyright, addCopyrightSymbol, addCopyrightYear)
if removeExif:
f.removeExif()
f.process()
except ExifFormatException as ex:
sys.stderr.write("Exif format error: %s\n" % ex)
except SystemExit:
pass
except:
sys.stderr.write("Unable to process %s\n" % filename)
raise

View file

@ -1,118 +0,0 @@
#!/usr/bin/env python
#-*- coding: utf-8 -*-
# Stéganô - Stéganô is a basic Python Steganography module.
# Copyright (C) 2010-2013 Cédric Bonhomme - http://cedricbonhomme.org/
#
# For more information : http://bitbucket.org/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: 2010/03/24 $"
__license__ = "GPLv3"
# Thanks to: http://www.julesberman.info/spec2img.htm
def hide(img, img_enc, copyright="http://bitbucket.org/cedricbonhomme/stegano", \
secret_message = None, secret_file = None):
"""
"""
import shutil
import datetime
from zlib import compress
from zlib import decompress
from base64 import b64encode
from .exif.minimal_exif_writer import MinimalExifWriter
if secret_file != None:
with open(secret_file, "r") as f:
secret_file_content = f.read()
text = "\nImage annotation date: "
text = text + str(datetime.date.today())
text = text + "\nImage description:\n"
if secret_file != None:
text = compress(b64encode(text + secret_file_content))
else:
text = compress(b64encode(text + secret_message))
try:
shutil.copy(img, img_enc)
except Exception as e:
print(("Impossible to copy image:", e))
return
f = MinimalExifWriter(img_enc)
f.removeExif()
f.newImageDescription(text)
f.newCopyright(copyright, addYear = 1)
f.process()
def reveal(img):
"""
"""
from base64 import b64decode
from zlib import decompress
from .exif.minimal_exif_reader import MinimalExifReader
try:
g = MinimalExifReader(img)
except:
print("Impossible to read description.")
return
print((b64decode(decompress(g.imageDescription()))))
print(("\nCopyright " + g.copyright()))
#print g.dateTimeOriginal()s
if __name__ == "__main__":
# Point of entry in execution mode.
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.")
# Original image
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.")
# Secret raw 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 textt 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 = '')
(options, args) = parser.parse_args()
if options.hide:
if options.secret_message != "" and options.secret_file == "":
hide(img=options.input_image_file, img_enc=options.output_image_file, \
secret_message=options.secret_message)
elif options.secret_message == "" and options.secret_file != "":
hide(img=options.input_image_file, img_enc=options.output_image_file, \
secret_file=options.secret_file)
elif options.reveal:
reveal(img=options.input_image_file)

View file

@ -0,0 +1,5 @@
#!/usr/bin/env python
from .exifHeader import hide, reveal
__all__ = ["hide", "reveal"]

View file

@ -0,0 +1,161 @@
#!/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.2.2 $"
__date__ = "$Date: 2016/05/26 $"
__revision__ = "$Date: 2017/01/18 $"
__license__ = "GPLv3"
import piexif
from stegano import tools
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, "rb") as f:
secret_message = f.read()
try:
text = compress(b64encode(bytes(secret_message, "utf-8")))
except Exception:
text = compress(b64encode(secret_message))
img = tools.open_image(input_image_file)
if img_format is None:
img_format = img.format
if "exif" in img.info:
exif_dict = piexif.load(img.info["exif"])
else:
exif_dict = {}
exif_dict["0th"] = {}
exif_dict["0th"][piexif.ImageIFD.ImageDescription] = text
exif_bytes = piexif.dump(exif_dict)
img.save(img_enc, format=img_format, exif=exif_bytes)
img.close()
return img
def reveal(input_image_file):
"""Find a message in an image."""
from base64 import b64decode
from zlib import decompress
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""))
description_key = piexif.ImageIFD.ImageDescription
encoded_message = exif_dict["0th"][description_key]
else:
encoded_message = b""
else:
raise ValueError("Given file is neither JPEG nor TIFF.")
finally:
img.close()
return b64decode(decompress(encoded_message))
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.",
)
# Original image
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.",
)
# Secret raw 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.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,
)
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,
)
elif options.reveal:
reveal(input_image_file=options.input_image_file)

View file

@ -1,155 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Stéganô - Stéganô is a basic Python Steganography module.
# Copyright (C) 2010-2013 Cédric Bonhomme - http://cedricbonhomme.org/
#
# For more information : http://bitbucket.org/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.2 $"
__date__ = "$Date: 2011/12/28 $"
__revision__ = "$Date: 2012/12/14 $"
__license__ = "GPLv3"
import math
import itertools
def identity():
"""
f(x) = x
"""
n = 0
while True:
yield n
n += 1
def Dead_Man_Walking():
n = 0
while True:
yield n + 7
n += 2
def OEIS_A000217():
"""
http://oeis.org/A000217
Triangular numbers: a(n) = C(n+1,2) = n(n+1)/2 = 0+1+2+...+n.
"""
n = 0
while True:
yield (n*(n+1))//2
n += 1
def fermat():
"""
Generate the n-th Fermat Number.
"""
y = 5
while True:
yield y
y = pow(y-1,2)+1
def mersenne():
"""
Generate 2^n-1.
"""
y = 1
while True:
yield y
y = 2*y + 1
def eratosthenes():
"""
Generate the prime numbers with the sieve of Eratosthenes.
"""
d = {}
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 eratosthenes_composite():
"""
Generate the composite numbers with the sieve of Eratosthenes.
"""
p1 = 3
for p2 in eratosthenes():
for n in range(p1 + 1, p2):
yield n
p1 = p2
def carmichael():
for m in eratosthenes_composite():
for a in range(2, m):
if pow(a,m,m) != a:
break
else:
yield m
def ackermann(m, n):
"""
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 fibonacci():
"""
A generator for Fibonacci numbers, goes to next number in series on each call.
This generator start at 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765, 10946, ...
See: http://oeis.org/A000045
"""
a, b = 1, 2
while True:
yield a
a, b = b, a + b
def syracuse(l=15):
"""
Generate the sequence of Syracuse.
"""
y = l
while True:
yield y
q,r = divmod(y,2)
if r == 0:
y = q
else:
y = 3*y + 1
def log_gen():
"""
Logarithmic generator.
"""
y = 1
while True:
adder = max(1, math.pow(10, int(math.log10(y))))
yield int(y)
y = y + adder
if __name__ == "__main__":
# Point of entry in execution mode.
f = fibonacci()
for x in range(13):
print(next(f), end=' ') # 0 1 1 2 3 5 8 13 21 34 55 89 144

5
stegano/lsb/__init__.py Normal file
View file

@ -0,0 +1,5 @@
#!/usr/bin/env python
from .lsb import hide, reveal
__all__ = ["hide", "reveal"]

254
stegano/lsb/generators.py Normal file
View 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

90
stegano/lsb/lsb.py Normal file
View file

@ -0,0 +1,90 @@
#!/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/13 $"
__revision__ = "$Date: 2019/05/31 $"
__license__ = "GPLv3"
from typing import IO, Iterator, Union
from stegano import tools
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.
"""
hider = tools.Hider(image, message, encoding, auto_convert_rgb)
width = hider.encoded_image.width
if not generator:
generator = identity()
while shift != 0:
next(generator)
shift -= 1
while hider.encode_another_pixel():
generated_number = next(generator)
col = generated_number % width
row = int(generated_number / width)
hider.encode_pixel((col, row))
return hider.encoded_image
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
if not generator:
generator = identity()
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

5
stegano/red/__init__.py Normal file
View file

@ -0,0 +1,5 @@
#!/usr/bin/env python
from .red import hide, reveal
__all__ = ["hide", "reveal"]

94
stegano/red/red.py Executable file
View file

@ -0,0 +1,94 @@
#!/usr/bin/env python
# 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
#
# 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.2 $"
__date__ = "$Date: 2010/10/01 $"
__revision__ = "$Date: 2017/02/06 $"
__license__ = "GPLv3"
from typing import IO, Union, cast
from stegano import tools
def hide(input_image: Union[str, IO[bytes]], message: str):
"""
Hide a message (string) in an image.
Use the red portion of a pixel (r, g, b) tuple to
hide the message string characters as ASCII values.
The red value of the first pixel is used for message_length of the string.
"""
message_length = len(message)
assert message_length != 0, "message message_length is zero"
assert message_length < 255, "message is too long"
img = tools.open_image(input_image)
# Ensure image mode is RGB
if img.mode != "RGB":
img = img.convert("RGB")
# Use a copy of image to hide the text in
encoded = img.copy()
width, height = img.size
index = 0
for row in range(height):
for col in range(width):
pixel = cast(tuple[int, int, int], img.getpixel((col, row)))
r, g, b = pixel
# first value is message_length of message
if row == 0 and col == 0 and index < message_length:
asc = message_length
elif index <= message_length:
c = message[index - 1]
asc = ord(c)
else:
asc = r
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.
Check the red portion of an pixel (r, g, b) tuple for
hidden message characters (ASCII values).
The red value of the first pixel is used for message_length of string.
"""
img = tools.open_image(input_image)
# Ensure image mode is RGB
if img.mode != "RGB":
img = img.convert("RGB")
width, height = img.size
message = ""
index = 0
for row in range(height):
for col in range(width):
pixel = cast(tuple[int, int, int], img.getpixel((col, row)))
r, g, b = pixel
# First pixel r value is length of message
if row == 0 and col == 0:
message_length = r
elif index <= message_length:
message += chr(r)
index += 1
img.close()
return message

View file

@ -1,163 +0,0 @@
#!/usr/bin/env python
#-*- coding: utf-8 -*-
# Stéganô - Stéganô is a basic Python Steganography module.
# Copyright (C) 2010-2011 Cédric Bonhomme - http://cedricbonhomme.org/
#
# For more information : http://bitbucket.org/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.2 $"
__date__ = "$Date: 2010/03/24 $"
__license__ = "GPLv3"
import sys
from PIL import Image
from . import tools
def hide(input_image_file, message):
"""
Hide a message (string) in an image with the
LSB (Least Significant Bit) technique.
"""
img = Image.open(input_image_file)
encoded = img.copy()
width, height = img.size
index = 0
message = str(len(message)) + ":" + message
#message_bits = tools.a2bits(message)
message_bits = "".join(tools.a2bits_list(message))
npixels = width * height
if len(message_bits) > npixels * 3:
raise Exception("""The message you want to hide is too long (%s > %s).""" % (len(message_bits), npixels * 3))
for row in range(height):
for col in range(width):
if index + 3 <= len(message_bits) :
# Get the colour component.
(r, g, b) = img.getpixel((col, row))
# 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
encoded.putpixel((col, row), (r, g , b))
index += 3
return encoded
def reveal(input_image_file):
"""
Find a message in an image
(with the LSB technique).
"""
img = Image.open(input_image_file)
width, height = img.size
buff, count = 0, 0
bitab = []
limit = None
for row in range(height):
for col in range(width):
# color = [r, g, b]
for color in img.getpixel((col, row)):
buff += (color&1)<<(7-count)
count += 1
if count == 8:
bitab.append(chr(buff))
buff, count = 0, 0
if bitab[-1] == ":" and limit == None:
try:
limit = int("".join(bitab[:-1]))
except:
pass
if len(bitab)-len(str(limit))-1 == limit :
return "".join(bitab)[len(str(limit))+1:]
return ""
def write(image, output_image_file):
"""
"""
try:
image.save(output_image_file)
except Exception as e:
# If hide() returns an error (Too long message).
print(e)
if __name__ == '__main__':
# Point of entry in execution mode.
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.")
# Original image
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.")
# Non binary secret message to hide
parser.add_option("-m", "--secret-message", dest="secret_message",
help="Your secret message to hide (non binary).")
# Binary secret to hide (OGG, executable, etc.)
parser.add_option("-f", "--secret-file", dest="secret_file",
help="Your secret to hide (Text or any binary file).")
# Output for the binary binary secret.
parser.add_option("-b", "--binary", dest="secret_binary",
help="Output for the binary secret (Text or any binary file).")
parser.set_defaults(input_image_file = './pictures/Lenna.png',
output_image_file = './pictures/Lenna_enc.png',
secret_message = '', secret_file = '', secret_binary = "")
(options, args) = parser.parse_args()
if options.hide:
if options.secret_message != "" and options.secret_file == "":
secret = options.secret_message
elif options.secret_message == "" and options.secret_file != "":
secret = tools.binary2base64(options.secret_file)
img_encoded = hide(options.input_image_file, secret)
try:
img_encoded.save(options.output_image_file)
except Exception as e:
# If hide() returns an error (Too long message).
print(e)
elif options.reveal:
secret = reveal(options.input_image_file)
if options.secret_binary != "":
data = tools.base642binary(secret)
with open(options.secret_binary, "w") as f:
f.write(data)
else:
print(secret)

View file

@ -1,183 +0,0 @@
#!/usr/bin/env python
#-*- coding: utf-8 -*-
# Stéganô - Stéganô is a basic Python Steganography module.
# Copyright (C) 2010-2013 Cédric Bonhomme - http://cedricbonhomme.org/
#
# For more information : http://bitbucket.org/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: 2011/12/28 $"
__license__ = "GPLv3"
import sys
from PIL import Image
from . import tools
from . import generators
def hide(input_image_file, message, generator_function):
"""
Hide a message (string) in an image with the
LSB (Least Significant Bit) technique.
"""
img = Image.open(input_image_file)
img_list = list(img.getdata())
width, height = img.size
index = 0
message = str(len(message)) + ":" + message
#message_bits = tools.a2bits(message)
message_bits = "".join(tools.a2bits_list(message))
npixels = width * height
if len(message_bits) > npixels * 3:
raise Exception("""The message you want to hide is too long (%s > %s).""" % (len(message_bits), npixels * 3))
generator = getattr(generators, generator_function)()
while index + 3 <= len(message_bits) :
generated_number = next(generator)
(r, g, b) = 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
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_file, generator_function):
"""
Find a message in an image
(with the LSB technique).
"""
img = Image.open(input_image_file)
img_list = list(img.getdata())
width, height = img.size
buff, count = 0, 0
bitab = []
limit = None
generator = getattr(generators, generator_function)()
while True:
generated_number = next(generator)
# color = [r, g, b]
for color in img_list[generated_number]:
buff += (color&1)<<(7-count)
count += 1
if count == 8:
bitab.append(chr(buff))
buff, count = 0, 0
if bitab[-1] == ":" and limit == None:
try:
limit = int("".join(bitab[:-1]))
except:
pass
if len(bitab)-len(str(limit))-1 == limit :
return "".join(bitab)[len(str(limit))+1:]
return ""
def write(image, output_image_file):
"""
"""
try:
image.save(output_image_file)
except Exception as e:
# If hide() returns an error (Too long message).
print(e)
if __name__ == '__main__':
# Point of entry in execution mode.
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.")
# Original image
parser.add_option("-i", "--input", dest="input_image_file",
help="Input image file.")
# Generator
parser.add_option("-g", "--generator", dest="generator_function",
help="Generator")
# Image containing the secret
parser.add_option("-o", "--output", dest="output_image_file",
help="Output image containing the secret.")
# Non binary secret message to hide
parser.add_option("-m", "--secret-message", dest="secret_message",
help="Your secret message to hide (non binary).")
# Binary secret to hide (OGG, executable, etc.)
parser.add_option("-f", "--secret-file", dest="secret_file",
help="Your secret to hide (Text or any binary file).")
# Output for the binary binary secret.
parser.add_option("-b", "--binary", dest="secret_binary",
help="Output for the binary secret (Text or any binary file).")
parser.set_defaults(input_image_file = './pictures/Lenna.png',
generator_function = 'fermat',
output_image_file = './pictures/Lenna_enc.png',
secret_message = '', secret_file = '', secret_binary = "")
(options, args) = parser.parse_args()
if options.hide:
if options.secret_message != "" and options.secret_file == "":
secret = options.secret_message
elif options.secret_message == "" and options.secret_file != "":
secret = tools.binary2base64(options.secret_file)
img_encoded = hide(options.input_image_file, secret, options.generator_function)
try:
img_encoded.save(options.output_image_file)
except Exception as e:
# If hide() returns an error (Too long message).
print(e)
elif options.reveal:
try:
secret = reveal(options.input_image_file, options.generator_function)
except IndexError:
print("Impossible to detect message.")
exit(0)
if options.secret_binary != "":
data = tools.base642binary(secret)
with open(options.secret_binary, "w") as f:
f.write(data)
else:
print(secret)

View file

@ -0,0 +1 @@
#!/usr/bin/env python

View file

@ -1,10 +1,8 @@
#!/usr/bin/env python #!/usr/bin/env python
#-*- coding: utf-8 -*- # Stegano - Stegano is a pure Python steganography module.
# Copyright (C) 2010-2025 Cédric Bonhomme - https://www.cedricbonhomme.org
# Stéganô - Stéganô is a basic Python Steganography module.
# Copyright (C) 2010-2013 Cédric Bonhomme - http://cedricbonhomme.org/
# #
# For more information : http://bitbucket.org/cedricbonhomme/stegano/ # For more information : https://github.com/cedricbonhomme/Stegano
# #
# This program is free software: you can redistribute it and/or modify # This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by # it under the terms of the GNU General Public License as published by
@ -20,22 +18,28 @@
# along with this program. If not, see <http://www.gnu.org/licenses/> # along with this program. If not, see <http://www.gnu.org/licenses/>
__author__ = "Cedric Bonhomme" __author__ = "Cedric Bonhomme"
__version__ = "$Revision: 0.1 $" __version__ = "$Revision: 0.9.4 $"
__date__ = "$Date: 2010/10/01 $" __date__ = "$Date: 2010/10/01 $"
__revision__ = "$Date: 2019/06/06 $"
__license__ = "GPLv3" __license__ = "GPLv3"
from typing import cast
from PIL import Image from PIL import Image
def steganalyse(img):
def steganalyse(img: Image.Image) -> Image.Image:
""" """
Steganlysis of the LSB technique. Steganlysis of the LSB technique.
""" """
encoded = img.copy() encoded = Image.new(img.mode, (img.size))
width, height = img.size width, height = img.size
bits = ""
for row in range(height): for row in range(height):
for col in range(width): for col in range(width):
r, g, b = img.getpixel((col, row)) if pixel := cast(tuple[int, int, int], img.getpixel((col, row))):
r, g, b = pixel[0:3]
else:
raise Exception("Error during steganlysis.")
if r % 2 == 0: if r % 2 == 0:
r = 0 r = 0
else: else:
@ -50,19 +54,3 @@ def steganalyse(img):
b = 255 b = 255
encoded.putpixel((col, row), (r, g, b)) encoded.putpixel((col, row), (r, g, b))
return encoded return encoded
if __name__ == '__main__':
# Point of entry in execution mode.
from optparse import OptionParser
parser = OptionParser()
parser.add_option("-i", "--input", dest="input_image_file",
help="Image file")
parser.add_option("-o", "--output", dest="output_image_file",
help="Image file")
parser.set_defaults(input_image_file = './pictures/Lenna.png',
output_image_file = './pictures/Lenna_steganalysed.png')
(options, args) = parser.parse_args()
input_image_file = Image.open(options.input_image_file)
output_image = steganalyse(input_image_file)
output_image.save(options.output_image_file)

View file

@ -0,0 +1,52 @@
#!/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.2 $"
__date__ = "$Date: 2010/10/01 $"
__revision__ = "$Date: 2021/11/01 $"
__license__ = "GPLv3"
import typing
from collections import Counter, OrderedDict
def steganalyse(img):
"""
Steganlysis of the LSB technique.
"""
width, height = img.size
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])
)
colours: float = 0
for colour in list(dict_colours.keys()):
colours += colour
colours = colours / len(dict_colours)
# return colours.most_common(10)
return list(dict_colours.keys())[:30], most_common

View file

@ -1,70 +0,0 @@
#!/usr/bin/env python
#-*- coding: utf-8 -*-
# Stéganô - Stéganô is a basic Python Steganography module.
# Copyright (C) 2010-2013 Cédric Bonhomme - http://cedricbonhomme.org/
#
# For more information : http://bitbucket.org/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: 2010/10/01 $"
__license__ = "GPLv3"
import operator
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()
for row in range(height):
for col in range(width):
r, g, b = img.getpixel((col, row))
colours[r] += 1
most_common = colours.most_common(10)
dict_colours = OrderedDict(sorted(list(colours.items()), key=lambda t: t[1]))
colours = 0
for colour in list(dict_colours.keys()):
colours += colour
colours = colours / len(dict_colours)
#return colours.most_common(10)
return list(dict_colours.keys())[:30], most_common
if __name__ == '__main__':
# Point of entry in execution mode.
from optparse import OptionParser
parser = OptionParser()
parser.add_option("-i", "--input", dest="input_image_file",
help="Image file.")
parser.add_option("-o", "--output", dest="output_image_file",
help="Image file.")
parser.set_defaults(input_image_file = './pictures/Lenna.png',
output_image_file = './pictures/Lenna_steganalysed.png')
(options, args) = parser.parse_args()
input_image_file = Image.open(options.input_image_file)
output_image = steganalyse(input_image_file)
soutput_image.save(options.output_image_file)

View file

@ -1,10 +1,8 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*- # Stegano - Stegano is a pure Python steganography module.
# Copyright (C) 2010-2025 Cédric Bonhomme - https://www.cedricbonhomme.org
# Stéganô - Stéganô is a basic Python Steganography module.
# Copyright (C) 2010-2013 Cédric Bonhomme - http://cedricbonhomme.org/
# #
# For more information : http://bitbucket.org/cedricbonhomme/stegano/ # For more information : https://github.com/cedricbonhomme/Stegano
# #
# This program is free software: you can redistribute it and/or modify # This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by # it under the terms of the GNU General Public License as published by
@ -20,25 +18,32 @@
# along with this program. If not, see <http://www.gnu.org/licenses/> # along with this program. If not, see <http://www.gnu.org/licenses/>
__author__ = "Cedric Bonhomme" __author__ = "Cedric Bonhomme"
__version__ = "$Revision: 0.1 $" __version__ = "$Revision: 0.3 $"
__date__ = "$Date: 2010/10/01 $" __date__ = "$Date: 2010/10/01 $"
__revision__ = "$Date: 2017/05/04 $"
__license__ = "GPLv3" __license__ = "GPLv3"
import base64 import base64
import itertools
from functools import reduce from functools import reduce
from typing import IO, List, Union, cast
def a2bits(chars): from PIL import Image
"""
Converts a string to its bits representation as a string of 0's and 1's. 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.
>>> a2bits("Hello World!") >>> a2bits("Hello World!")
'010010000110010101101100011011000110111100100000010101110110111101110010011011000110010000100001' '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):
""" 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. """Convert a string to its bits representation as a list of 0's and 1's.
>>> a2bits_list("Hello World!") >>> a2bits_list("Hello World!")
['01001000', ['01001000',
@ -56,55 +61,170 @@ def a2bits_list(chars):
>>> "".join(a2bits_list("Hello World!")) >>> "".join(a2bits_list("Hello World!"))
'010010000110010101101100011011000110111100100000010101110110111101110010011011000110010000100001' '010010000110010101101100011011000110111100100000010101110110111101110010011011000110010000100001'
""" """
return [bin(ord(x))[2:].rjust(8,"0") for x in chars] return [bin(ord(x))[2:].rjust(ENCODINGS[encoding], "0") for x in chars]
def bs(s):
""" def bs(s: int) -> str:
Converts an int to its bits representation as a string of 0's and 1's. """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) return str(s) if s <= 1 else bs(s >> 1) + str(s & 1)
def setlsb(component, bit):
""" 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) return component & ~1 | int(bit)
def n_at_a_time(items, n, fillvalue):
""" def n_at_a_time(items: List[int], n: int, fillvalue: str):
Returns an iterator which groups n items at a time. """Returns an iterator which groups n items at a time.
Any final partial tuple will be padded with the fillvalue Any final partial tuple will be padded with the fillvalue
>>> list(n_at_a_time([1, 2, 3, 4, 5], 2, 'X')) >>> list(n_at_a_time([1, 2, 3, 4, 5], 2, 'X'))
[(1, 2), (3, 4), (5, 'X')] [(1, 2), (3, 4), (5, 'X')]
""" """
it = iter(items) it = iter(items)
return its.izip_longest(*[it] * n, fillvalue=fillvalue) return itertools.zip_longest(*[it] * n, fillvalue=fillvalue)
def binary2base64(binary_file):
""" def binary2base64(binary_file: str) -> str:
Convert a binary file (OGG, executable, etc.) to a """Convert a binary file (OGG, executable, etc.) to a
printable string. printable string.
""" """
# Use mode = "rb" to read binary file # Use mode = "rb" to read binary file
fin = open(binary_file, "rb") with open(binary_file, "rb") as bin_file:
binary_data = fin.read() encoded_string = base64.b64encode(bin_file.read())
fin.close() return encoded_string.decode()
# Encode binary to base64 string (printable)
return base64.b64encode(binary_data)
"""fout = open(output_file, "w") def base642binary(b64_fname: str) -> bytes:
fout.write(b64_data) """Convert a printable string to a binary file."""
fout.close""" b64_fname += "==="
def base642binary(b64_fname):
"""
Convert a printable file to a binary file.
"""
# Read base64 string
#fin = open(b64_fname, "r")
#b64_str = fin.read()
#fin.close()
# Decode base64 string to original binary sound object
return base64.b64decode(b64_fname) return base64.b64decode(b64_fname)
def open_image(fname_or_instance: Union[str, IO[bytes], Image.Image]) -> Image.Image:
"""Opens an image and returns it.
:param fname_or_instance: Can be a path to the image (str),
a file-like object (IO[bytes]),
or a PIL Image instance.
"""
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):
# Determine expected pixel format based on mode
if self.encoded_image.mode == "RGBA":
r, g, b, *a = cast(
tuple[int, int, int, int], self.encoded_image.getpixel(coordinate)
)
else:
r, g, b, *a = cast(
tuple[int, int, int], 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):
# Tell mypy that this will be a 3- or 4-tuple of ints
pixel = cast(
tuple[int, int, int] | tuple[int, int, int, int],
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 :]
if self.close_file:
self.encoded_image.close()
return True
else:
return False

5
stegano/wav/__init__.py Normal file
View file

@ -0,0 +1,5 @@
#!/usr/bin/env python
from .wav import hide, reveal
__all__ = ["hide", "reveal"]

112
stegano/wav/wav.py Normal file
View file

@ -0,0 +1,112 @@
#!/usr/bin/env python
# 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
#
# 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.2 $"
__date__ = "$Date: 2010/10/01 $"
__revision__ = "$Date: 2017/02/06 $"
__license__ = "GPLv3"
import wave
from typing import IO, Union
from stegano import tools
def hide(
input_file: Union[str, IO[bytes]],
message: str,
output_file: Union[str, IO[bytes]],
encoding: str = "UTF-8",
):
"""
Hide a message (string) in a .wav audio file.
Use the lsb of each PCM encoded sample to hide the message string characters as ASCII values.
The first eight bits are used for message_length of the string.
"""
message_length = len(message)
assert message_length != 0, "message message_length is zero"
assert message_length < 255, "message is too long"
output = wave.open(output_file, "wb")
with wave.open(input_file, "rb") as input:
# get .wav params
nchannels, sampwidth, framerate, nframes, comptype, _ = input.getparams()
assert comptype == "NONE", "only uncompressed files are supported"
nsamples = nframes * nchannels
message_bits = f"{message_length:08b}" + "".join(
tools.a2bits_list(message, encoding)
)
assert len(message_bits) <= nsamples, "message is too long"
# copy over .wav params to output
output.setnchannels(nchannels)
output.setsampwidth(sampwidth)
output.setframerate(framerate)
# encode message in frames
frames = bytearray(input.readframes(nsamples))
for i in range(nsamples):
if i < len(message_bits):
if message_bits[i] == "0":
frames[i] = frames[i] & ~1
else:
frames[i] = frames[i] | 1
# write out
output.writeframes(frames)
def reveal(input_file: Union[str, IO[bytes]], encoding: str = "UTF-8"):
"""
Find a message in an image.
Check the lsb of each PCM encoded sample for hidden message characters (ASCII values).
The first eight bits are used for message_length of the string.
"""
message = ""
encoding_len = tools.ENCODINGS[encoding]
with wave.open(input_file, "rb") as input:
nchannels, _, _, nframes, comptype, _ = input.getparams()
assert comptype == "NONE", "only uncompressed files are supported"
nsamples = nframes * nchannels
frames = bytearray(input.readframes(nsamples))
# Read first 8 bits for message length
length_bits = ""
for i in range(8):
length_bits += str(frames[i] & 1)
message_length = int(length_bits, 2)
# Read message bits
message_bits = ""
for i in range(8, 8 + message_length * encoding_len):
message_bits += str(frames[i] & 1)
# Convert bits to string
chars = [
chr(int(message_bits[i : i + encoding_len], 2))
for i in range(0, len(message_bits), encoding_len)
]
message = "".join(chars)
return message

0
tests/__init__.py Normal file
View file

256
tests/expected-results/LFSR Normal file
View 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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,33 @@
561
1105
1729
2465
2821
6601
8911
10585
15841
29341
41041
46657
52633
62745
63973
75361
101101
115921
126217
162401
172081
188461
252601
278545
294409
314821
334153
340561
399001
410041
449065
488881
512461

View file

@ -0,0 +1,114 @@
4
6
8
9
10
12
14
15
16
18
20
21
22
24
25
26
27
28
30
32
33
34
35
36
38
39
40
42
44
45
46
48
49
50
51
52
54
55
56
57
58
60
62
63
64
65
66
68
69
70
72
74
75
76
77
78
80
81
82
84
85
86
87
88
90
91
92
93
94
95
96
98
99
100
102
104
105
106
108
110
111
112
114
115
116
117
118
119
120
121
122
123
124
125
126
128
129
130
132
133
134
135
136
138
140
141
142
143
144
145
146
147
148
150

View file

@ -0,0 +1,168 @@
2
3
5
7
11
13
17
19
23
29
31
37
41
43
47
53
59
61
67
71
73
79
83
89
97
101
103
107
109
113
127
131
137
139
149
151
157
163
167
173
179
181
191
193
197
199
211
223
227
229
233
239
241
251
257
263
269
271
277
281
283
293
307
311
313
317
331
337
347
349
353
359
367
373
379
383
389
397
401
409
419
421
431
433
439
443
449
457
461
463
467
479
487
491
499
503
509
521
523
541
547
557
563
569
571
577
587
593
599
601
607
613
617
619
631
641
643
647
653
659
661
673
677
683
691
701
709
719
727
733
739
743
751
757
761
769
773
787
797
809
811
821
823
827
829
839
853
857
859
863
877
881
883
887
907
911
919
929
937
941
947
953
967
971
977
983
991
997

View file

@ -0,0 +1,9 @@
3
5
17
257
65537
4294967297
18446744073709551617
340282366920938463463374607431768211457
115792089237316195423570985008687907853269984665640564039457584007913129639937

View file

@ -0,0 +1,20 @@
3
7
31
127
2047
8191
131071
524287
8388607
536870911
2147483647
137438953471
2199023255551
8796093022207
140737488355327
9007199254740991
576460752303423487
2305843009213693951
147573952589676412927
2361183241434822606847

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

File diff suppressed because it is too large Load diff

View 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)])

View file

@ -0,0 +1,54 @@
0
1
3
6
10
15
21
28
36
45
55
66
78
91
105
120
136
153
171
190
210
231
253
276
300
325
351
378
406
435
465
496
528
561
595
630
666
703
741
780
820
861
903
946
990
1035
1081
1128
1176
1225
1275
1326
1378
1431

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 151 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 400 KiB

View file

Before

Width:  |  Height:  |  Size: 464 KiB

After

Width:  |  Height:  |  Size: 464 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 3.8 MiB

After

Width:  |  Height:  |  Size: 3.8 MiB

Before After
Before After

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

109
tests/test_exifHeader.py Normal file
View file

@ -0,0 +1,109 @@
#!/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.2 $"
__date__ = "$Date: 2016/05/17 $"
__revision__ = "$Date: 2017/01/18 $"
__license__ = "GPLv3"
import io
import os
import unittest
from stegano import exifHeader
class TestEXIFHeader(unittest.TestCase):
def test_hide_empty_message(self):
"""Test hiding the empty string."""
exifHeader.hide(
"./tests/sample-files/20160505T130442.jpg", "./image.jpg", secret_message=""
)
clear_message = exifHeader.reveal("./image.jpg")
self.assertEqual(b"", clear_message)
def test_hide_and_reveal(self):
messages_to_hide = ["a", "foo", "Hello World!", ":Python:"]
for message in messages_to_hide:
exifHeader.hide(
"./tests/sample-files/20160505T130442.jpg",
"./image.jpg",
secret_message=message,
)
clear_message = exifHeader.reveal("./image.jpg")
self.assertEqual(message, clear_message.decode())
def test_with_image_without_exif_data(self):
exifHeader.hide(
"./tests/sample-files/Lenna.jpg", "./image.jpg", secret_message=""
)
clear_message = exifHeader.reveal("./image.jpg")
self.assertEqual(b"", clear_message)
def test_with_text_file(self):
text_file_to_hide = "./tests/sample-files/lorem_ipsum.txt"
with open(text_file_to_hide, "rb") as f:
message = f.read()
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):
exifHeader.hide(
"./tests/sample-files/Lenna.png", "./image.png", secret_message="Secret"
)
with self.assertRaises(ValueError):
exifHeader.reveal("./image.png")
def test_with_bytes(self):
outputBytes = io.BytesIO()
message = b"Secret"
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)
def tearDown(self):
try:
os.unlink("./image.jpg")
except Exception:
pass
try:
os.unlink("./image.png")
except Exception:
pass
if __name__ == "__main__":
unittest.main()

201
tests/test_generators.py Normal file
View file

@ -0,0 +1,201 @@
#!/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/03/01 $"
__revision__ = "$Date: 2017/03/01 $"
__license__ = "GPLv3"
import itertools
import unittest
import cv2
import numpy as np
from stegano.lsb 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),
)
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,
),
)
def test_eratosthenes(self):
"""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") 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") 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") 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") 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") 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 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()))
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 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()))
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()

248
tests/test_lsb.py Normal file
View file

@ -0,0 +1,248 @@
#!/usr/bin/env python
# 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
#
# 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.6 $"
__date__ = "$Date: 2016/04/13 $"
__revision__ = "$Date: 2022/01/04 $"
__license__ = "GPLv3"
import io
import os
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):
lsb.hide("./tests/sample-files/Lenna.png", "", generators.eratosthenes())
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,
generators.eratosthenes(),
encoding="UTF-32LE",
)
secret.save("./image.png")
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:"]
for message in messages_to_hide:
secret = lsb.hide(
"./tests/sample-files/transparent.png",
message,
generators.eratosthenes(),
)
secret.save("./image.png")
clear_message = lsb.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!"
lsb.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!"
# 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 = "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.assertRaisesRegex(
Exception, "The message you want to hide is too long:"
):
lsb.hide("./tests/sample-files/Lenna.png", message, generators.identity())
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")
with self.assertRaises(IndexError):
lsb.reveal("./image.png", generators.identity())
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 Exception:
pass
if __name__ == "__main__":
unittest.main()

64
tests/test_red.py Normal file
View file

@ -0,0 +1,64 @@
#!/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: 2016/05/19 $"
__license__ = "GPLv3"
import os
import unittest
from stegano import red
class TestRed(unittest.TestCase):
def test_hide_empty_message(self):
"""
Test hiding the empty string.
"""
with self.assertRaises(AssertionError):
red.hide("./tests/sample-files/Lenna.png", "")
def test_hide_and_reveal(self):
messages_to_hide = ["a", "foo", "Hello World!", ":Python:"]
for message in messages_to_hide:
secret = red.hide("./tests/sample-files/Lenna.png", message)
secret.save("./image.png")
clear_message = red.reveal("./image.png")
self.assertEqual(message, clear_message)
def test_with_too_long_message(self):
with open("./tests/sample-files/lorem_ipsum.txt") as f:
message = f.read()
with self.assertRaises(AssertionError):
red.hide("./tests/sample-files/Lenna.png", message)
def tearDown(self):
try:
os.unlink("./image.png")
except Exception:
pass
if __name__ == "__main__":
unittest.main()

View 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()

87
tests/test_tools.py Normal file
View file

@ -0,0 +1,87 @@
#!/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/22 $"
__revision__ = "$Date: 2017/02/22 $"
__license__ = "GPLv3"
import unittest
from stegano import tools
class TestTools(unittest.TestCase):
def test_a2bits(self):
bits = tools.a2bits("Hello World!")
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",
],
)
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",
],
)
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")])
def test_binary2base64(self):
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)

62
tests/test_wav.py Normal file
View file

@ -0,0 +1,62 @@
#!/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: 2016/05/19 $"
__license__ = "GPLv3"
import os
import unittest
from stegano import wav
class TestWav(unittest.TestCase):
def test_hide_empty_message(self):
"""
Test hiding the empty string.
"""
with self.assertRaises(AssertionError):
wav.hide("./tests/sample-files/free-software-song.wav", "", "./audio.wav")
def test_hide_and_reveal(self):
messages_to_hide = ["a", "foo", "Hello World!", ":Python:"]
for message in messages_to_hide:
wav.hide("./tests/sample-files/free-software-song.wav", message, "./audio.wav")
clear_message = wav.reveal("./audio.wav")
self.assertEqual(message, clear_message)
def test_with_too_long_message(self):
with open("./tests/sample-files/lorem_ipsum.txt") as f:
message = f.read()
with self.assertRaises(AssertionError):
wav.hide("./tests/sample-files/free-software-song.wav", message, "./audio.wav")
def tearDown(self):
try:
os.unlink("./audio.wav")
except Exception:
pass
if __name__ == "__main__":
unittest.main()