diff --git a/stegano/wav/__init__.py b/stegano/wav/__init__.py new file mode 100644 index 0000000..e2529fe --- /dev/null +++ b/stegano/wav/__init__.py @@ -0,0 +1,5 @@ +#!/usr/bin/env python + +from .wav import hide, reveal + +__all__ = ["hide", "reveal"] diff --git a/stegano/wav/wav.py b/stegano/wav/wav.py new file mode 100644 index 0000000..65caacd --- /dev/null +++ b/stegano/wav/wav.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python +# Stegano - Stéganô is a basic Python Steganography module. +# Copyright (C) 2010-2024 Cédric Bonhomme - https://www.cedricbonhomme.org +# +# For more information : https://github.com/cedricbonhomme/Stegano +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see + +__author__ = "Cedric Bonhomme" +__version__ = "$Revision: 0.2 $" +__date__ = "$Date: 2010/10/01 $" +__revision__ = "$Date: 2017/02/06 $" +__license__ = "GPLv3" + +from typing import IO, Union + +import wave + +def hide(input_file: Union[str, IO[bytes]], message: str, output_file: Union[str, IO[bytes]]): + """ + Hide a message (string) in a .wav audio file. + + Use the lsb of each sample to hide the message string characters as ASCII values. + The first eight bits are used for message_length of the string. + """ + message_length = len(message) + assert message_length != 0, "message message_length is zero" + # TODO messages in audio files could likely be much longer in most cases + assert message_length < 255, "message is too long" + + output = wave.open(output_file, "wb") + with wave.open(input_file, "rb") as input: + pass + # TODO + + +def reveal(input_file: Union[str, IO[bytes]]): + """ + Find a message in an image. + + Check the lsb of each sample for hidden message characters (ASCII values). + The first eight bits are used for message_length of the string. + """ + message = "" + with wave.open(input_file, "rb") as input: + pass + # TODO + return message diff --git a/tests/sample-files/free-software-song.wav b/tests/sample-files/free-software-song.wav new file mode 100644 index 0000000..405684a Binary files /dev/null and b/tests/sample-files/free-software-song.wav differ diff --git a/tests/test_wav.py b/tests/test_wav.py new file mode 100644 index 0000000..86276d0 --- /dev/null +++ b/tests/test_wav.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python +# Stegano - Stegano is a pure Python steganography module. +# Copyright (C) 2010-2025 Cédric Bonhomme - https://www.cedricbonhomme.org +# +# For more information : https://github.com/cedricbonhomme/Stegano +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see + +__author__ = "Cedric Bonhomme" +__version__ = "$Revision: 0.1 $" +__date__ = "$Date: 2016/05/19 $" +__license__ = "GPLv3" + +import os +import unittest + +from stegano import wav + + +class TestWav(unittest.TestCase): + def test_hide_empty_message(self): + """ + Test hiding the empty string. + """ + with self.assertRaises(AssertionError): + wav.hide("./tests/sample-files/free-software-song.wav", "", "./audio.wav") + + def test_hide_and_reveal(self): + messages_to_hide = ["a", "foo", "Hello World!", ":Python:"] + + for message in messages_to_hide: + wav.hide("./tests/sample-files/free-software-song.wav", message, "./audio.wav") + clear_message = wav.reveal("./audio.wav") + + self.assertEqual(message, clear_message) + + def test_with_too_long_message(self): + with open("./tests/sample-files/lorem_ipsum.txt") as f: + message = f.read() + with self.assertRaises(AssertionError): + wav.hide("./tests/sample-files/free-software-song.wav", message, "./audio.wav") + + def tearDown(self): + try: + os.unlink("./audio.wav") + except Exception: + pass + + +if __name__ == "__main__": + unittest.main()