diff --git a/stegano/exifHeader/exifHeader.py b/stegano/exifHeader/exifHeader.py index 24a8d65..9a15ce2 100644 --- a/stegano/exifHeader/exifHeader.py +++ b/stegano/exifHeader/exifHeader.py @@ -1,5 +1,5 @@ #!/usr/bin/env python -#-*- coding: utf-8 -*- +# -*- coding: utf-8 -*- # Stéganô - Stéganô is a basic Python Steganography module. # Copyright (C) 2010-2019 Cédric Bonhomme - https://www.cedricbonhomme.org @@ -25,13 +25,17 @@ __date__ = "$Date: 2016/05/26 $" __revision__ = "$Date: 2017/01/18 $" __license__ = "GPLv3" -from PIL import Image - import piexif from stegano import tools -def hide(input_image_file, img_enc, secret_message = None, secret_file = None, img_format = None): +def hide( + input_image_file, + img_enc, + secret_message=None, + secret_file=None, + img_format=None, +): """Hide a message (string) in an image. """ from zlib import compress @@ -72,13 +76,13 @@ def reveal(input_image_file): img = tools.open_image(input_image_file) try: - if img.format in ['JPEG', 'TIFF']: - if 'exif' in img.info: - exif_dict = piexif.load(img.info.get("exif", b'')) + if img.format in ["JPEG", "TIFF"]: + if "exif" in img.info: + exif_dict = piexif.load(img.info.get("exif", b"")) description_key = piexif.ImageIFD.ImageDescription encoded_message = exif_dict["0th"][description_key] else: - encoded_message = b'' + encoded_message = b"" else: raise ValueError("Given file is neither JPEG nor TIFF.") finally: @@ -91,41 +95,70 @@ if __name__ == "__main__": # Point of entry in execution mode. # TODO: improve the management of arguments from optparse import OptionParser + parser = OptionParser(version=__version__) - parser.add_option('--hide', action='store_true', default=False, - help="Hides a message in an image.") - parser.add_option('--reveal', action='store_true', default=False, - help="Reveals the message hided in an image.") + parser.add_option( + "--hide", + action="store_true", + default=False, + help="Hides a message in an image.", + ) + parser.add_option( + "--reveal", + action="store_true", + default=False, + help="Reveals the message hided in an image.", + ) # Original image - parser.add_option("-i", "--input", dest="input_image_file", - help="Input image file.") + parser.add_option( + "-i", "--input", dest="input_image_file", help="Input image file." + ) # Image containing the secret - parser.add_option("-o", "--output", dest="output_image_file", - help="Output image containing the secret.") + parser.add_option( + "-o", + "--output", + dest="output_image_file", + help="Output image containing the secret.", + ) # Secret raw message to hide - parser.add_option("-m", "--secret-message", dest="secret_message", - help="Your raw secret message to hide.") + parser.add_option( + "-m", + "--secret-message", + dest="secret_message", + help="Your raw secret message to hide.", + ) # Secret text file to hide. - parser.add_option("-f", "--secret-file", dest="secret_file", - help="Your secret text file to hide.") + parser.add_option( + "-f", + "--secret-file", + dest="secret_file", + help="Your secret text file to hide.", + ) - parser.set_defaults(input_image_file = './pictures/Elisha-Cuthbert.jpg', - output_image_file = './pictures/Elisha-Cuthbert_enc.jpg', - secret_message = '', secret_file = '') + parser.set_defaults( + input_image_file="./pictures/Elisha-Cuthbert.jpg", + output_image_file="./pictures/Elisha-Cuthbert_enc.jpg", + secret_message="", + secret_file="", + ) (options, args) = parser.parse_args() if options.hide: if options.secret_message != "" and options.secret_file == "": - hide(input_image_file=options.input_image_file, \ - img_enc=options.output_image_file, \ - secret_message=options.secret_message) + hide( + input_image_file=options.input_image_file, + img_enc=options.output_image_file, + secret_message=options.secret_message, + ) elif options.secret_message == "" and options.secret_file != "": - hide(input_image_file=options.input_image_file, \ - img_enc=options.output_image_file, \ - secret_file=options.secret_file) + hide( + input_image_file=options.input_image_file, + img_enc=options.output_image_file, + secret_file=options.secret_file, + ) elif options.reveal: reveal(input_image_file=options.input_image_file) diff --git a/stegano/lsb/lsb.py b/stegano/lsb/lsb.py index 8f08391..8c0ac87 100755 --- a/stegano/lsb/lsb.py +++ b/stegano/lsb/lsb.py @@ -1,5 +1,5 @@ #!/usr/bin/env python -#-*- coding: utf-8 -*- +# -*- coding: utf-8 -*- # Stéganô - Stéganô is a basic Python Steganography module. # Copyright (C) 2010-2019 Cédric Bonhomme - https://www.cedricbonhomme.org @@ -25,17 +25,17 @@ __date__ = "$Date: 2016/08/04 $" __revision__ = "$Date: 2017/05/04 $" __license__ = "GPLv3" -import sys - -from PIL import Image -from typing import Union, IO +from typing import IO, Union from stegano import tools -def hide(input_image: Union[str, IO[bytes]], - message: str, - encoding: str = 'UTF-8', - auto_convert_rgb: bool = False): + +def hide( + input_image: Union[str, IO[bytes]], + message: str, + encoding: str = "UTF-8", + auto_convert_rgb: bool = False, +): """Hide a message (string) in an image with the LSB (Least Significant Bit) technique. """ @@ -44,14 +44,15 @@ def hide(input_image: Union[str, IO[bytes]], img = tools.open_image(input_image) - if img.mode not in ['RGB', 'RGBA']: + 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') + 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") encoded = img.copy() width, height = img.size @@ -59,16 +60,19 @@ def hide(input_image: Union[str, IO[bytes]], message = str(message_length) + ":" + str(message) message_bits = "".join(tools.a2bits_list(message, encoding)) - message_bits += '0' * ((3 - (len(message_bits) % 3)) % 3) + 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)) + raise Exception( + "The message you want to hide is too long: {}".format( + message_length + ) + ) for row in range(height): for col in range(width): - if index + 3 <= len_message_bits : + if index + 3 <= len_message_bits: # Get the colour component. pixel = img.getpixel((col, row)) @@ -78,11 +82,11 @@ def hide(input_image: Union[str, IO[bytes]], # 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]) + g = tools.setlsb(g, message_bits[index + 1]) + b = tools.setlsb(b, message_bits[index + 2]) # Save the new pixel - if img.mode == 'RGBA': + if img.mode == "RGBA": encoded.putpixel((col, row), (r, g, b, pixel[3])) else: encoded.putpixel((col, row), (r, g, b)) @@ -93,7 +97,7 @@ def hide(input_image: Union[str, IO[bytes]], return encoded -def reveal(input_image: Union[str, IO[bytes]], encoding='UTF-8'): +def reveal(input_image: Union[str, IO[bytes]], encoding="UTF-8"): """Find a message in an image (with the LSB technique). """ img = tools.open_image(input_image) @@ -106,20 +110,20 @@ def reveal(input_image: Union[str, IO[bytes]], encoding='UTF-8'): # pixel = [r, g, b] or [r,g,b,a] pixel = img.getpixel((col, row)) - if img.mode == 'RGBA': - pixel = pixel[:3] # ignore the alpha + if img.mode == "RGBA": + pixel = pixel[:3] # ignore the alpha for color in pixel: - buff += (color&1)<<(tools.ENCODINGS[encoding]-1 - count) + buff += (color & 1) << (tools.ENCODINGS[encoding] - 1 - count) count += 1 if count == tools.ENCODINGS[encoding]: bitab.append(chr(buff)) buff, count = 0, 0 - if bitab[-1] == ":" and limit == None: + if bitab[-1] == ":" and limit is None: try: limit = int("".join(bitab[:-1])) except: pass - if len(bitab)-len(str(limit))-1 == limit : + if len(bitab) - len(str(limit)) - 1 == limit: img.close() - return "".join(bitab)[len(str(limit))+1:] + return "".join(bitab)[len(str(limit)) + 1 :] diff --git a/stegano/lsbset/generators.py b/stegano/lsbset/generators.py index f01ab41..f0bdee9 100644 --- a/stegano/lsbset/generators.py +++ b/stegano/lsbset/generators.py @@ -25,9 +25,10 @@ __date__ = "$Date: 2011/12/28 $" __revision__ = "$Date: 2017/03/10 $" __license__ = "GPLv3" -import math import itertools -from typing import Iterator, List, Dict +import math +from typing import Dict, Iterator, List + def identity() -> Iterator[int]: """f(x) = x @@ -37,15 +38,17 @@ def identity() -> Iterator[int]: yield n n += 1 + def triangular_numbers() -> Iterator[int]: """Triangular numbers: a(n) = C(n+1,2) = n(n+1)/2 = 0+1+2+...+n. http://oeis.org/A000217 """ n = 0 while True: - yield (n*(n+1))//2 + yield (n * (n + 1)) // 2 n += 1 + def fermat() -> Iterator[int]: """Generate the n-th Fermat Number. https://oeis.org/A000215 @@ -53,7 +56,8 @@ def fermat() -> Iterator[int]: y = 3 while True: yield y - y = pow(y-1,2)+1 + y = pow(y - 1, 2) + 1 + def mersenne() -> Iterator[int]: """Generate 2^p - 1, where p is prime. @@ -61,13 +65,14 @@ def mersenne() -> Iterator[int]: """ prime_numbers = eratosthenes() while True: - yield 2**next(prime_numbers) - 1 + yield 2 ** next(prime_numbers) - 1 + def eratosthenes() -> Iterator[int]: """Generate the prime numbers with the sieve of Eratosthenes. https://oeis.org/A000040 """ - d = {} # type: Dict[int, List[int]] + d = {} # type: Dict[int, List[int]] for i in itertools.count(2): if i in d: for j in d[i]: @@ -77,6 +82,7 @@ def eratosthenes() -> Iterator[int]: d[i * i] = [i] yield i + def composite() -> Iterator[int]: """Generate the composite numbers using the sieve of Eratosthenes. https://oeis.org/A002808 @@ -87,6 +93,7 @@ def composite() -> Iterator[int]: yield n p1 = p2 + def carmichael() -> Iterator[int]: """Composite numbers n such that a^(n-1) == 1 (mod n) for every a coprime to n. @@ -94,11 +101,12 @@ def carmichael() -> Iterator[int]: """ for m in composite(): for a in range(2, m): - if pow(a,m,m) != a: + if pow(a, m, m) != a: break else: yield m + def ackermann_naive(m: int, n: int) -> int: """Ackermann number. """ @@ -109,6 +117,7 @@ def ackermann_naive(m: int, n: int) -> int: else: return ackermann(m - 1, ackermann(m, n - 1)) + def ackermann(m: int, n: int) -> int: """Ackermann number. """ @@ -127,6 +136,7 @@ def ackermann(m: int, n: int) -> int: else: return n + 1 + def fibonacci() -> Iterator[int]: """Generate the sequence of Fibonacci. https://oeis.org/A000045 @@ -136,6 +146,7 @@ def fibonacci() -> Iterator[int]: yield a a, b = b, a + b + def log_gen() -> Iterator[int]: """Logarithmic generator. """ diff --git a/stegano/lsbset/lsbset.py b/stegano/lsbset/lsbset.py index 74cbd0b..04fa9fa 100644 --- a/stegano/lsbset/lsbset.py +++ b/stegano/lsbset/lsbset.py @@ -1,5 +1,5 @@ #!/usr/bin/env python -#-*- coding: utf-8 -*- +# -*- coding: utf-8 -*- # Stéganô - Stéganô is a basic Python Steganography module. # Copyright (C) 2010-2019 Cédric Bonhomme - https://www.cedricbonhomme.org @@ -25,20 +25,21 @@ __date__ = "$Date: 2016/03/13 $" __revision__ = "$Date: 2018/12/18 $" __license__ = "GPLv3" -import sys +from typing import IO, Iterator, Union from PIL import Image -from typing import Union, Iterator, IO from stegano import tools -from . import generators -def hide(input_image: Union[str, IO[bytes]], - message: str, - generator: Iterator[int], - shift: int = 0, - encoding: str = 'UTF-8', - auto_convert_rgb: bool = False): + +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. """ @@ -47,14 +48,15 @@ def hide(input_image: Union[str, IO[bytes]], img = tools.open_image(input_image) - if img.mode not in ['RGB', 'RGBA']: + 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') + 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 @@ -62,36 +64,39 @@ def hide(input_image: Union[str, IO[bytes]], message = str(message_length) + ":" + str(message) message_bits = "".join(tools.a2bits_list(message, encoding)) - message_bits += '0' * ((3 - (len(message_bits) % 3)) % 3) + 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)) + 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 : + 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]) + g = tools.setlsb(g, message_bits[index + 1]) + b = tools.setlsb(b, message_bits[index + 2]) # Save the new pixel - if img.mode == 'RGBA': - img_list[generated_number] = (r, g , b, a[0]) + if img.mode == "RGBA": + img_list[generated_number] = (r, g, b, a[0]) else: - img_list[generated_number] = (r, g , b) + img_list[generated_number] = (r, g, b) index += 3 # create empty new image of appropriate format - encoded = Image.new('RGB', (img.size)) + encoded = Image.new("RGB", (img.size)) # insert saved data into the image encoded.putdata(img_list) @@ -99,10 +104,12 @@ def hide(input_image: Union[str, IO[bytes]], return encoded -def reveal(input_image: Union[str, IO[bytes]], - generator: Iterator[int], - shift: int = 0, - encoding: str = 'UTF-8'): +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) @@ -120,7 +127,7 @@ def reveal(input_image: Union[str, IO[bytes]], generated_number = next(generator) # color = [r, g, b] for color in img_list[generated_number]: - buff += (color&1)<<(tools.ENCODINGS[encoding]-1 - count) + buff += (color & 1) << (tools.ENCODINGS[encoding] - 1 - count) count += 1 if count == tools.ENCODINGS[encoding]: bitab.append(chr(buff)) @@ -130,5 +137,5 @@ def reveal(input_image: Union[str, IO[bytes]], 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:] + if len(bitab) - len(str(limit)) - 1 == limit: + return "".join(bitab)[len(str(limit)) + 1 :] diff --git a/stegano/red/red.py b/stegano/red/red.py index 0488d90..ff841b5 100755 --- a/stegano/red/red.py +++ b/stegano/red/red.py @@ -25,12 +25,11 @@ __date__ = "$Date: 2010/10/01 $" __revision__ = "$Date: 2017/02/06 $" __license__ = "GPLv3" -import sys +from typing import IO, Union -from PIL import Image -from typing import Union, IO from stegano import tools + def hide(input_image: Union[str, IO[bytes]], message: str): """ Hide a message (string) in an image. @@ -54,15 +53,16 @@ def hide(input_image: Union[str, IO[bytes]], message: str): if row == 0 and col == 0 and index < message_length: asc = message_length elif index <= message_length: - c = message[index -1] + c = message[index - 1] asc = ord(c) else: asc = r - encoded.putpixel((col, row), (asc, g , b)) + encoded.putpixel((col, row), (asc, g, b)) index += 1 img.close() return encoded + def reveal(input_image: Union[str, IO[bytes]]): """ Find a message in an image. diff --git a/stegano/tools.py b/stegano/tools.py index 48f4d39..897bba4 100755 --- a/stegano/tools.py +++ b/stegano/tools.py @@ -27,16 +27,13 @@ __license__ = "GPLv3" import base64 import itertools -from typing import List, Iterator, Tuple, Union from functools import reduce from typing import IO, Iterator, List, Tuple, Union from PIL import Image -ENCODINGS = { - 'UTF-8': 8, - 'UTF-32LE': 32 -} +ENCODINGS = {"UTF-8": 8, "UTF-32LE": 32} + def a2bits(chars: str) -> str: """Converts a string to its bits representation as a string of 0's and 1's. @@ -44,9 +41,12 @@ def a2bits(chars: str) -> str: >>> a2bits("Hello World!") '010010000110010101101100011011000110111100100000010101110110111101110010011011000110010000100001' """ - return bin(reduce(lambda x, y : (x<<8)+y, (ord(c) for c in chars), 1))[3:] + return bin(reduce(lambda x, y: (x << 8) + y, (ord(c) for c in chars), 1))[ + 3: + ] -def a2bits_list(chars: str, encoding: str ='UTF-8') -> List[str]: + +def a2bits_list(chars: str, encoding: str = "UTF-8") -> List[str]: """Convert a string to its bits representation as a list of 0's and 1's. >>> a2bits_list("Hello World!") @@ -65,20 +65,24 @@ def a2bits_list(chars: str, encoding: str ='UTF-8') -> List[str]: >>> "".join(a2bits_list("Hello World!")) '010010000110010101101100011011000110111100100000010101110110111101110010011011000110010000100001' """ - return [bin(ord(x))[2:].rjust(ENCODINGS[encoding],"0") for x in chars] + return [bin(ord(x))[2:].rjust(ENCODINGS[encoding], "0") for x in chars] + def bs(s: int) -> str: """Converts an int to its bits representation as a string of 0's and 1's. """ - return str(s) if s<=1 else bs(s>>1) + str(s&1) + return str(s) if s <= 1 else bs(s >> 1) + str(s & 1) + def setlsb(component: int, bit: str) -> int: """Set Least Significant Bit of a colour component. """ return component & ~1 | int(bit) -def n_at_a_time(items: List[int], n: int, fillvalue: str) \ - -> Iterator[Tuple[Union[int, str]]]: + +def n_at_a_time( + items: List[int], n: int, fillvalue: str +) -> Iterator[Tuple[Union[int, str]]]: """Returns an iterator which groups n items at a time. Any final partial tuple will be padded with the fillvalue @@ -88,6 +92,7 @@ def n_at_a_time(items: List[int], n: int, fillvalue: str) \ it = iter(items) return itertools.zip_longest(*[it] * n, fillvalue=fillvalue) + def binary2base64(binary_file: str) -> str: """Convert a binary file (OGG, executable, etc.) to a printable string. @@ -97,12 +102,14 @@ def binary2base64(binary_file: str) -> str: encoded_string = base64.b64encode(bin_file.read()) return encoded_string.decode() + def base642binary(b64_fname: str) -> bytes: """Convert a printable string to a binary file. """ - b64_fname += '===' + b64_fname += "===" return base64.b64decode(b64_fname) + def open_image(fname_or_instance: Union[str, IO[bytes]]): """Opens a Image and returns it.