From 7156129ef33354bda28435a6422c7911522d8876 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Valentin=20Mogu=C3=A9rou?= Date: Sat, 19 Mar 2022 19:31:19 +0100 Subject: [PATCH] =?UTF-8?q?Transf=C3=A9rer=20les=20fichiers=20vers=20''?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- default_image.jpg | Bin 0 -> 37406 bytes libstegano.py | 145 ++++++++++++++++++++++++++++++++++++++++++++++ steganocli.py | 73 +++++++++++++++++++++++ 3 files changed, 218 insertions(+) create mode 100644 default_image.jpg create mode 100644 libstegano.py create mode 100644 steganocli.py diff --git a/default_image.jpg b/default_image.jpg new file mode 100644 index 0000000000000000000000000000000000000000..414b225c7668fce0a00b0cc0af88160690a33166 GIT binary patch literal 37406 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 Xau4*RNy^Ihwm94evDm2Ef2RHqpETP0 literal 0 HcmV?d00001 diff --git a/libstegano.py b/libstegano.py new file mode 100644 index 0000000..e2c7164 --- /dev/null +++ b/libstegano.py @@ -0,0 +1,145 @@ +""" +A library for the steganography program +Copyright (C) 2022 Valentin Moguérou + +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 . +""" + +from typing import BinaryIO +from PIL import Image + +# ================= BINARY OPERATIONS ================= + +def write_lsb_in_bin(byte, bit): + "Write the specified bit in the LSB of the specified byte" + return byte & 254 | bit if bit==1 else byte & 254 + +def read_lsb_in_bin(byte): + return byte & 1 + +def combine_bits_to_byte(bits): + """ + Turns a sequence of bits into an integer. + Example: turns [0, 1, 1, 0, 1, 1, 0, 1] into 0b01101101 or 109 + """ + return sum(bits[-1-i]<>(7-i)&1 for i in range(8)] + +def hex_print(byte_list, margin=0, line_width=16): + "Prints a byte list in hexadecimal" + + for line in range(0, len(byte_list), line_width): + print(' '*margin + ' '.join(f'{byte:02X}' for byte in byte_list[line:line+line_width])) + +# ================= STRING OPERATIONS ================= + +def encode_string(string: str) -> bytearray: + "Turns the given string into a byte array" + return bytearray(ord(ch) for ch in string+'\0') + +def decode_string(bytelist: bytearray) -> str: + "Turns the given byte array into a string" + return ''.join(chr(b) for b in bytelist) + +# ================= WRITE OPERATIONS ================= + +def write_band(band: bytearray, byte_list: bytearray, starting_pos=0) -> bool: + "Writes a byte sequence in the given band" + + for index in range(starting_pos, len(band), 8): + band[index:index+8] = (write_lsb_in_bin(band[index+i], bit) for i, bit in enumerate(split_byte_into_bits(byte_list[index//8]))) + if byte_list[index//8] == 0: + return True, index+8 # finished writing + return False, index+8 # didn't finish writing, return last index + +def write_image(img: Image.Image, byte_list, verbose=False): + data = [bytearray(band.tobytes()) for band in img.split()] + + ok, position = write_band(data[0], byte_list) + if not ok: + ok, position = write_band(data[1], byte_list, position) + if not ok: + ok, position = write_band(data[2], byte_list, position) + if not ok: + raise ValueError("The byte sequence is too long.") + + if verbose: + print(f"{position} bits, {position//8} bytes successfully written.") + + return Image.merge(img.mode, [Image.frombytes('L', img.size, bytes(band)) for band in data]) + +def write_file(from_file: BinaryIO, to_file: BinaryIO, byte_list, verbose=False): + write_image(Image.open(from_file), byte_list, verbose=verbose).save(to_file) + +# ================= READ OPERATIONS ================= + +def read_band(band: bytes, byte_list: bytearray) -> bool: + "Turns a sequence of pixels into a byte list" + + for i in range(0, len(band), 8): + bits = [read_lsb_in_bin(b) for b in band[i:i+8]] + if all(b==0 for b in bits): + return True # finished reading + byte_list.append(combine_bits_to_byte(bits)) + return False # didn't finish reading + +def read_image(img: Image.Image, verbose=False): + "Read the whole image" + + data = [band.tobytes() for band in img.split()] + byte_list = bytearray() + + if read_band(data[0], byte_list): + if verbose: + print("Successfully finished reading.") + elif read_band(data[1], byte_list): + if verbose: + print("Successfully finished reading.") + print("Read the whole red band.") + elif read_band(data[1], byte_list): + if verbose: + print("Successfully finished reading.") + print("Read the whole green band.") + else: + if verbose: + print("Read the whole blue band.") + raise ValueError("Invalid image, did not find end control sequence.") + + return byte_list + +def read_file(from_file: BinaryIO, verbose=False): + return read_image(Image.open(from_file), verbose=verbose) + +# ================= EXAMPLE PROGRAM ================= + +def main(): + s = "This is a string" + oldimg = Image.open('guitar.jpg') + oldimg.load() + print(encode_string(s)) + newimg = write_image(oldimg, encode_string(s)) + newimg.save('dissimulated.png', 'PNG') + print('Written...') + + print(f"String: --> {read_image(Image.open('guitar.jpg'))} (guitar.jpg)") + print(f"String: --> {decode_string(read_image(Image.open('dissimulated.png')))} (dissimulated.png)") + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/steganocli.py b/steganocli.py new file mode 100644 index 0000000..eb87ca5 --- /dev/null +++ b/steganocli.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python3 + +""" +A command-line interface for the steganography program +Copyright (C) 2022 Valentin Moguérou + +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 . +""" + +from argparse import ArgumentParser as ArgParser +from pathlib import Path + +from libstegano import read_file, write_file, decode_string, encode_string, hex_print + +def read(args): + if args.verbose: + print("Read mode enabled.") + + with open(args.from_file, 'rb') as from_file: + byte_list = read_file(from_file, verbose=args.verbose) + + if args.verbose: + print(f"Read {(len(byte_list)+1)*8} bits, {len(byte_list)+1} bytes.") + print("======== BEGIN MESSAGE ========") + print(decode_string(byte_list)) + print("========= END MESSAGE =========") + else: + print(decode_string(byte_list)) + +def write(args): + byte_list = encode_string(args.string) + + with open(args.from_file, 'rb') as from_file, open(args.to_file, 'wb') as to_file: + if args.verbose: + print("Write mode enabled.") + print("================== BYTE STREAM ==================") + hex_print(byte_list, margin=1) + print("================== BYTE STREAM ==================") + + write_file(from_file, to_file, byte_list, verbose=args.verbose) + +def main(): + parser = ArgParser() + parser.add_argument('-v', '--verbose', action='store_true') + + subparsers = parser.add_subparsers(required=True) + + parser_read = subparsers.add_parser('read', help='Retrieve data from an image') + parser_read.add_argument('from_file', type=Path) + parser_read.set_defaults(func=read) + + parser_write = subparsers.add_parser('write', help='Dissimulate data in an image') + parser_write.add_argument('string', type=str) + parser_write.add_argument('to_file', type=Path) + parser_write.add_argument('--from', type=Path, dest='from_file', default=Path('default_image.jpg')) + parser_write.set_defaults(func=write) + + args = parser.parse_args() + args.func(args) + +if __name__ == "__main__": + main() \ No newline at end of file