Compare commits

..

146 commits

Author SHA1 Message Date
Cédric Bonhomme
bd3238acfa
chg: [dependencies] Updated dependencies.
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-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
47 changed files with 1776 additions and 1734 deletions

View file

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

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=15 --max-line-length=127 --statistics
- name: Test with pytest
run: |
poetry run nose2 -v --pretty-assert
env:
testing: actions
# - name: Type check with mypy
# run: |
# poetry run mypy .

27
.github/workflows/release.yml vendored Normal file
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

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"]
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.1.0
hooks:
- id: fix-byte-order-marker
- id: trailing-whitespace
exclude: .md
- id: end-of-file-fixer
exclude: tests/.*
- repo: https://github.com/pypa/pip-audit
rev: v2.7.3
hooks:
- id: pip-audit

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

View file

@ -9,11 +9,13 @@
- Adrien Cosson - https://cosson.io - Adrien Cosson - https://cosson.io
- Andrew Roberts <andy.roberts.uk@gmail.com> - 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 - Maxwell Gerber - https://github.com/maxwellgerber
- Nejdet Çağdaş Yücesoy <nejdetyucesoy@gmail.com> - Nejdet Çağdaş Yücesoy <nejdetyucesoy@gmail.com>
- panni <panni@fragstore.net> - panni <panni@fragstore.net>
- Peter Justin <peter@peterjustin.me> - Peter Justin <peter@peterjustin.me>
- Christophe Goessen - https://github.com/cgoessen
- thundersparkf - https://github.com/thundersparkf - thundersparkf - https://github.com/thundersparkf
- Mickaël Schoentgen <mschoentgen@nuxeo.com>
And thank you to the testers! And thank you to the testers!

View file

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

View file

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

View file

@ -1 +0,0 @@

View file

@ -1,140 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Stegano - Stegano is a pure Python steganography module.
# Copyright (C) 2010-2021 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.8 $"
__date__ = "$Date: 2016/08/04 $"
__revision__ = "$Date: 2019/06/01 $"
__license__ = "GPLv3"
import argparse
try:
from stegano import lsb
except:
print("Install Stegano: pipx install Stegano")
from stegano import tools
def main():
parser = argparse.ArgumentParser(prog="stegano-lsb")
subparsers = parser.add_subparsers(help="sub-command help", dest="command")
# Subparser: Hide
parser_hide = subparsers.add_parser("hide", help="hide help")
# Original image
parser_hide.add_argument(
"-i",
"--input",
dest="input_image_file",
required=True,
help="Input image file.",
)
parser_hide.add_argument(
"-e",
"--encoding",
dest="encoding",
choices=tools.ENCODINGS.keys(),
default="UTF-8",
help="Specify the encoding of the message to hide."
+ " UTF-8 (default) or UTF-32LE.",
)
group_secret = parser_hide.add_mutually_exclusive_group(required=True)
# Non binary secret message to hide
group_secret.add_argument(
"-m", dest="secret_message", help="Your secret message to hide (non binary)."
)
# Binary secret message to hide
group_secret.add_argument(
"-f", dest="secret_file", help="Your secret to hide (Text or any binary file)."
)
# Image containing the secret
parser_hide.add_argument(
"-o",
"--output",
dest="output_image_file",
required=True,
help="Output image containing the secret.",
)
# Shift the message to hide
parser_hide.add_argument(
"-s", "--shift", dest="shift", default=0, help="Shift for the message to hide"
)
# Subparser: Reveal
parser_reveal = subparsers.add_parser("reveal", help="reveal help")
parser_reveal.add_argument(
"-i",
"--input",
dest="input_image_file",
required=True,
help="Input image file.",
)
parser_reveal.add_argument(
"-e",
"--encoding",
dest="encoding",
choices=tools.ENCODINGS.keys(),
default="UTF-8",
help="Specify the encoding of the message to reveal."
+ " UTF-8 (default) or UTF-32LE.",
)
parser_reveal.add_argument(
"-o",
dest="secret_binary",
help="Output for the binary secret (Text or any binary file).",
)
# Shift the message to reveal
parser_reveal.add_argument(
"-s", "--shift", dest="shift", default=0, help="Shift for the reveal"
)
arguments = parser.parse_args()
if arguments.command == "hide":
if arguments.secret_message != None:
secret = arguments.secret_message
elif arguments.secret_file != None:
secret = tools.binary2base64(arguments.secret_file)
img_encoded = lsb.hide(
arguments.input_image_file, secret, arguments.encoding, int(arguments.shift)
)
try:
img_encoded.save(arguments.output_image_file)
except Exception as e:
# If hide() returns an error (Too long message).
print(e)
elif arguments.command == "reveal":
secret = lsb.reveal(
arguments.input_image_file, arguments.encoding, int(arguments.shift)
)
if arguments.secret_binary != None:
data = tools.base642binary(secret)
with open(arguments.secret_binary, "wb") as f:
f.write(data)
else:
print(secret)

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,19 +9,13 @@
# #
# 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 = []
@ -40,8 +33,8 @@ source_suffix = ".rst"
master_doc = "index" master_doc = "index"
# General information about the project. # General information about the project.
project = u"Stegano" project = "Stegano"
copyright = u"2010-2021, Cédric Bonhomme" copyright = "2010-2025, Cédric Bonhomme"
author = "Cédric Bonhomme <cedric@cedricbonhomme.org>" author = "Cédric Bonhomme <cedric@cedricbonhomme.org>"
# The version info for the project you're documenting, acts as replacement for # The version info for the project you're documenting, acts as replacement for
@ -49,9 +42,9 @@ author = "Cédric Bonhomme <cedric@cedricbonhomme.org>"
# built documents. # built documents.
# #
# The short X.Y version. # The short X.Y version.
version = "0.9" version = "0.11"
# The full version, including alpha/beta/rc tags. # The full version, including alpha/beta/rc tags.
release = "0.9.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.
@ -166,12 +159,12 @@ latex_engine = "pdflatex"
# 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"Stegano Documentation", u"Cédric Bonhomme", "howto"), ("index", "Stgan.tex", "Stegano Documentation", "Cédric Bonhomme", "howto"),
] ]
latex_show_urls = True latex_show_urls = True
latex_show_pagerefs = True latex_show_pagerefs = True
ADDITIONAL_PREAMBLE = """ ADDITIONAL_PREAMBLE = r"""
\setcounter{tocdepth}{3} \setcounter{tocdepth}{3}
""" """

View file

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

View file

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

View file

@ -2,14 +2,14 @@ Using Stegano as a Python module
================================ ================================
You can find more examples in the You can find more examples in the
`unit tests directory <https://git.sr.ht/~cedric/stegano/tree/master/tests>`_. `unit tests directory <https://github.com/cedricbonhomme/Stegano/tree/master/tests>`_.
LSB method LSB method
---------- ----------
.. code-block:: python .. code-block:: python
Python 3.10.0 (default, Oct 17 2021, 09:02:57) [GCC 11.2.0] on linux Python 3.11.0 (main, Oct 31 2022, 15:15:22) [GCC 12.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information. Type "help", "copyright", "credits" or "license" for more information.
>>> from stegano import lsb >>> from stegano import lsb
>>> secret = lsb.hide("./tests/sample-files/Lenna.png", "Hello world!") >>> secret = lsb.hide("./tests/sample-files/Lenna.png", "Hello world!")
@ -26,28 +26,33 @@ Sets are used in order to select the pixels where the message will be hidden.
.. code-block:: python .. code-block:: python
Python 3.10.0 (default, Oct 17 2021, 09:02:57) [GCC 11.2.0] on linux Python 3.11.0 (main, Oct 31 2022, 15:15:22) [GCC 12.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information. Type "help", "copyright", "credits" or "license" for more information.
>>> from stegano import lsbset >>> from stegano import lsb
>>> from stegano.lsbset import generators >>> from stegano.lsb import generators
# Hide a secret with the Sieve of Eratosthenes # Hide a secret with the Sieve of Eratosthenes
>>> secret_message = "Hello World!" >>> secret_message = "Hello World!"
>>> secret_image = lsbset.hide("./tests/sample-files/Lenna.png", >>> secret_image = lsb.hide("./tests/sample-files/Lenna.png", secret_message, generators.eratosthenes())
secret_message,
generators.eratosthenes())
>>> secret_image.save("./image.png") >>> secret_image.save("./image.png")
# Try to decode with another generator # Try to decode with another generator
>>> message = lsbset.reveal("./image.png", generators.fibonacci()) >>> message = lsb.reveal("./image.png", generators.fibonacci())
Traceback (most recent call last): Traceback (most recent call last):
File "<stdin>", line 1, in <module> File "/Users/flavien/.local/share/virtualenvs/Stegano-sY_cwr69/bin/stegano-lsb", line 6, in <module>
File "/home/cedric/projects/Stegano/stegano/lsbset/lsbset.py", line 111, in reveal sys.exit(main())
for color in img_list[generated_number]: File "/Users/flavien/Perso/dev/Stegano/bin/lsb.py", line 190, in main
IndexError: list index out of range 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 # Decode with Eratosthenes
>>> message = lsbset.reveal("./image.png", generators.eratosthenes()) >>> message = lsb.reveal("./image.png", generators.eratosthenes())
>>> message >>> message
'Hello World!' 'Hello World!'
@ -107,7 +112,7 @@ For JPEG and TIFF images.
.. code-block:: python .. code-block:: python
Python 3.10.0 (default, Oct 17 2021, 09:02:57) [GCC 11.2.0] on linux Python 3.11.0 (main, Oct 31 2022, 15:15:22) [GCC 12.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information. Type "help", "copyright", "credits" or "license" for more information.
>>> from stegano import exifHeader >>> from stegano import exifHeader
>>> secret = exifHeader.hide("./tests/sample-files/20160505T130442.jpg", >>> secret = exifHeader.hide("./tests/sample-files/20160505T130442.jpg",

View file

@ -12,52 +12,56 @@ Display help
.. code-block:: bash .. code-block:: bash
$ stegano-lsb --help $ stegano-lsb --help
usage: stegano-lsb [-h] {hide,reveal} ... usage: stegano-lsb [-h] {hide,reveal,list-generators} ...
positional arguments: positional arguments:
{hide,reveal} sub-command help {hide,reveal,list-generators}
hide hide help sub-command help
reveal reveal help hide hide help
reveal reveal help
list-generators list-generators help
optional arguments: options:
-h, --help show this help message and exit -h, --help show this help message and exit
.. code-block:: bash .. code-block:: bash
$ stegano-lsb hide --help $ stegano-lsb hide --help
usage: stegano-lsb hide [-h] -i INPUT_IMAGE_FILE [-e {UTF-8,UTF-32LE}] 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
(-m SECRET_MESSAGE | -f SECRET_FILE) -o
OUTPUT_IMAGE_FILE
optional arguments: options:
-h, --help show this help message and exit -h, --help show this help message and exit
-i INPUT_IMAGE_FILE, --input INPUT_IMAGE_FILE -i INPUT_IMAGE_FILE, --input INPUT_IMAGE_FILE
Input image file. Input image file.
-e {UTF-8,UTF-32LE}, --encoding {UTF-8,UTF-32LE} -e {UTF-8,UTF-32LE}, --encoding {UTF-8,UTF-32LE}
Specify the encoding of the message to hide. UTF-8 Specify the encoding of the message to hide. UTF-8 (default) or UTF-32LE.
(default) or UTF-32LE. -g [GENERATOR_FUNCTION ...], --generator [GENERATOR_FUNCTION ...]
-m SECRET_MESSAGE Your secret message to hide (non binary). Generator (with optional arguments)
-f SECRET_FILE Your secret to hide (Text or any binary file). -s SHIFT, --shift SHIFT
-o OUTPUT_IMAGE_FILE, --output OUTPUT_IMAGE_FILE 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. Output image containing the secret.
.. code-block:: bash .. code-block:: bash
$ stegano-lsb reveal --help $ stegano-lsb reveal --help
usage: stegano-lsb reveal [-h] -i INPUT_IMAGE_FILE [-e {UTF-8,UTF-32LE}] usage: stegano-lsb reveal [-h] -i INPUT_IMAGE_FILE [-e {UTF-8,UTF-32LE}] [-g [GENERATOR_FUNCTION ...]] [-s SHIFT] [-o SECRET_BINARY]
[-o SECRET_BINARY]
optional arguments: options:
-h, --help show this help message and exit -h, --help show this help message and exit
-i INPUT_IMAGE_FILE, --input INPUT_IMAGE_FILE -i INPUT_IMAGE_FILE, --input INPUT_IMAGE_FILE
Input image file. Input image file.
-e {UTF-8,UTF-32LE}, --encoding {UTF-8,UTF-32LE} -e {UTF-8,UTF-32LE}, --encoding {UTF-8,UTF-32LE}
Specify the encoding of the message to reveal. UTF-8 Specify the encoding of the message to reveal. UTF-8 (default) or UTF-32LE.
(default) or UTF-32LE. -g [GENERATOR_FUNCTION ...], --generator [GENERATOR_FUNCTION ...]
-o SECRET_BINARY Output for the binary secret (Text or any binary Generator (with optional arguments)
file). -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 Hide and reveal a text message
@ -92,63 +96,40 @@ Hide and reveal a binary file
The command ``stegano-lsb-set``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Sets are used in order to select the pixels where the message will be hidden. Sets are used in order to select the pixels where the message will be hidden.
Hide and reveal a text message Hide and reveal a text message with set
------------------------------ ---------------------------------------
.. code-block:: bash .. code-block:: bash
# Hide the message with the Sieve of Eratosthenes # Hide the message with the Sieve of Eratosthenes
$ stegano-lsb-set hide -i ./tests/sample-files/Montenach.png --generator eratosthenes -m 'Joyeux Noël!' -o ./surprise.png $ stegano-lsb hide -i ./tests/sample-files/Montenach.png --generator eratosthenes -m 'Joyeux Noël!' -o ./surprise.png
# Try to reveal with Mersenne numbers # Try to reveal with Mersenne numbers
$ stegano-lsb-set reveal --generator mersenne -i ./surprise.png $ stegano-lsb reveal --generator mersenne -i ./surprise.png
# Try to reveal with fermat numbers # Try to reveal with fermat numbers
$ stegano-lsb-set reveal --generator fermat -i ./surprise.png $ stegano-lsb reveal --generator fermat -i ./surprise.png
# Try to reveal with carmichael numbers # Try to reveal with carmichael numbers
$ stegano-lsb-set reveal --generator carmichael -i ./surprise.png $ stegano-lsb reveal --generator carmichael -i ./surprise.png
# Try to reveal with Sieve of Eratosthenes # Try to reveal with Sieve of Eratosthenes
$ stegano-lsb-set reveal --generator eratosthenes -i ./surprise.png $ stegano-lsb reveal --generator eratosthenes -i ./surprise.png
An other example:
.. code-block:: bash
# Hide the message - LSB with a set defined by the identity function (f(x) = x).
stegano-lsb-set hide -i ./tests/sample-files/Montenach.png --generator identity -m 'I like steganography.' -o ./enc-identity.png
# Hide the message - LSB only.
stegano-lsb hide -i ./tests/sample-files/Montenach.png -m 'I like steganography.' -o ./enc.png
# Check if the two generated files are the same.
sha1sum ./enc-identity.png ./enc.png
# The output of lsb is given to lsb-set.
stegano-lsb-set reveal -i ./enc.png --generator identity
# The output of lsb-set is given to lsb.
stegano-lsb reveal -i ./enc-identity.png
Sometimes it can be useful to skip the first values of a set. For example if you want 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 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. (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``: In this case, simply use the optional arguments ``--shift`` or ``-s``:
.. code-block:: bash .. code-block:: bash
stegano-lsb-set reveal -i ./tests/sample-files/Lenna.png --generator fibonacci --shift 7 $ 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 List all available generators
@ -156,7 +137,7 @@ List all available generators
.. code-block:: bash .. code-block:: bash
$ stegano-lsb-set list-generators $ stegano-lsb list-generators
Generator id: Generator id:
ackermann ackermann
Desciption: Desciption:

View file

@ -7,7 +7,7 @@ Parity
.. code-block:: bash .. code-block:: bash
# Hide the message with Sieve of Eratosthenes # Hide the message with Sieve of Eratosthenes
stegano-lsb-set hide -i ./tests/sample-files/20160505T130442.jpg -o ./surprise.png --generator eratosthenes -m 'Very important message.' stegano-lsb hide -i ./tests/sample-files/20160505T130442.jpg -o ./surprise.png --generator eratosthenes -m 'Very important message.'
# Steganalysis of the original photo # Steganalysis of the original photo
stegano-steganalysis-parity -i ./tests/sample-files/20160505T130442.jpg -o ./surprise_st_original.png stegano-steganalysis-parity -i ./tests/sample-files/20160505T130442.jpg -o ./surprise_st_original.png
@ -16,4 +16,4 @@ Parity
stegano-steganalysis-parity -i ./surprise.png -o ./surprise_st_secret.png stegano-steganalysis-parity -i ./surprise.png -o ./surprise_st_secret.png
# Reveal with Sieve of Eratosthenes # Reveal with Sieve of Eratosthenes
stegano-lsb-set reveal -i ./surprise.png --generator eratosthenes stegano-lsb reveal -i ./surprise.png --generator eratosthenes

View file

@ -1,13 +0,0 @@
[mypy]
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

1480
poetry.lock generated

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

View file

@ -1,8 +1,6 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*-
# Stegano - Stegano is a pure Python steganography module. # Stegano - Stegano is a pure Python steganography module.
# Copyright (C) 2010-2021 Cédric Bonhomme - https://www.cedricbonhomme.org # Copyright (C) 2010-2025 Cédric Bonhomme - https://www.cedricbonhomme.org
# #
# For more information : https://github.com/cedricbonhomme/Stegano # For more information : https://github.com/cedricbonhomme/Stegano
# #
@ -26,18 +24,19 @@ __revision__ = "$Date: 2019/06/04 $"
__license__ = "GPLv3" __license__ = "GPLv3"
import inspect import inspect
import crayons import crayons
try: try:
from stegano import lsbset from stegano import lsb
from stegano.lsbset import generators from stegano.lsb import generators
except: except Exception:
print("Install stegano: pipx install Stegano") print("Install stegano: pipx install Stegano")
from stegano import tools
import argparse import argparse
from stegano import tools
class ValidateGenerator(argparse.Action): class ValidateGenerator(argparse.Action):
def __call__(self, parser, args, values, option_string=None): def __call__(self, parser, args, values, option_string=None):
@ -54,8 +53,10 @@ class ValidateGenerator(argparse.Action):
def main(): def main():
parser = argparse.ArgumentParser(prog="stegano-lsb-set") parser = argparse.ArgumentParser(prog="stegano-lsb")
subparsers = parser.add_subparsers(help="sub-command help", dest="command") subparsers = parser.add_subparsers(
help="sub-command help", dest="command", required=True
)
# Subparser: Hide # Subparser: Hide
parser_hide = subparsers.add_parser("hide", help="hide help") parser_hide = subparsers.add_parser("hide", help="hide help")
@ -74,7 +75,7 @@ def main():
choices=tools.ENCODINGS.keys(), choices=tools.ENCODINGS.keys(),
default="UTF-8", default="UTF-8",
help="Specify the encoding of the message to hide." help="Specify the encoding of the message to hide."
+ " UTF-8 (default) or UTF-32LE.", " UTF-8 (default) or UTF-32LE.",
) )
# Generator # Generator
@ -84,9 +85,12 @@ def main():
dest="generator_function", dest="generator_function",
action=ValidateGenerator, action=ValidateGenerator,
nargs="*", nargs="*",
required=True, required=False,
default=None,
help="Generator (with optional arguments)", help="Generator (with optional arguments)",
) )
# Shift the message to hide
parser_hide.add_argument( parser_hide.add_argument(
"-s", "--shift", dest="shift", default=0, help="Shift for the generator" "-s", "--shift", dest="shift", default=0, help="Shift for the generator"
) )
@ -126,17 +130,21 @@ def main():
choices=tools.ENCODINGS.keys(), choices=tools.ENCODINGS.keys(),
default="UTF-8", default="UTF-8",
help="Specify the encoding of the message to reveal." help="Specify the encoding of the message to reveal."
+ " UTF-8 (default) or UTF-32LE.", " UTF-8 (default) or UTF-32LE.",
) )
# Generator
parser_reveal.add_argument( parser_reveal.add_argument(
"-g", "-g",
"--generator", "--generator",
dest="generator_function", dest="generator_function",
action=ValidateGenerator, action=ValidateGenerator,
nargs="*", nargs="*",
required=True, required=False,
help="Generator (with optional arguments)", help="Generator (with optional arguments)",
) )
# Shift the message to reveal
parser_reveal.add_argument( parser_reveal.add_argument(
"-s", "--shift", dest="shift", default=0, help="Shift for the generator" "-s", "--shift", dest="shift", default=0, help="Shift for the generator"
) )
@ -147,46 +155,44 @@ def main():
) )
# Subparser: List generators # Subparser: List generators
parser_list_generators = subparsers.add_parser( subparsers.add_parser("list-generators", help="list-generators help")
"list-generators", help="list-generators help"
)
arguments = parser.parse_args() arguments = parser.parse_args()
if arguments.command != "list-generators": if arguments.command != "list-generators":
try: if not arguments.generator_function:
arguments.generator_function[0] generator = None
except AttributeError: else:
print("You must specify the name of a generator.") try:
parser.print_help() if arguments.generator_function[0] == "LFSR":
exit(1) # 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])()
try: except AttributeError:
if arguments.generator_function[0] == "LFSR": print(f"Unknown generator: {arguments.generator_function}")
# Compute the size of the image for use by the LFSR generator if needed exit(1)
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 as e:
print("Unknown generator: {}".format(arguments.generator_function))
exit(1)
if arguments.command == "hide": if arguments.command == "hide":
if arguments.secret_message != None: if arguments.secret_message is not None:
secret = arguments.secret_message secret = arguments.secret_message
elif arguments.secret_file != "": elif arguments.secret_file != "":
secret = tools.binary2base64(arguments.secret_file) secret = tools.binary2base64(arguments.secret_file)
img_encoded = lsbset.hide( img_encoded = lsb.hide(
arguments.input_image_file, secret, generator, int(arguments.shift) image=arguments.input_image_file,
message=secret,
generator=generator,
shift=int(arguments.shift),
encoding=arguments.encoding,
) )
try: try:
img_encoded.save(arguments.output_image_file) img_encoded.save(arguments.output_image_file)
@ -196,15 +202,18 @@ def main():
elif arguments.command == "reveal": elif arguments.command == "reveal":
try: try:
secret = lsbset.reveal( secret = lsb.reveal(
arguments.input_image_file, generator, int(arguments.shift) encoded_image=arguments.input_image_file,
generator=generator,
shift=int(arguments.shift),
encoding=arguments.encoding,
) )
except IndexError: except IndexError:
print("Impossible to detect message.") print("Impossible to detect message.")
exit(0) exit(0)
if arguments.secret_binary != None: if arguments.secret_binary is not None:
data = tools.base642binary(secret) data = tools.base642binary(secret)
with open(arguments.secret_binary, "w") as f: with open(arguments.secret_binary, "wb") as f:
f.write(data) f.write(data)
else: else:
print(secret) print(secret)
@ -213,6 +222,6 @@ def main():
all_generators = inspect.getmembers(generators, inspect.isfunction) all_generators = inspect.getmembers(generators, inspect.isfunction)
for generator in all_generators: for generator in all_generators:
print("Generator id:") print("Generator id:")
print(" {}".format(crayons.green(generator[0], bold=True))) print(f" {crayons.green(generator[0], bold=True)}")
print("Desciption:") print("Desciption:")
print(" {}".format(generator[1].__doc__)) print(f" {generator[1].__doc__}")

View file

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

View file

@ -1,8 +1,6 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*-
# Stegano - Stegano is a pure Python steganography module. # Stegano - Stegano is a pure Python steganography module.
# Copyright (C) 2010-2021 Cédric Bonhomme - https://www.cedricbonhomme.org # Copyright (C) 2010-2025 Cédric Bonhomme - https://www.cedricbonhomme.org
# #
# For more information : https://github.com/cedricbonhomme/Stegano # For more information : https://github.com/cedricbonhomme/Stegano
# #
@ -28,7 +26,7 @@ import argparse
try: try:
from stegano import red from stegano import red
except: except Exception:
print("Install stegano: sudo pip install Stegano") print("Install stegano: sudo pip install Stegano")

View file

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

View file

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

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. # Stegano - Stegano is a pure Python steganography module.
# Copyright (C) 2010-2021 Cédric Bonhomme - https://www.cedricbonhomme.org # Copyright (C) 2010-2025 Cédric Bonhomme - https://www.cedricbonhomme.org
# #
# For more information : https://git.sr.ht/~cedric/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
@ -26,6 +24,7 @@ __revision__ = "$Date: 2017/01/18 $"
__license__ = "GPLv3" __license__ = "GPLv3"
import piexif import piexif
from stegano import tools from stegano import tools
@ -37,11 +36,11 @@ def hide(
img_format=None, img_format=None,
): ):
"""Hide a message (string) in an image.""" """Hide a message (string) in an image."""
from zlib import compress
from base64 import b64encode from base64 import b64encode
from zlib import compress
if secret_file is not None: if secret_file is not None:
with open(secret_file, "r") as f: with open(secret_file) as f:
secret_message = f.read() secret_message = f.read()
try: try:

View file

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

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. # Stegano - Stegano is a pure Python steganography module.
# Copyright (C) 2010-2021 Cédric Bonhomme - https://www.cedricbonhomme.org # Copyright (C) 2010-2025 Cédric Bonhomme - https://www.cedricbonhomme.org
# #
# For more information : https://git.sr.ht/~cedric/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
@ -26,15 +24,15 @@ __revision__ = "$Date: 2021/11/29 $"
__license__ = "GPLv3" __license__ = "GPLv3"
import itertools import itertools
import math
from typing import Any, Dict, Iterator, List
import cv2 import cv2
import numpy as np import numpy as np
import math
from typing import Dict, Iterator, List, Any
def identity() -> Iterator[int]: def identity() -> Iterator[int]:
"""f(x) = x """f(x) = x"""
"""
n = 0 n = 0
while True: while True:
yield n yield n
@ -74,7 +72,7 @@ def eratosthenes() -> Iterator[int]:
"""Generate the prime numbers with the sieve of Eratosthenes. """Generate the prime numbers with the sieve of Eratosthenes.
https://oeis.org/A000040 https://oeis.org/A000040
""" """
d = {} # type: Dict[int, List[int]] d: Dict[int, List[int]] = {}
for i in itertools.count(2): for i in itertools.count(2):
if i in d: if i in d:
for j in d[i]: for j in d[i]:
@ -91,8 +89,7 @@ def composite() -> Iterator[int]:
""" """
p1 = 3 p1 = 3
for p2 in eratosthenes(): for p2 in eratosthenes():
for n in range(p1 + 1, p2): yield from range(p1 + 1, p2)
yield n
p1 = p2 p1 = p2
@ -110,8 +107,7 @@ def carmichael() -> Iterator[int]:
def ackermann_slow(m: int, n: int) -> int: def ackermann_slow(m: int, n: int) -> int:
"""Ackermann number. """Ackermann number."""
"""
if m == 0: if m == 0:
return n + 1 return n + 1
elif n == 0: elif n == 0:
@ -121,8 +117,7 @@ def ackermann_slow(m: int, n: int) -> int:
def ackermann_naive(m: int) -> Iterator[int]: def ackermann_naive(m: int) -> Iterator[int]:
"""Naive Ackermann encapsulated in a generator. """Naive Ackermann encapsulated in a generator."""
"""
n = 0 n = 0
while True: while True:
yield ackermann_slow(m, n) yield ackermann_slow(m, n)
@ -130,8 +125,7 @@ def ackermann_naive(m: int) -> Iterator[int]:
def ackermann_fast(m: int, n: int) -> int: def ackermann_fast(m: int, n: int) -> int:
"""Ackermann number. """Ackermann number."""
"""
while m >= 4: while m >= 4:
if n == 0: if n == 0:
n = 1 n = 1
@ -149,8 +143,7 @@ def ackermann_fast(m: int, n: int) -> int:
def ackermann(m: int) -> Iterator[int]: def ackermann(m: int) -> Iterator[int]:
"""Ackermann encapsulated in a generator. """Ackermann encapsulated in a generator."""
"""
n = 0 n = 0
while True: while True:
yield ackermann_fast(m, n) yield ackermann_fast(m, n)
@ -168,8 +161,7 @@ def fibonacci() -> Iterator[int]:
def log_gen() -> Iterator[int]: def log_gen() -> Iterator[int]:
"""Logarithmic generator. """Logarithmic generator."""
"""
y = 1 y = 1
while True: while True:
adder = max(1, math.pow(10, int(math.log10(y)))) adder = max(1, math.pow(10, int(math.log10(y))))
@ -231,7 +223,7 @@ def LFSR(m: int) -> Iterator[int]:
# Add the feedback bit # Add the feedback bit
state.insert(0, feedback) state.insert(0, feedback)
# Convert the registers to an int # Convert the registers to an int
out = sum([e * (2 ** i) for i, e in enumerate(state)]) out = sum(e * (2**i) for i, e in enumerate(state))
yield out yield out
@ -246,13 +238,15 @@ def shi_tomashi(
""" """
image = cv2.imread(image_path) image = cv2.imread(image_path)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
corners: np.signedinteger[Any] = cv2.goodFeaturesToTrack( corners: np.ndarray = cv2.goodFeaturesToTrack(
gray, max_corners, quality, min_distance gray, max_corners, quality, min_distance
) )
corners = np.int0(corners) corners_int: np.ndarray[Any, np.dtype[np.signedinteger[Any]]] = np.array(
np.intp(corners)
)
i = 0 i = 0
while True: while True:
x, y = corners[i].ravel() x, y = corners_int[i].ravel()
# Compute the pixel number with top left of image as origin # Compute the pixel number with top left of image as origin
# using coordinates of the corner. # using coordinates of the corner.
# (y * number of pixels a row) + pixels left in last row # (y * number of pixels a row) + pixels left in last row

136
stegano/lsb/lsb.py Executable file → Normal 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. # Stegano - Stegano is a pure Python steganography module.
# Copyright (C) 2010-2021 Cédric Bonhomme - https://www.cedricbonhomme.org # Copyright (C) 2010-2025 Cédric Bonhomme - https://www.cedricbonhomme.org
# #
# For more information : https://git.sr.ht/~cedric/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,111 +18,73 @@
# 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.4 $" __version__ = "$Revision: 0.7 $"
__date__ = "$Date: 2016/08/04 $" __date__ = "$Date: 2016/03/13 $"
__revision__ = "$Date: 2019/06/01 $" __revision__ = "$Date: 2019/05/31 $"
__license__ = "GPLv3" __license__ = "GPLv3"
from typing import IO, Union from typing import IO, Iterator, Union
from stegano import tools from stegano import tools
from .generators import identity
def hide( def hide(
input_image: Union[str, IO[bytes]], image: Union[str, IO[bytes]],
message: str, message: str,
encoding: str = "UTF-8", generator: Union[None, Iterator[int]] = None,
shift: int = 0, shift: int = 0,
encoding: str = "UTF-8",
auto_convert_rgb: bool = False, auto_convert_rgb: bool = False,
): ):
"""Hide a message (string) in an image with the """Hide a message (string) in an image with the
LSB (Least Significant Bit) technique. LSB (Least Significant Bit) technique.
""" """
message_length = len(message) hider = tools.Hider(image, message, encoding, auto_convert_rgb)
assert message_length != 0, "message length is zero" width = hider.encoded_image.width
img = tools.open_image(input_image) if not generator:
generator = identity()
if img.mode not in ["RGB", "RGBA"]: while shift != 0:
if not auto_convert_rgb: next(generator)
print("The mode of the image is not RGB. Mode is {}".format(img.mode)) shift -= 1
answer = input("Convert the image to RGB ? [Y / n]\n") or "Y"
if answer.lower() == "n":
raise Exception("Not a RGB image.")
img = img.convert("RGB")
encoded = img.copy() while hider.encode_another_pixel():
width, height = img.size generated_number = next(generator)
index = 0
message = str(message_length) + ":" + str(message) col = generated_number % width
message_bits = "".join(tools.a2bits_list(message, encoding)) row = int(generated_number / width)
message_bits += "0" * ((3 - (len(message_bits) % 3)) % 3)
npixels = width * height hider.encode_pixel((col, row))
len_message_bits = len(message_bits)
if len_message_bits > npixels * 3:
raise Exception(
"The message you want to hide is too long: {}".format(message_length)
)
for row in range(height):
for col in range(width):
if shift != 0:
shift -= 1
continue
if index + 3 <= len_message_bits:
# Get the colour component. return hider.encoded_image
pixel = img.getpixel((col, row))
r = pixel[0]
g = pixel[1]
b = pixel[2]
# Change the Least Significant Bit of each colour component.
r = tools.setlsb(r, message_bits[index])
g = tools.setlsb(g, message_bits[index + 1])
b = tools.setlsb(b, message_bits[index + 2])
# Save the new pixel
if img.mode == "RGBA":
encoded.putpixel((col, row), (r, g, b, pixel[3]))
else:
encoded.putpixel((col, row), (r, g, b))
index += 3
else:
img.close()
return encoded
def reveal(input_image: Union[str, IO[bytes]], encoding: str = "UTF-8", shift: int = 0): 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).""" """Find a message in an image (with the LSB technique)."""
img = tools.open_image(input_image) revealer = tools.Revealer(encoded_image, encoding, close_file)
width, height = img.size width = revealer.encoded_image.width
buff, count = 0, 0
bitab = []
limit = None
for row in range(height):
for col in range(width):
if shift != 0:
shift -= 1
continue
# pixel = [r, g, b] or [r,g,b,a]
pixel = img.getpixel((col, row))
if img.mode == "RGBA":
pixel = pixel[:3] # ignore the alpha
for color in pixel:
buff += (color & 1) << (tools.ENCODINGS[encoding] - 1 - count)
count += 1
if count == tools.ENCODINGS[encoding]:
bitab.append(chr(buff))
buff, count = 0, 0
if bitab[-1] == ":" and limit is None:
try:
limit = int("".join(bitab[:-1]))
except Exception:
pass
if len(bitab) - len(str(limit)) - 1 == limit: if not generator:
img.close() generator = identity()
return "".join(bitab)[len(str(limit)) + 1 :]
while shift != 0:
next(generator)
shift -= 1
while True:
generated_number = next(generator)
col = generated_number % width
row = int(generated_number / width)
if revealer.decode_pixel((col, row)):
return revealer.secret_message

View file

@ -1,4 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from .lsbset import *

View file

@ -1,136 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Stegano - Stegano is a pure Python steganography module.
# Copyright (C) 2010-2021 Cédric Bonhomme - https://www.cedricbonhomme.org
#
# For more information : https://git.sr.ht/~cedric/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 PIL import Image
from stegano import tools
def hide(
input_image: Union[str, IO[bytes]],
message: str,
generator: Iterator[int],
shift: int = 0,
encoding: str = "UTF-8",
auto_convert_rgb: bool = False,
):
"""Hide a message (string) in an image with the
LSB (Least Significant Bit) technique.
"""
message_length = len(message)
assert message_length != 0, "message length is zero"
img = tools.open_image(input_image)
if img.mode not in ["RGB", "RGBA"]:
if not auto_convert_rgb:
print("The mode of the image is not RGB. Mode is {}".format(img.mode))
answer = input("Convert the image to RGB ? [Y / n]\n") or "Y"
if answer.lower() == "n":
raise Exception("Not a RGB image.")
img = img.convert("RGB")
img_list = list(img.getdata())
width, height = img.size
index = 0
message = str(message_length) + ":" + str(message)
message_bits = "".join(tools.a2bits_list(message, encoding))
message_bits += "0" * ((3 - (len(message_bits) % 3)) % 3)
npixels = width * height
len_message_bits = len(message_bits)
if len_message_bits > npixels * 3:
raise Exception(
"The message you want to hide is too long: {}".format(message_length)
)
while shift != 0:
next(generator)
shift -= 1
while index + 3 <= len_message_bits:
generated_number = next(generator)
r, g, b, *a = img_list[generated_number]
# Change the Least Significant Bit of each colour component.
r = tools.setlsb(r, message_bits[index])
g = tools.setlsb(g, message_bits[index + 1])
b = tools.setlsb(b, message_bits[index + 2])
# Save the new pixel
if img.mode == "RGBA":
img_list[generated_number] = (r, g, b, *a)
else:
img_list[generated_number] = (r, g, b)
index += 3
# create empty new image of appropriate format
encoded = Image.new(img.mode, (img.size))
# insert saved data into the image
encoded.putdata(img_list)
return encoded
def reveal(
input_image: Union[str, IO[bytes]],
generator: Iterator[int],
shift: int = 0,
encoding: str = "UTF-8",
):
"""Find a message in an image (with the LSB technique)."""
img = tools.open_image(input_image)
img_list = list(img.getdata())
width, height = img.size
buff, count = 0, 0
bitab = []
limit = None
while shift != 0:
next(generator)
shift -= 1
while True:
generated_number = next(generator)
# color = [r, g, b]
for color in img_list[generated_number][:3]: # ignore the alpha
buff += (color & 1) << (tools.ENCODINGS[encoding] - 1 - count)
count += 1
if count == tools.ENCODINGS[encoding]:
bitab.append(chr(buff))
buff, count = 0, 0
if bitab[-1] == ":" and limit is None:
if "".join(bitab[:-1]).isdigit():
limit = int("".join(bitab[:-1]))
else:
raise IndexError("Impossible to detect message.")
if len(bitab) - len(str(limit)) - 1 == limit:
return "".join(bitab)[len(str(limit)) + 1 :]

View file

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

View file

@ -1,10 +1,8 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*-
# Stegano - Stéganô is a basic Python Steganography module. # Stegano - Stéganô is a basic Python Steganography module.
# Copyright (C) 2010-2021 Cédric Bonhomme - https://www.cedricbonhomme.org # Copyright (C) 2010-2024 Cédric Bonhomme - https://www.cedricbonhomme.org
# #
# For more information : https://git.sr.ht/~cedric/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

View file

@ -1,5 +1 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*-
from .parity import *
from .statistics import *

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. # Stegano - Stegano is a pure Python steganography module.
# Copyright (C) 2010-2021 Cédric Bonhomme - https://www.cedricbonhomme.org # Copyright (C) 2010-2025 Cédric Bonhomme - https://www.cedricbonhomme.org
# #
# For more information : https://git.sr.ht/~cedric/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
@ -36,7 +34,10 @@ def steganalyse(img: Image.Image) -> Image.Image:
width, height = img.size width, height = img.size
for row in range(height): for row in range(height):
for col in range(width): for col in range(width):
r, g, b = img.getpixel((col, row))[0:3] if pixel := 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:

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. # Stegano - Stegano is a pure Python steganography module.
# Copyright (C) 2010-2021 Cédric Bonhomme - https://www.cedricbonhomme.org # Copyright (C) 2010-2025 Cédric Bonhomme - https://www.cedricbonhomme.org
# #
# For more information : https://git.sr.ht/~cedric/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
@ -26,9 +24,7 @@ __revision__ = "$Date: 2021/11/01 $"
__license__ = "GPLv3" __license__ = "GPLv3"
import typing import typing
from collections import Counter, OrderedDict
from collections import Counter
from collections import OrderedDict
def steganalyse(img): def steganalyse(img):
@ -36,7 +32,7 @@ def steganalyse(img):
Steganlysis of the LSB technique. Steganlysis of the LSB technique.
""" """
width, height = img.size width, height = img.size
colours_counter = Counter() # type: typing.Counter[int] colours_counter: typing.Counter[int] = Counter()
for row in range(height): for row in range(height):
for col in range(width): for col in range(width):
r, g, b = img.getpixel((col, row)) r, g, b = img.getpixel((col, row))
@ -47,7 +43,7 @@ def steganalyse(img):
sorted(list(colours_counter.items()), key=lambda t: t[1]) sorted(list(colours_counter.items()), key=lambda t: t[1])
) )
colours = 0 # type: float colours: float = 0
for colour in list(dict_colours.keys()): for colour in list(dict_colours.keys()):
colours += colour colours += colour
colours = colours / len(dict_colours) colours = colours / len(dict_colours)

View file

@ -1,10 +1,8 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*-
# Stegano - Stegano is a pure Python steganography module. # Stegano - Stegano is a pure Python steganography module.
# Copyright (C) 2010-2021 Cédric Bonhomme - https://www.cedricbonhomme.org # Copyright (C) 2010-2025 Cédric Bonhomme - https://www.cedricbonhomme.org
# #
# For more information : https://git.sr.ht/~cedric/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
@ -28,7 +26,7 @@ __license__ = "GPLv3"
import base64 import base64
import itertools import itertools
from functools import reduce from functools import reduce
from typing import IO, Iterator, List, Tuple, Union from typing import IO, List, Union
from PIL import Image from PIL import Image
@ -76,9 +74,7 @@ def setlsb(component: int, bit: str) -> int:
return component & ~1 | int(bit) return component & ~1 | int(bit)
def n_at_a_time( def n_at_a_time(items: List[int], n: int, fillvalue: str):
items: List[int], n: int, fillvalue: str
) -> Iterator[Tuple[Union[int, str]]]:
"""Returns an iterator which groups n items at a time. """Returns an iterator which groups n items at a time.
Any final partial tuple will be padded with the fillvalue Any final partial tuple will be padded with the fillvalue
@ -115,3 +111,113 @@ def open_image(fname_or_instance: Union[str, IO[bytes]]):
return fname_or_instance return fname_or_instance
return Image.open(fname_or_instance) return Image.open(fname_or_instance)
class Hider:
def __init__(
self,
input_image: Union[str, IO[bytes]],
message: str,
encoding: str = "UTF-8",
auto_convert_rgb: bool = False,
):
self._index = 0
message_length = len(message)
assert message_length != 0, "message length is zero"
image = open_image(input_image)
if image.mode not in ["RGB", "RGBA"]:
if not auto_convert_rgb:
print(f"The mode of the image is not RGB. Mode is {image.mode}")
answer = input("Convert the image to RGB ? [Y / n]\n") or "Y"
if answer.lower() == "n":
raise Exception("Not a RGB image.")
image = image.convert("RGB")
self.encoded_image = image.copy()
image.close()
message = str(message_length) + ":" + str(message)
self._message_bits = "".join(a2bits_list(message, encoding))
self._message_bits += "0" * ((3 - (len(self._message_bits) % 3)) % 3)
width, height = self.encoded_image.size
npixels = width * height
self._len_message_bits = len(self._message_bits)
if self._len_message_bits > npixels * 3:
raise Exception(
f"The message you want to hide is too long: {message_length}"
)
def encode_another_pixel(self):
return True if self._index + 3 <= self._len_message_bits else False
def encode_pixel(self, coordinate: tuple):
# Get the colour component.
r, g, b, *a = self.encoded_image.getpixel(coordinate)
# Change the Least Significant Bit of each colour component.
r = setlsb(r, self._message_bits[self._index])
g = setlsb(g, self._message_bits[self._index + 1])
b = setlsb(b, self._message_bits[self._index + 2])
# Save the new pixel
if self.encoded_image.mode == "RGBA":
self.encoded_image.putpixel(coordinate, (r, g, b, *a))
else:
self.encoded_image.putpixel(coordinate, (r, g, b))
self._index += 3
class Revealer:
def __init__(
self,
encoded_image: Union[str, IO[bytes]],
encoding: str = "UTF-8",
close_file: bool = True,
):
self.encoded_image = open_image(encoded_image)
self._encoding_length = ENCODINGS[encoding]
self._buff, self._count = 0, 0
self._bitab: List[str] = []
self._limit: Union[None, int] = None
self.secret_message = ""
self.close_file = close_file
def decode_pixel(self, coordinate: tuple):
# pixel = [r, g, b] or [r,g,b,a]
pixel = self.encoded_image.getpixel(coordinate)
if self.encoded_image.mode == "RGBA":
pixel = pixel[:3] # ignore the alpha
for color in pixel:
self._buff += (color & 1) << (self._encoding_length - 1 - self._count)
self._count += 1
if self._count == self._encoding_length:
self._bitab.append(chr(self._buff))
self._buff, self._count = 0, 0
if self._bitab[-1] == ":" and self._limit is None:
if "".join(self._bitab[:-1]).isdigit():
self._limit = int("".join(self._bitab[:-1]))
else:
raise IndexError("Impossible to detect message.")
if len(self._bitab) - len(str(self._limit)) - 1 == self._limit:
self.secret_message = "".join(self._bitab)[
len(str(self._limit)) + 1 : # noqa: E203
]
if self.close_file:
self.encoded_image.close()
return True
else:
return False

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. # Stegano - Stegano is a pure Python steganography module.
# Copyright (C) 2010-2021 Cédric Bonhomme - https://www.cedricbonhomme.org # Copyright (C) 2010-2025 Cédric Bonhomme - https://www.cedricbonhomme.org
# #
# For more information : https://git.sr.ht/~cedric/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
@ -25,9 +23,9 @@ __date__ = "$Date: 2016/05/17 $"
__revision__ = "$Date: 2017/01/18 $" __revision__ = "$Date: 2017/01/18 $"
__license__ = "GPLv3" __license__ = "GPLv3"
import io
import os import os
import unittest import unittest
import io
from stegano import exifHeader from stegano import exifHeader
@ -35,10 +33,9 @@ from stegano import exifHeader
class TestEXIFHeader(unittest.TestCase): class TestEXIFHeader(unittest.TestCase):
def test_hide_empty_message(self): def test_hide_empty_message(self):
"""Test hiding the empty string.""" """Test hiding the empty string."""
secret = exifHeader.hide( exifHeader.hide(
"./tests/sample-files/20160505T130442.jpg", "./image.jpg", secret_message="" "./tests/sample-files/20160505T130442.jpg", "./image.jpg", secret_message=""
) )
# secret.save(""./image.png"")
clear_message = exifHeader.reveal("./image.jpg") clear_message = exifHeader.reveal("./image.jpg")
@ -48,7 +45,7 @@ class TestEXIFHeader(unittest.TestCase):
messages_to_hide = ["a", "foo", "Hello World!", ":Python:"] messages_to_hide = ["a", "foo", "Hello World!", ":Python:"]
for message in messages_to_hide: for message in messages_to_hide:
secret = exifHeader.hide( exifHeader.hide(
"./tests/sample-files/20160505T130442.jpg", "./tests/sample-files/20160505T130442.jpg",
"./image.jpg", "./image.jpg",
secret_message=message, secret_message=message,
@ -56,13 +53,12 @@ class TestEXIFHeader(unittest.TestCase):
clear_message = exifHeader.reveal("./image.jpg") clear_message = exifHeader.reveal("./image.jpg")
self.assertEqual(message, message) self.assertEqual(message, clear_message.decode())
def test_with_image_without_exif_data(self): def test_with_image_without_exif_data(self):
secret = exifHeader.hide( exifHeader.hide(
"./tests/sample-files/Lenna.jpg", "./image.jpg", secret_message="" "./tests/sample-files/Lenna.jpg", "./image.jpg", secret_message=""
) )
# secret.save(""./image.png"")
clear_message = exifHeader.reveal("./image.jpg") clear_message = exifHeader.reveal("./image.jpg")
@ -72,7 +68,7 @@ class TestEXIFHeader(unittest.TestCase):
text_file_to_hide = "./tests/sample-files/lorem_ipsum.txt" text_file_to_hide = "./tests/sample-files/lorem_ipsum.txt"
with open(text_file_to_hide, "rb") as f: with open(text_file_to_hide, "rb") as f:
message = f.read() message = f.read()
secret = exifHeader.hide( exifHeader.hide(
"./tests/sample-files/20160505T130442.jpg", "./tests/sample-files/20160505T130442.jpg",
img_enc="./image.jpg", img_enc="./image.jpg",
secret_file=text_file_to_hide, secret_file=text_file_to_hide,
@ -82,13 +78,12 @@ class TestEXIFHeader(unittest.TestCase):
self.assertEqual(message, clear_message) self.assertEqual(message, clear_message)
def test_with_png_image(self): def test_with_png_image(self):
secret = exifHeader.hide( exifHeader.hide(
"./tests/sample-files/Lenna.png", "./image.png", secret_message="Secret" "./tests/sample-files/Lenna.png", "./image.png", secret_message="Secret"
) )
# secret.save(""./image.png"")
with self.assertRaises(ValueError): with self.assertRaises(ValueError):
clear_message = exifHeader.reveal("./image.png") exifHeader.reveal("./image.png")
def test_with_bytes(self): def test_with_bytes(self):
outputBytes = io.BytesIO() outputBytes = io.BytesIO()
@ -102,11 +97,11 @@ class TestEXIFHeader(unittest.TestCase):
def tearDown(self): def tearDown(self):
try: try:
os.unlink("./image.jpg") os.unlink("./image.jpg")
except: except Exception:
pass pass
try: try:
os.unlink("./image.png") os.unlink("./image.png")
except: except Exception:
pass pass

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. # Stegano - Stegano is a pure Python steganography module.
# Copyright (C) 2010-2021 Cédric Bonhomme - https://www.cedricbonhomme.org # Copyright (C) 2010-2025 Cédric Bonhomme - https://www.cedricbonhomme.org
# #
# For more information : https://git.sr.ht/~cedric/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
@ -25,12 +23,13 @@ __date__ = "$Date: 2017/03/01 $"
__revision__ = "$Date: 2017/03/01 $" __revision__ = "$Date: 2017/03/01 $"
__license__ = "GPLv3" __license__ = "GPLv3"
import unittest
import itertools import itertools
import unittest
import cv2 import cv2
import numpy as np import numpy as np
from stegano.lsbset import generators from stegano.lsb import generators
class TestGenerators(unittest.TestCase): class TestGenerators(unittest.TestCase):
@ -71,7 +70,7 @@ class TestGenerators(unittest.TestCase):
def test_eratosthenes(self): def test_eratosthenes(self):
"""Test the Eratosthenes sieve.""" """Test the Eratosthenes sieve."""
with open("./tests/expected-results/eratosthenes", "r") as f: with open("./tests/expected-results/eratosthenes") as f:
self.assertEqual( self.assertEqual(
tuple(itertools.islice(generators.eratosthenes(), 168)), tuple(itertools.islice(generators.eratosthenes(), 168)),
tuple(int(line) for line in f), tuple(int(line) for line in f),
@ -79,7 +78,7 @@ class TestGenerators(unittest.TestCase):
def test_composite(self): def test_composite(self):
"""Test the composite sieve.""" """Test the composite sieve."""
with open("./tests/expected-results/composite", "r") as f: with open("./tests/expected-results/composite") as f:
self.assertEqual( self.assertEqual(
tuple(itertools.islice(generators.composite(), 114)), tuple(itertools.islice(generators.composite(), 114)),
tuple(int(line) for line in f), tuple(int(line) for line in f),
@ -87,7 +86,7 @@ class TestGenerators(unittest.TestCase):
def test_fermat(self): def test_fermat(self):
"""Test the Fermat generator.""" """Test the Fermat generator."""
with open("./tests/expected-results/fermat", "r") as f: with open("./tests/expected-results/fermat") as f:
self.assertEqual( self.assertEqual(
tuple(itertools.islice(generators.fermat(), 9)), tuple(itertools.islice(generators.fermat(), 9)),
tuple(int(line) for line in f), tuple(int(line) for line in f),
@ -95,7 +94,7 @@ class TestGenerators(unittest.TestCase):
def test_triangular_numbers(self): def test_triangular_numbers(self):
"""Test the Triangular numbers generator.""" """Test the Triangular numbers generator."""
with open("./tests/expected-results/triangular_numbers", "r") as f: with open("./tests/expected-results/triangular_numbers") as f:
self.assertEqual( self.assertEqual(
tuple(itertools.islice(generators.triangular_numbers(), 54)), tuple(itertools.islice(generators.triangular_numbers(), 54)),
tuple(int(line) for line in f), tuple(int(line) for line in f),
@ -103,7 +102,7 @@ class TestGenerators(unittest.TestCase):
def test_mersenne(self): def test_mersenne(self):
"""Test the Mersenne generator.""" """Test the Mersenne generator."""
with open("./tests/expected-results/mersenne", "r") as f: with open("./tests/expected-results/mersenne") as f:
self.assertEqual( self.assertEqual(
tuple(itertools.islice(generators.mersenne(), 20)), tuple(itertools.islice(generators.mersenne(), 20)),
tuple(int(line) for line in f), tuple(int(line) for line in f),
@ -111,7 +110,7 @@ class TestGenerators(unittest.TestCase):
def test_carmichael(self): def test_carmichael(self):
"""Test the Carmichael generator.""" """Test the Carmichael generator."""
with open("./tests/expected-results/carmichael", "r") as f: with open("./tests/expected-results/carmichael") as f:
self.assertEqual( self.assertEqual(
tuple(itertools.islice(generators.carmichael(), 33)), tuple(itertools.islice(generators.carmichael(), 33)),
tuple(int(line) for line in f), tuple(int(line) for line in f),
@ -119,7 +118,7 @@ class TestGenerators(unittest.TestCase):
def test_ackermann_slow(self): def test_ackermann_slow(self):
"""Test the Ackermann set.""" """Test the Ackermann set."""
with open("./tests/expected-results/ackermann", "r") as f: with open("./tests/expected-results/ackermann") as f:
self.assertEqual(generators.ackermann_slow(3, 1), int(f.readline())) self.assertEqual(generators.ackermann_slow(3, 1), int(f.readline()))
self.assertEqual(generators.ackermann_slow(3, 2), int(f.readline())) self.assertEqual(generators.ackermann_slow(3, 2), int(f.readline()))
@ -127,31 +126,30 @@ class TestGenerators(unittest.TestCase):
"""Test the Naive Ackermann generator""" """Test the Naive Ackermann generator"""
gen = generators.ackermann_naive(3) gen = generators.ackermann_naive(3)
next(gen) next(gen)
with open("./tests/expected-results/ackermann", "r") as f: with open("./tests/expected-results/ackermann") as f:
self.assertEqual(next(gen), int(f.readline())) self.assertEqual(next(gen), int(f.readline()))
self.assertEqual(next(gen), int(f.readline())) self.assertEqual(next(gen), int(f.readline()))
def test_ackermann_fast(self): def test_ackermann_fast(self):
"""Test the Ackermann set.""" """Test the Ackermann set."""
with open("./tests/expected-results/ackermann", "r") as f: with open("./tests/expected-results/ackermann") as f:
self.assertEqual(generators.ackermann_fast(3, 1), int(f.readline())) self.assertEqual(generators.ackermann_fast(3, 1), int(f.readline()))
self.assertEqual(generators.ackermann_fast(3, 2), int(f.readline())) self.assertEqual(generators.ackermann_fast(3, 2), int(f.readline()))
self.assertEqual(generators.ackermann_fast(4, 1), int(f.readline())) self.assertEqual(generators.ackermann_fast(4, 1), int(f.readline()))
self.assertEqual(generators.ackermann_fast(4, 2), int(f.readline()))
def test_ackermann(self): def test_ackermann(self):
"""Test the Ackermann generator""" """Test the Ackermann generator"""
gen = generators.ackermann(3) gen = generators.ackermann(3)
next(gen) next(gen)
with open("./tests/expected-results/ackermann", "r") as f: with open("./tests/expected-results/ackermann") as f:
self.assertEqual(next(gen), int(f.readline())) self.assertEqual(next(gen), int(f.readline()))
self.assertEqual(next(gen), int(f.readline())) self.assertEqual(next(gen), int(f.readline()))
def test_LFSR(self): def test_LFSR(self):
"""Test the LFSR generator""" """Test the LFSR generator"""
with open("./tests/expected-results/LFSR", "r") as f: with open("./tests/expected-results/LFSR") as f:
self.assertEqual( self.assertEqual(
tuple(itertools.islice(generators.LFSR(2 ** 8), 256)), tuple(itertools.islice(generators.LFSR(2**8), 256)),
tuple(int(line) for line in f), tuple(int(line) for line in f),
) )
@ -176,14 +174,13 @@ class TestGenerators(unittest.TestCase):
test_file_reshaped = test_file.reshape( test_file_reshaped = test_file.reshape(
int(test_file.shape[0]), int(test_file.shape[1]) int(test_file.shape[0]), int(test_file.shape[1])
) )
self.assertIsNone( res = np.testing.assert_allclose(corners, test_file_reshaped, rtol=1e-0, atol=0) # type: ignore
np.testing.assert_allclose(corners, test_file_reshaped, rtol=1e-0, atol=0) self.assertIsNone(res)
)
@staticmethod @staticmethod
def shi_tomashi_reconfigure( def shi_tomashi_reconfigure(
file_name: str, file_name: str,
corners: int = 1000, max_corners: int = 1000,
quality: float = 0.001, quality: float = 0.001,
min_distance: int = 10, min_distance: int = 10,
): ):
@ -192,8 +189,10 @@ class TestGenerators(unittest.TestCase):
""" """
image = cv2.imread(file_name) image = cv2.imread(file_name)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
corners = cv2.goodFeaturesToTrack(gray, corners, quality, min_distance) corners = cv2.goodFeaturesToTrack(gray, max_corners, quality, min_distance)
corners = np.int0(corners) # 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) corners = corners.reshape(corners.shape[0], -1)
np.savetxt("tests/expected-results/shi_tomashi.txt", corners) np.savetxt("tests/expected-results/shi_tomashi.txt", corners)

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. # Stegano - Stegano is a pure Python steganography module.
# Copyright (C) 2010-2021 Cédric Bonhomme - https://www.cedricbonhomme.org # Copyright (C) 2010-2024 Cédric Bonhomme - https://www.cedricbonhomme.org
# #
# For more information : https://git.sr.ht/~cedric/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,18 +18,18 @@
# 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.3 $" __version__ = "$Revision: 0.6 $"
__date__ = "$Date: 2016/04/12 $" __date__ = "$Date: 2016/04/13 $"
__revision__ = "$Date: 2017/05/04 $" __revision__ = "$Date: 2022/01/04 $"
__license__ = "GPLv3" __license__ = "GPLv3"
import io import io
import os import os
import base64
import unittest import unittest
from unittest.mock import patch from unittest.mock import patch
from stegano import lsb from stegano import lsb
from stegano.lsb import generators
class TestLSB(unittest.TestCase): class TestLSB(unittest.TestCase):
@ -40,133 +38,205 @@ class TestLSB(unittest.TestCase):
Test hiding the empty string. Test hiding the empty string.
""" """
with self.assertRaises(AssertionError): with self.assertRaises(AssertionError):
lsb.hide("./tests/sample-files/Lenna.png", "") lsb.hide("./tests/sample-files/Lenna.png", "", generators.eratosthenes())
def test_hide_and_reveal(self): def test_hide_and_reveal_without_generator(self):
messages_to_hide = ["a", "foo", "Hello World!", ":Python:"] messages_to_hide = ["a", "foo", "Hello World!", ":Python:"]
for message in messages_to_hide: for message in messages_to_hide:
secret = lsb.hide("./tests/sample-files/Lenna.png", message) secret = lsb.hide("./tests/sample-files/Lenna.png", message)
secret.save("./image.png") secret.save("./image.png")
clear_message = lsb.reveal("./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) self.assertEqual(message, clear_message)
def test_hide_and_reveal_UTF32LE(self): def test_hide_and_reveal_UTF32LE(self):
messages_to_hide = "I love 🍕 and 🍫!" messages_to_hide = "I love 🍕 and 🍫!"
secret = lsb.hide( secret = lsb.hide(
"./tests/sample-files/Lenna.png", messages_to_hide, encoding="UTF-32LE" "./tests/sample-files/Lenna.png",
messages_to_hide,
generators.eratosthenes(),
encoding="UTF-32LE",
) )
secret.save("./image.png") secret.save("./image.png")
clear_message = lsb.reveal("./image.png", encoding="UTF-32LE") clear_message = lsb.reveal(
"./image.png", generators.eratosthenes(), encoding="UTF-32LE"
)
self.assertEqual(messages_to_hide, clear_message) self.assertEqual(messages_to_hide, clear_message)
def test_with_transparent_png(self): def test_with_transparent_png(self):
messages_to_hide = ["🍕", "a", "foo", "Hello World!", ":Python:"] messages_to_hide = ["a", "foo", "Hello World!", ":Python:"]
for message in messages_to_hide: for message in messages_to_hide:
secret = lsb.hide( secret = lsb.hide(
"./tests/sample-files/transparent.png", message, encoding="UTF-32LE" "./tests/sample-files/transparent.png",
message,
generators.eratosthenes(),
) )
secret.save("./image.png") secret.save("./image.png")
clear_message = lsb.reveal("./image.png", encoding="UTF-32LE") clear_message = lsb.reveal("./image.png", generators.eratosthenes())
self.assertEqual(message, clear_message) self.assertEqual(message, clear_message)
@patch("builtins.input", return_value="y") @patch("builtins.input", return_value="y")
def test_manual_convert_rgb(self, input): def test_manual_convert_rgb(self, input):
message_to_hide = "I love 🍕 and 🍫!" message_to_hide = "Hello World!"
lsb.hide( lsb.hide(
"./tests/sample-files/Lenna-grayscale.png", "./tests/sample-files/Lenna-grayscale.png",
message_to_hide, message_to_hide,
encoding="UTF-32LE", generators.eratosthenes(),
) )
@patch("builtins.input", return_value="n") @patch("builtins.input", return_value="n")
def test_refuse_convert_rgb(self, input): def test_refuse_convert_rgb(self, input):
message_to_hide = "I love 🍕 and 🍫!" message_to_hide = "Hello World!"
with self.assertRaises(Exception): # lsb.hide(
# "./tests/sample-files/Lenna-grayscale.png",
# message_to_hide,
# generators.eratosthenes(),
# )
with self.assertRaisesRegex(Exception, "Not a RGB image."):
lsb.hide( lsb.hide(
"./tests/sample-files/Lenna-grayscale.png", "./tests/sample-files/Lenna-grayscale.png",
message_to_hide, message_to_hide,
encoding="UTF-32LE", generators.eratosthenes(),
) )
def test_auto_convert_rgb(self):
message_to_hide = "I love 🍕 and 🍫!"
lsb.hide(
"./tests/sample-files/Lenna-grayscale.png",
message_to_hide,
encoding="UTF-32LE",
auto_convert_rgb=True,
)
def test_with_text_file(self):
text_file_to_hide = "./tests/sample-files/lorem_ipsum.txt"
with open(text_file_to_hide) as f:
message = f.read()
secret = lsb.hide("./tests/sample-files/Lenna.png", message)
secret.save("./image.png")
clear_message = lsb.reveal("./image.png")
self.assertEqual(message, clear_message)
def test_with_binary_file(self):
binary_file_to_hide = "./tests/sample-files/free-software-song.ogg"
with open(binary_file_to_hide, "rb") as bin_file:
encoded_string = base64.b64encode(bin_file.read())
message = encoded_string.decode()
secret = lsb.hide("./tests/sample-files/Montenach.png", message)
secret.save("./image.png")
clear_message = lsb.reveal("./image.png")
clear_message += "==="
clear_message = base64.b64decode(clear_message)
with open("file1", "wb") as f:
f.write(clear_message)
with open("file1", "rb") as bin_file:
encoded_string = base64.b64encode(bin_file.read())
message1 = encoded_string.decode()
self.assertEqual(message, message1)
try:
os.unlink("./file1")
except Exception:
pass
def test_with_too_long_message(self):
with open("./tests/sample-files/lorem_ipsum.txt") as f:
message = f.read()
message += message * 2
with self.assertRaises(Exception):
lsb.hide("./tests/sample-files/Lenna.png", message)
def test_with_bytes(self):
messages_to_hide = ["a", "foo", "Hello World!", ":Python:"]
for message in messages_to_hide:
outputBytes = io.BytesIO()
with open("./tests/sample-files/20160505T130442.jpg", "rb") as f:
bytes_image = lsb.hide(f, message)
bytes_image.save(outputBytes, "PNG")
outputBytes.seek(0)
clear_message = lsb.reveal(outputBytes)
self.assertEqual(message, clear_message)
def test_with_location_of_image_as_argument(self): def test_with_location_of_image_as_argument(self):
messages_to_hide = ["Hello World!"] messages_to_hide = ["Hello World!"]
for message in messages_to_hide: for message in messages_to_hide:
outputBytes = io.BytesIO() outputBytes = io.BytesIO()
bytes_image = lsb.hide("./tests/sample-files/20160505T130442.jpg", message) bytes_image = lsb.hide(
"./tests/sample-files/20160505T130442.jpg",
message,
generators.identity(),
)
bytes_image.save(outputBytes, "PNG") bytes_image.save(outputBytes, "PNG")
outputBytes.seek(0) outputBytes.seek(0)
clear_message = lsb.reveal(outputBytes) clear_message = lsb.reveal(outputBytes, generators.identity())
self.assertEqual(message, clear_message) 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): def tearDown(self):
try: try:
os.unlink("./image.png") os.unlink("./image.png")

View file

@ -1,207 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Stegano - Stegano is a pure Python steganography module.
# Copyright (C) 2010-2021 Cédric Bonhomme - https://www.cedricbonhomme.org
#
# For more information : https://git.sr.ht/~cedric/stegano
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>
__author__ = "Cedric Bonhomme"
__version__ = "$Revision: 0.5 $"
__date__ = "$Date: 2016/04/13 $"
__revision__ = "$Date: 2021/11/29 $"
__license__ = "GPLv3"
import io
import os
import unittest
from unittest.mock import patch
from stegano import lsbset
from stegano.lsbset import generators
class TestLSBSet(unittest.TestCase):
def test_hide_empty_message(self):
"""
Test hiding the empty string.
"""
with self.assertRaises(AssertionError):
lsbset.hide("./tests/sample-files/Lenna.png", "", generators.eratosthenes())
def test_hide_and_reveal(self):
messages_to_hide = ["a", "foo", "Hello World!", ":Python:"]
for message in messages_to_hide:
secret = lsbset.hide(
"./tests/sample-files/Lenna.png", message, generators.eratosthenes()
)
secret.save("./image.png")
clear_message = lsbset.reveal("./image.png", generators.eratosthenes())
self.assertEqual(message, clear_message)
def test_hide_and_reveal_with_ackermann(self):
messages_to_hide = ["foo"]
for message in messages_to_hide:
secret = lsbset.hide(
"./tests/sample-files/Lenna.png", message, generators.ackermann(m=3)
)
secret.save("./image.png")
clear_message = lsbset.reveal("./image.png", generators.ackermann(m=3))
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 = lsbset.hide(
"./tests/sample-files/Lenna.png",
message,
generators.shi_tomashi("./tests/sample-files/Lenna.png"),
)
secret.save("./image.png")
clear_message = lsbset.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 = lsbset.hide(
"./tests/sample-files/Lenna.png", message, generators.eratosthenes(), 4
)
secret.save("./image.png")
clear_message = lsbset.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 = lsbset.hide(
"./tests/sample-files/Lenna.png",
messages_to_hide,
generators.eratosthenes(),
encoding="UTF-32LE",
)
secret.save("./image.png")
clear_message = lsbset.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 = lsbset.hide(
"./tests/sample-files/transparent.png",
message,
generators.eratosthenes(),
)
secret.save("./image.png")
clear_message = lsbset.reveal("./image.png", generators.eratosthenes())
self.assertEqual(message, clear_message)
@patch("builtins.input", return_value="y")
def test_manual_convert_rgb(self, input):
message_to_hide = "Hello World!"
lsbset.hide(
"./tests/sample-files/Lenna-grayscale.png",
message_to_hide,
generators.eratosthenes(),
)
@patch("builtins.input", return_value="n")
def test_refuse_convert_rgb(self, input):
message_to_hide = "Hello World!"
with self.assertRaises(Exception):
lsbset.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 = lsbset.hide(
"./tests/sample-files/20160505T130442.jpg",
message,
generators.identity(),
)
bytes_image.save(outputBytes, "PNG")
outputBytes.seek(0)
clear_message = lsbset.reveal(outputBytes, generators.identity())
self.assertEqual(message, clear_message)
def test_auto_convert_rgb(self):
message_to_hide = "Hello World!"
lsbset.hide(
"./tests/sample-files/Lenna-grayscale.png",
message_to_hide,
generators.eratosthenes(),
auto_convert_rgb=True,
)
def test_with_too_long_message(self):
with open("./tests/sample-files/lorem_ipsum.txt") as f:
message = f.read()
message += message * 2
with self.assertRaises(Exception):
lsbset.hide(
"./tests/sample-files/Lenna.png", message, generators.identity()
)
def test_hide_and_reveal_with_bad_generator(self):
message_to_hide = "Hello World!"
secret = lsbset.hide(
"./tests/sample-files/Lenna.png", message_to_hide, generators.eratosthenes()
)
secret.save("./image.png")
with self.assertRaises(IndexError):
lsbset.reveal("./image.png", generators.identity())
def test_with_unknown_generator(self):
message_to_hide = "Hello World!"
with self.assertRaises(AttributeError):
lsbset.hide(
"./tests/sample-files/Lenna.png",
message_to_hide,
generators.eratosthene(),
)
def tearDown(self):
try:
os.unlink("./image.png")
except Exception:
pass
if __name__ == "__main__":
unittest.main()

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. # Stegano - Stegano is a pure Python steganography module.
# Copyright (C) 2010-2021 Cédric Bonhomme - https://www.cedricbonhomme.org # Copyright (C) 2010-2025 Cédric Bonhomme - https://www.cedricbonhomme.org
# #
# For more information : https://git.sr.ht/~cedric/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
@ -36,7 +34,7 @@ class TestRed(unittest.TestCase):
Test hiding the empty string. Test hiding the empty string.
""" """
with self.assertRaises(AssertionError): with self.assertRaises(AssertionError):
secret = red.hide("./tests/sample-files/Lenna.png", "") red.hide("./tests/sample-files/Lenna.png", "")
def test_hide_and_reveal(self): def test_hide_and_reveal(self):
messages_to_hide = ["a", "foo", "Hello World!", ":Python:"] messages_to_hide = ["a", "foo", "Hello World!", ":Python:"]
@ -47,7 +45,7 @@ class TestRed(unittest.TestCase):
clear_message = red.reveal("./image.png") clear_message = red.reveal("./image.png")
self.assertEqual(message, message) self.assertEqual(message, clear_message)
def test_with_too_long_message(self): def test_with_too_long_message(self):
with open("./tests/sample-files/lorem_ipsum.txt") as f: with open("./tests/sample-files/lorem_ipsum.txt") as f:
@ -58,7 +56,7 @@ class TestRed(unittest.TestCase):
def tearDown(self): def tearDown(self):
try: try:
os.unlink("./image.png") os.unlink("./image.png")
except: except Exception:
pass pass

View file

@ -1,10 +1,9 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*-
# Stegano - Stegano is a pure Python steganography module. # Stegano - Stegano is a pure Python steganography module.
# Copyright (C) 2010-2017 Cédric Bonhomme - https://www.cedricbonhomme.org # Copyright (C) 2010-2017 Cédric Bonhomme - https://www.cedricbonhomme.org
# #
# For more information : https://git.sr.ht/~cedric/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
@ -25,15 +24,12 @@ __date__ = "$Date: 2019/06/06 $"
__revision__ = "$Date: 2019/06/06 $" __revision__ = "$Date: 2019/06/06 $"
__license__ = "GPLv3" __license__ = "GPLv3"
import io
import os
import base64
import unittest import unittest
from unittest.mock import patch
from PIL import Image, ImageChops
from stegano import lsb from stegano import lsb
from stegano.steganalysis import parity, statistics from stegano.steganalysis import parity, statistics
from PIL import Image, ImageChops
class TestSteganalysis(unittest.TestCase): class TestSteganalysis(unittest.TestCase):

View file

@ -1,10 +1,8 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*-
# Stegano - Stegano is a pure Python steganography module. # Stegano - Stegano is a pure Python steganography module.
# Copyright (C) 2010-2021 Cédric Bonhomme - https://www.cedricbonhomme.org # Copyright (C) 2010-2025 Cédric Bonhomme - https://www.cedricbonhomme.org
# #
# For more information : https://git.sr.ht/~cedric/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
@ -25,9 +23,7 @@ __date__ = "$Date: 2017/02/22 $"
__revision__ = "$Date: 2017/02/22 $" __revision__ = "$Date: 2017/02/22 $"
__license__ = "GPLv3" __license__ = "GPLv3"
import os
import unittest import unittest
import io
from stegano import tools from stegano import tools
@ -85,7 +81,7 @@ class TestTools(unittest.TestCase):
self.assertEqual(list(result), [(1, 2), (3, 4), (5, "X")]) self.assertEqual(list(result), [(1, 2), (3, 4), (5, "X")])
def test_binary2base64(self): def test_binary2base64(self):
with open("./tests/expected-results/binary2base64", "r") as f: with open("./tests/expected-results/binary2base64") as f:
expected_value = f.read() expected_value = f.read()
value = tools.binary2base64("tests/sample-files/free-software-song.ogg") value = tools.binary2base64("tests/sample-files/free-software-song.ogg")
self.assertEqual(expected_value, value) self.assertEqual(expected_value, value)