mirror of
https://github.com/cedricbonhomme/Stegano.git
synced 2025-05-12 17:18:30 +02:00
Merge pull request #22 from sh4nks/master
Pass Image.Image instance to 'hide' and 'reveal' methods
This commit is contained in:
commit
c5b7df87f5
6 changed files with 205 additions and 124 deletions
|
@ -1,5 +1,5 @@
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
#-*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Stéganô - Stéganô is a basic Python Steganography module.
|
# Stéganô - Stéganô is a basic Python Steganography module.
|
||||||
# Copyright (C) 2010-2019 Cédric Bonhomme - https://www.cedricbonhomme.org
|
# Copyright (C) 2010-2019 Cédric Bonhomme - https://www.cedricbonhomme.org
|
||||||
|
@ -25,10 +25,17 @@ __date__ = "$Date: 2016/05/26 $"
|
||||||
__revision__ = "$Date: 2017/01/18 $"
|
__revision__ = "$Date: 2017/01/18 $"
|
||||||
__license__ = "GPLv3"
|
__license__ = "GPLv3"
|
||||||
|
|
||||||
from PIL import Image
|
|
||||||
import piexif
|
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.
|
"""Hide a message (string) in an image.
|
||||||
"""
|
"""
|
||||||
from zlib import compress
|
from zlib import compress
|
||||||
|
@ -43,7 +50,7 @@ def hide(input_image_file, img_enc, secret_message = None, secret_file = None, i
|
||||||
except:
|
except:
|
||||||
text = compress(b64encode(secret_message))
|
text = compress(b64encode(secret_message))
|
||||||
|
|
||||||
img = Image.open(input_image_file)
|
img = tools.open_image(input_image_file)
|
||||||
|
|
||||||
if img_format is None:
|
if img_format is None:
|
||||||
img_format = img.format
|
img_format = img.format
|
||||||
|
@ -66,15 +73,16 @@ def reveal(input_image_file):
|
||||||
from base64 import b64decode
|
from base64 import b64decode
|
||||||
from zlib import decompress
|
from zlib import decompress
|
||||||
|
|
||||||
img = Image.open(input_image_file)
|
img = tools.open_image(input_image_file)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if img.format in ['JPEG', 'TIFF']:
|
if img.format in ["JPEG", "TIFF"]:
|
||||||
if 'exif' in img.info:
|
if "exif" in img.info:
|
||||||
exif_dict = piexif.load(img.info.get("exif", b''))
|
exif_dict = piexif.load(img.info.get("exif", b""))
|
||||||
description_key = piexif.ImageIFD.ImageDescription
|
description_key = piexif.ImageIFD.ImageDescription
|
||||||
encoded_message = exif_dict["0th"][description_key]
|
encoded_message = exif_dict["0th"][description_key]
|
||||||
else:
|
else:
|
||||||
encoded_message = b''
|
encoded_message = b""
|
||||||
else:
|
else:
|
||||||
raise ValueError("Given file is neither JPEG nor TIFF.")
|
raise ValueError("Given file is neither JPEG nor TIFF.")
|
||||||
finally:
|
finally:
|
||||||
|
@ -87,41 +95,70 @@ if __name__ == "__main__":
|
||||||
# Point of entry in execution mode.
|
# Point of entry in execution mode.
|
||||||
# TODO: improve the management of arguments
|
# TODO: improve the management of arguments
|
||||||
from optparse import OptionParser
|
from optparse import OptionParser
|
||||||
|
|
||||||
parser = OptionParser(version=__version__)
|
parser = OptionParser(version=__version__)
|
||||||
parser.add_option('--hide', action='store_true', default=False,
|
parser.add_option(
|
||||||
help="Hides a message in an image.")
|
"--hide",
|
||||||
parser.add_option('--reveal', action='store_true', default=False,
|
action="store_true",
|
||||||
help="Reveals the message hided in an image.")
|
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
|
# Original image
|
||||||
parser.add_option("-i", "--input", dest="input_image_file",
|
parser.add_option(
|
||||||
help="Input image file.")
|
"-i", "--input", dest="input_image_file", help="Input image file."
|
||||||
|
)
|
||||||
# Image containing the secret
|
# Image containing the secret
|
||||||
parser.add_option("-o", "--output", dest="output_image_file",
|
parser.add_option(
|
||||||
help="Output image containing the secret.")
|
"-o",
|
||||||
|
"--output",
|
||||||
|
dest="output_image_file",
|
||||||
|
help="Output image containing the secret.",
|
||||||
|
)
|
||||||
|
|
||||||
# Secret raw message to hide
|
# Secret raw message to hide
|
||||||
parser.add_option("-m", "--secret-message", dest="secret_message",
|
parser.add_option(
|
||||||
help="Your raw secret message to hide.")
|
"-m",
|
||||||
|
"--secret-message",
|
||||||
|
dest="secret_message",
|
||||||
|
help="Your raw secret message to hide.",
|
||||||
|
)
|
||||||
|
|
||||||
# Secret text file to hide.
|
# Secret text file to hide.
|
||||||
parser.add_option("-f", "--secret-file", dest="secret_file",
|
parser.add_option(
|
||||||
help="Your secret text file to hide.")
|
"-f",
|
||||||
|
"--secret-file",
|
||||||
|
dest="secret_file",
|
||||||
|
help="Your secret text file to hide.",
|
||||||
|
)
|
||||||
|
|
||||||
parser.set_defaults(input_image_file = './pictures/Elisha-Cuthbert.jpg',
|
parser.set_defaults(
|
||||||
output_image_file = './pictures/Elisha-Cuthbert_enc.jpg',
|
input_image_file="./pictures/Elisha-Cuthbert.jpg",
|
||||||
secret_message = '', secret_file = '')
|
output_image_file="./pictures/Elisha-Cuthbert_enc.jpg",
|
||||||
|
secret_message="",
|
||||||
|
secret_file="",
|
||||||
|
)
|
||||||
|
|
||||||
(options, args) = parser.parse_args()
|
(options, args) = parser.parse_args()
|
||||||
|
|
||||||
if options.hide:
|
if options.hide:
|
||||||
if options.secret_message != "" and options.secret_file == "":
|
if options.secret_message != "" and options.secret_file == "":
|
||||||
hide(input_image_file=options.input_image_file, \
|
hide(
|
||||||
img_enc=options.output_image_file, \
|
input_image_file=options.input_image_file,
|
||||||
secret_message=options.secret_message)
|
img_enc=options.output_image_file,
|
||||||
|
secret_message=options.secret_message,
|
||||||
|
)
|
||||||
elif options.secret_message == "" and options.secret_file != "":
|
elif options.secret_message == "" and options.secret_file != "":
|
||||||
hide(input_image_file=options.input_image_file, \
|
hide(
|
||||||
img_enc=options.output_image_file, \
|
input_image_file=options.input_image_file,
|
||||||
secret_file=options.secret_file)
|
img_enc=options.output_image_file,
|
||||||
|
secret_file=options.secret_file,
|
||||||
|
)
|
||||||
|
|
||||||
elif options.reveal:
|
elif options.reveal:
|
||||||
reveal(input_image_file=options.input_image_file)
|
reveal(input_image_file=options.input_image_file)
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
#-*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Stéganô - Stéganô is a basic Python Steganography module.
|
# Stéganô - Stéganô is a basic Python Steganography module.
|
||||||
# Copyright (C) 2010-2019 Cédric Bonhomme - https://www.cedricbonhomme.org
|
# Copyright (C) 2010-2019 Cédric Bonhomme - https://www.cedricbonhomme.org
|
||||||
|
@ -25,33 +25,34 @@ __date__ = "$Date: 2016/08/04 $"
|
||||||
__revision__ = "$Date: 2017/05/04 $"
|
__revision__ = "$Date: 2017/05/04 $"
|
||||||
__license__ = "GPLv3"
|
__license__ = "GPLv3"
|
||||||
|
|
||||||
import sys
|
from typing import IO, Union
|
||||||
|
|
||||||
from PIL import Image
|
|
||||||
from typing import Union, IO
|
|
||||||
|
|
||||||
from stegano import tools
|
from stegano import tools
|
||||||
|
|
||||||
def hide(input_image: Union[str, IO[bytes]],
|
|
||||||
message: str,
|
def hide(
|
||||||
encoding: str = 'UTF-8',
|
input_image: Union[str, IO[bytes]],
|
||||||
auto_convert_rgb: bool = False):
|
message: str,
|
||||||
|
encoding: str = "UTF-8",
|
||||||
|
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)
|
message_length = len(message)
|
||||||
assert message_length != 0, "message length is zero"
|
assert message_length != 0, "message length is zero"
|
||||||
|
|
||||||
img = Image.open(input_image)
|
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:
|
if not auto_convert_rgb:
|
||||||
print('The mode of the image is not RGB. Mode is {}'.\
|
print(
|
||||||
format(img.mode))
|
"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':
|
answer = input("Convert the image to RGB ? [Y / n]\n") or "Y"
|
||||||
raise Exception('Not a RGB image.')
|
if answer.lower() == "n":
|
||||||
img = img.convert('RGB')
|
raise Exception("Not a RGB image.")
|
||||||
|
img = img.convert("RGB")
|
||||||
|
|
||||||
encoded = img.copy()
|
encoded = img.copy()
|
||||||
width, height = img.size
|
width, height = img.size
|
||||||
|
@ -59,16 +60,19 @@ def hide(input_image: Union[str, IO[bytes]],
|
||||||
|
|
||||||
message = str(message_length) + ":" + str(message)
|
message = str(message_length) + ":" + str(message)
|
||||||
message_bits = "".join(tools.a2bits_list(message, encoding))
|
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
|
npixels = width * height
|
||||||
len_message_bits = len(message_bits)
|
len_message_bits = len(message_bits)
|
||||||
if len_message_bits > npixels * 3:
|
if len_message_bits > npixels * 3:
|
||||||
raise Exception("The message you want to hide is too long: {}". \
|
raise Exception(
|
||||||
format(message_length))
|
"The message you want to hide is too long: {}".format(
|
||||||
|
message_length
|
||||||
|
)
|
||||||
|
)
|
||||||
for row in range(height):
|
for row in range(height):
|
||||||
for col in range(width):
|
for col in range(width):
|
||||||
if index + 3 <= len_message_bits :
|
if index + 3 <= len_message_bits:
|
||||||
|
|
||||||
# Get the colour component.
|
# Get the colour component.
|
||||||
pixel = img.getpixel((col, row))
|
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.
|
# Change the Least Significant Bit of each colour component.
|
||||||
r = tools.setlsb(r, message_bits[index])
|
r = tools.setlsb(r, message_bits[index])
|
||||||
g = tools.setlsb(g, message_bits[index+1])
|
g = tools.setlsb(g, message_bits[index + 1])
|
||||||
b = tools.setlsb(b, message_bits[index+2])
|
b = tools.setlsb(b, message_bits[index + 2])
|
||||||
|
|
||||||
# Save the new pixel
|
# Save the new pixel
|
||||||
if img.mode == 'RGBA':
|
if img.mode == "RGBA":
|
||||||
encoded.putpixel((col, row), (r, g, b, pixel[3]))
|
encoded.putpixel((col, row), (r, g, b, pixel[3]))
|
||||||
else:
|
else:
|
||||||
encoded.putpixel((col, row), (r, g, b))
|
encoded.putpixel((col, row), (r, g, b))
|
||||||
|
@ -93,10 +97,10 @@ def hide(input_image: Union[str, IO[bytes]],
|
||||||
return encoded
|
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).
|
"""Find a message in an image (with the LSB technique).
|
||||||
"""
|
"""
|
||||||
img = Image.open(input_image)
|
img = tools.open_image(input_image)
|
||||||
width, height = img.size
|
width, height = img.size
|
||||||
buff, count = 0, 0
|
buff, count = 0, 0
|
||||||
bitab = []
|
bitab = []
|
||||||
|
@ -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 = [r, g, b] or [r,g,b,a]
|
||||||
pixel = img.getpixel((col, row))
|
pixel = img.getpixel((col, row))
|
||||||
if img.mode == 'RGBA':
|
if img.mode == "RGBA":
|
||||||
pixel = pixel[:3] # ignore the alpha
|
pixel = pixel[:3] # ignore the alpha
|
||||||
for color in pixel:
|
for color in pixel:
|
||||||
buff += (color&1)<<(tools.ENCODINGS[encoding]-1 - count)
|
buff += (color & 1) << (tools.ENCODINGS[encoding] - 1 - count)
|
||||||
count += 1
|
count += 1
|
||||||
if count == tools.ENCODINGS[encoding]:
|
if count == tools.ENCODINGS[encoding]:
|
||||||
bitab.append(chr(buff))
|
bitab.append(chr(buff))
|
||||||
buff, count = 0, 0
|
buff, count = 0, 0
|
||||||
if bitab[-1] == ":" and limit == None:
|
if bitab[-1] == ":" and limit is None:
|
||||||
try:
|
try:
|
||||||
limit = int("".join(bitab[:-1]))
|
limit = int("".join(bitab[:-1]))
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if len(bitab)-len(str(limit))-1 == limit :
|
if len(bitab) - len(str(limit)) - 1 == limit:
|
||||||
img.close()
|
img.close()
|
||||||
return "".join(bitab)[len(str(limit))+1:]
|
return "".join(bitab)[len(str(limit)) + 1 :]
|
||||||
|
|
|
@ -25,9 +25,10 @@ __date__ = "$Date: 2011/12/28 $"
|
||||||
__revision__ = "$Date: 2017/03/10 $"
|
__revision__ = "$Date: 2017/03/10 $"
|
||||||
__license__ = "GPLv3"
|
__license__ = "GPLv3"
|
||||||
|
|
||||||
import math
|
|
||||||
import itertools
|
import itertools
|
||||||
from typing import Iterator, List, Dict
|
import math
|
||||||
|
from typing import Dict, Iterator, List
|
||||||
|
|
||||||
|
|
||||||
def identity() -> Iterator[int]:
|
def identity() -> Iterator[int]:
|
||||||
"""f(x) = x
|
"""f(x) = x
|
||||||
|
@ -37,15 +38,17 @@ def identity() -> Iterator[int]:
|
||||||
yield n
|
yield n
|
||||||
n += 1
|
n += 1
|
||||||
|
|
||||||
|
|
||||||
def triangular_numbers() -> Iterator[int]:
|
def triangular_numbers() -> Iterator[int]:
|
||||||
"""Triangular numbers: a(n) = C(n+1,2) = n(n+1)/2 = 0+1+2+...+n.
|
"""Triangular numbers: a(n) = C(n+1,2) = n(n+1)/2 = 0+1+2+...+n.
|
||||||
http://oeis.org/A000217
|
http://oeis.org/A000217
|
||||||
"""
|
"""
|
||||||
n = 0
|
n = 0
|
||||||
while True:
|
while True:
|
||||||
yield (n*(n+1))//2
|
yield (n * (n + 1)) // 2
|
||||||
n += 1
|
n += 1
|
||||||
|
|
||||||
|
|
||||||
def fermat() -> Iterator[int]:
|
def fermat() -> Iterator[int]:
|
||||||
"""Generate the n-th Fermat Number.
|
"""Generate the n-th Fermat Number.
|
||||||
https://oeis.org/A000215
|
https://oeis.org/A000215
|
||||||
|
@ -53,7 +56,8 @@ def fermat() -> Iterator[int]:
|
||||||
y = 3
|
y = 3
|
||||||
while True:
|
while True:
|
||||||
yield y
|
yield y
|
||||||
y = pow(y-1,2)+1
|
y = pow(y - 1, 2) + 1
|
||||||
|
|
||||||
|
|
||||||
def mersenne() -> Iterator[int]:
|
def mersenne() -> Iterator[int]:
|
||||||
"""Generate 2^p - 1, where p is prime.
|
"""Generate 2^p - 1, where p is prime.
|
||||||
|
@ -61,13 +65,14 @@ def mersenne() -> Iterator[int]:
|
||||||
"""
|
"""
|
||||||
prime_numbers = eratosthenes()
|
prime_numbers = eratosthenes()
|
||||||
while True:
|
while True:
|
||||||
yield 2**next(prime_numbers) - 1
|
yield 2 ** next(prime_numbers) - 1
|
||||||
|
|
||||||
|
|
||||||
def eratosthenes() -> Iterator[int]:
|
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 = {} # type: 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]:
|
||||||
|
@ -77,6 +82,7 @@ def eratosthenes() -> Iterator[int]:
|
||||||
d[i * i] = [i]
|
d[i * i] = [i]
|
||||||
yield i
|
yield i
|
||||||
|
|
||||||
|
|
||||||
def composite() -> Iterator[int]:
|
def composite() -> Iterator[int]:
|
||||||
"""Generate the composite numbers using the sieve of Eratosthenes.
|
"""Generate the composite numbers using the sieve of Eratosthenes.
|
||||||
https://oeis.org/A002808
|
https://oeis.org/A002808
|
||||||
|
@ -87,6 +93,7 @@ def composite() -> Iterator[int]:
|
||||||
yield n
|
yield n
|
||||||
p1 = p2
|
p1 = p2
|
||||||
|
|
||||||
|
|
||||||
def carmichael() -> Iterator[int]:
|
def carmichael() -> Iterator[int]:
|
||||||
"""Composite numbers n such that a^(n-1) == 1 (mod n) for every a coprime
|
"""Composite numbers n such that a^(n-1) == 1 (mod n) for every a coprime
|
||||||
to n.
|
to n.
|
||||||
|
@ -94,11 +101,12 @@ def carmichael() -> Iterator[int]:
|
||||||
"""
|
"""
|
||||||
for m in composite():
|
for m in composite():
|
||||||
for a in range(2, m):
|
for a in range(2, m):
|
||||||
if pow(a,m,m) != a:
|
if pow(a, m, m) != a:
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
yield m
|
yield m
|
||||||
|
|
||||||
|
|
||||||
def ackermann_naive(m: int, n: int) -> int:
|
def ackermann_naive(m: int, n: int) -> int:
|
||||||
"""Ackermann number.
|
"""Ackermann number.
|
||||||
"""
|
"""
|
||||||
|
@ -109,6 +117,7 @@ def ackermann_naive(m: int, n: int) -> int:
|
||||||
else:
|
else:
|
||||||
return ackermann(m - 1, ackermann(m, n - 1))
|
return ackermann(m - 1, ackermann(m, n - 1))
|
||||||
|
|
||||||
|
|
||||||
def ackermann(m: int, n: int) -> int:
|
def ackermann(m: int, n: int) -> int:
|
||||||
"""Ackermann number.
|
"""Ackermann number.
|
||||||
"""
|
"""
|
||||||
|
@ -127,6 +136,7 @@ def ackermann(m: int, n: int) -> int:
|
||||||
else:
|
else:
|
||||||
return n + 1
|
return n + 1
|
||||||
|
|
||||||
|
|
||||||
def fibonacci() -> Iterator[int]:
|
def fibonacci() -> Iterator[int]:
|
||||||
"""Generate the sequence of Fibonacci.
|
"""Generate the sequence of Fibonacci.
|
||||||
https://oeis.org/A000045
|
https://oeis.org/A000045
|
||||||
|
@ -136,6 +146,7 @@ def fibonacci() -> Iterator[int]:
|
||||||
yield a
|
yield a
|
||||||
a, b = b, a + b
|
a, b = b, a + b
|
||||||
|
|
||||||
|
|
||||||
def log_gen() -> Iterator[int]:
|
def log_gen() -> Iterator[int]:
|
||||||
"""Logarithmic generator.
|
"""Logarithmic generator.
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
#-*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# Stéganô - Stéganô is a basic Python Steganography module.
|
# Stéganô - Stéganô is a basic Python Steganography module.
|
||||||
# Copyright (C) 2010-2019 Cédric Bonhomme - https://www.cedricbonhomme.org
|
# Copyright (C) 2010-2019 Cédric Bonhomme - https://www.cedricbonhomme.org
|
||||||
|
@ -25,36 +25,38 @@ __date__ = "$Date: 2016/03/13 $"
|
||||||
__revision__ = "$Date: 2018/12/18 $"
|
__revision__ = "$Date: 2018/12/18 $"
|
||||||
__license__ = "GPLv3"
|
__license__ = "GPLv3"
|
||||||
|
|
||||||
import sys
|
from typing import IO, Iterator, Union
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
from typing import Union, Iterator, IO
|
|
||||||
|
|
||||||
from stegano import tools
|
from stegano import tools
|
||||||
from . import generators
|
|
||||||
|
|
||||||
def hide(input_image: Union[str, IO[bytes]],
|
|
||||||
message: str,
|
def hide(
|
||||||
generator: Iterator[int],
|
input_image: Union[str, IO[bytes]],
|
||||||
shift: int = 0,
|
message: str,
|
||||||
encoding: str = 'UTF-8',
|
generator: Iterator[int],
|
||||||
auto_convert_rgb: bool = False):
|
shift: int = 0,
|
||||||
|
encoding: str = "UTF-8",
|
||||||
|
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)
|
message_length = len(message)
|
||||||
assert message_length != 0, "message length is zero"
|
assert message_length != 0, "message length is zero"
|
||||||
|
|
||||||
img = Image.open(input_image)
|
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:
|
if not auto_convert_rgb:
|
||||||
print('The mode of the image is not RGB. Mode is {}'.\
|
print(
|
||||||
format(img.mode))
|
"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':
|
answer = input("Convert the image to RGB ? [Y / n]\n") or "Y"
|
||||||
raise Exception('Not a RGB image.')
|
if answer.lower() == "n":
|
||||||
img = img.convert('RGB')
|
raise Exception("Not a RGB image.")
|
||||||
|
img = img.convert("RGB")
|
||||||
|
|
||||||
img_list = list(img.getdata())
|
img_list = list(img.getdata())
|
||||||
width, height = img.size
|
width, height = img.size
|
||||||
|
@ -62,36 +64,39 @@ def hide(input_image: Union[str, IO[bytes]],
|
||||||
|
|
||||||
message = str(message_length) + ":" + str(message)
|
message = str(message_length) + ":" + str(message)
|
||||||
message_bits = "".join(tools.a2bits_list(message, encoding))
|
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
|
npixels = width * height
|
||||||
len_message_bits = len(message_bits)
|
len_message_bits = len(message_bits)
|
||||||
if len_message_bits > npixels * 3:
|
if len_message_bits > npixels * 3:
|
||||||
raise Exception("The message you want to hide is too long: {}". \
|
raise Exception(
|
||||||
format(message_length))
|
"The message you want to hide is too long: {}".format(
|
||||||
|
message_length
|
||||||
|
)
|
||||||
|
)
|
||||||
while shift != 0:
|
while shift != 0:
|
||||||
next(generator)
|
next(generator)
|
||||||
shift -= 1
|
shift -= 1
|
||||||
|
|
||||||
while index + 3 <= len_message_bits :
|
while index + 3 <= len_message_bits:
|
||||||
generated_number = next(generator)
|
generated_number = next(generator)
|
||||||
r, g, b, *a = img_list[generated_number]
|
r, g, b, *a = img_list[generated_number]
|
||||||
|
|
||||||
# Change the Least Significant Bit of each colour component.
|
# Change the Least Significant Bit of each colour component.
|
||||||
r = tools.setlsb(r, message_bits[index])
|
r = tools.setlsb(r, message_bits[index])
|
||||||
g = tools.setlsb(g, message_bits[index+1])
|
g = tools.setlsb(g, message_bits[index + 1])
|
||||||
b = tools.setlsb(b, message_bits[index+2])
|
b = tools.setlsb(b, message_bits[index + 2])
|
||||||
|
|
||||||
# Save the new pixel
|
# Save the new pixel
|
||||||
if img.mode == 'RGBA':
|
if img.mode == "RGBA":
|
||||||
img_list[generated_number] = (r, g , b, a[0])
|
img_list[generated_number] = (r, g, b, a[0])
|
||||||
else:
|
else:
|
||||||
img_list[generated_number] = (r, g , b)
|
img_list[generated_number] = (r, g, b)
|
||||||
|
|
||||||
index += 3
|
index += 3
|
||||||
|
|
||||||
# create empty new image of appropriate format
|
# 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
|
# insert saved data into the image
|
||||||
encoded.putdata(img_list)
|
encoded.putdata(img_list)
|
||||||
|
@ -99,13 +104,15 @@ def hide(input_image: Union[str, IO[bytes]],
|
||||||
return encoded
|
return encoded
|
||||||
|
|
||||||
|
|
||||||
def reveal(input_image: Union[str, IO[bytes]],
|
def reveal(
|
||||||
generator: Iterator[int],
|
input_image: Union[str, IO[bytes]],
|
||||||
shift: int = 0,
|
generator: Iterator[int],
|
||||||
encoding: str = 'UTF-8'):
|
shift: int = 0,
|
||||||
|
encoding: str = "UTF-8",
|
||||||
|
):
|
||||||
"""Find a message in an image (with the LSB technique).
|
"""Find a message in an image (with the LSB technique).
|
||||||
"""
|
"""
|
||||||
img = Image.open(input_image)
|
img = tools.open_image(input_image)
|
||||||
img_list = list(img.getdata())
|
img_list = list(img.getdata())
|
||||||
width, height = img.size
|
width, height = img.size
|
||||||
buff, count = 0, 0
|
buff, count = 0, 0
|
||||||
|
@ -120,7 +127,7 @@ def reveal(input_image: Union[str, IO[bytes]],
|
||||||
generated_number = next(generator)
|
generated_number = next(generator)
|
||||||
# color = [r, g, b]
|
# color = [r, g, b]
|
||||||
for color in img_list[generated_number]:
|
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
|
count += 1
|
||||||
if count == tools.ENCODINGS[encoding]:
|
if count == tools.ENCODINGS[encoding]:
|
||||||
bitab.append(chr(buff))
|
bitab.append(chr(buff))
|
||||||
|
@ -130,5 +137,5 @@ def reveal(input_image: Union[str, IO[bytes]],
|
||||||
limit = int("".join(bitab[:-1]))
|
limit = int("".join(bitab[:-1]))
|
||||||
else:
|
else:
|
||||||
raise IndexError("Impossible to detect message.")
|
raise IndexError("Impossible to detect message.")
|
||||||
if len(bitab)-len(str(limit))-1 == limit :
|
if len(bitab) - len(str(limit)) - 1 == limit:
|
||||||
return "".join(bitab)[len(str(limit))+1:]
|
return "".join(bitab)[len(str(limit)) + 1 :]
|
||||||
|
|
|
@ -25,10 +25,10 @@ __date__ = "$Date: 2010/10/01 $"
|
||||||
__revision__ = "$Date: 2017/02/06 $"
|
__revision__ = "$Date: 2017/02/06 $"
|
||||||
__license__ = "GPLv3"
|
__license__ = "GPLv3"
|
||||||
|
|
||||||
import sys
|
from typing import IO, Union
|
||||||
|
|
||||||
|
from stegano import tools
|
||||||
|
|
||||||
from PIL import Image
|
|
||||||
from typing import Union, IO
|
|
||||||
|
|
||||||
def hide(input_image: Union[str, IO[bytes]], message: str):
|
def hide(input_image: Union[str, IO[bytes]], message: str):
|
||||||
"""
|
"""
|
||||||
|
@ -41,7 +41,7 @@ def hide(input_image: Union[str, IO[bytes]], message: str):
|
||||||
message_length = len(message)
|
message_length = len(message)
|
||||||
assert message_length != 0, "message message_length is zero"
|
assert message_length != 0, "message message_length is zero"
|
||||||
assert message_length < 255, "message is too long"
|
assert message_length < 255, "message is too long"
|
||||||
img = Image.open(input_image)
|
img = tools.open_image(input_image)
|
||||||
# Use a copy of image to hide the text in
|
# Use a copy of image to hide the text in
|
||||||
encoded = img.copy()
|
encoded = img.copy()
|
||||||
width, height = img.size
|
width, height = img.size
|
||||||
|
@ -53,15 +53,16 @@ def hide(input_image: Union[str, IO[bytes]], message: str):
|
||||||
if row == 0 and col == 0 and index < message_length:
|
if row == 0 and col == 0 and index < message_length:
|
||||||
asc = message_length
|
asc = message_length
|
||||||
elif index <= message_length:
|
elif index <= message_length:
|
||||||
c = message[index -1]
|
c = message[index - 1]
|
||||||
asc = ord(c)
|
asc = ord(c)
|
||||||
else:
|
else:
|
||||||
asc = r
|
asc = r
|
||||||
encoded.putpixel((col, row), (asc, g , b))
|
encoded.putpixel((col, row), (asc, g, b))
|
||||||
index += 1
|
index += 1
|
||||||
img.close()
|
img.close()
|
||||||
return encoded
|
return encoded
|
||||||
|
|
||||||
|
|
||||||
def reveal(input_image: Union[str, IO[bytes]]):
|
def reveal(input_image: Union[str, IO[bytes]]):
|
||||||
"""
|
"""
|
||||||
Find a message in an image.
|
Find a message in an image.
|
||||||
|
@ -70,7 +71,7 @@ def reveal(input_image: Union[str, IO[bytes]]):
|
||||||
hidden message characters (ASCII values).
|
hidden message characters (ASCII values).
|
||||||
The red value of the first pixel is used for message_length of string.
|
The red value of the first pixel is used for message_length of string.
|
||||||
"""
|
"""
|
||||||
img = Image.open(input_image)
|
img = tools.open_image(input_image)
|
||||||
width, height = img.size
|
width, height = img.size
|
||||||
message = ""
|
message = ""
|
||||||
index = 0
|
index = 0
|
||||||
|
|
|
@ -27,13 +27,13 @@ __license__ = "GPLv3"
|
||||||
|
|
||||||
import base64
|
import base64
|
||||||
import itertools
|
import itertools
|
||||||
from typing import List, Iterator, Tuple, Union
|
|
||||||
from functools import reduce
|
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:
|
def a2bits(chars: str) -> str:
|
||||||
"""Converts a string to its bits representation as a string of 0's and 1's.
|
"""Converts a string to its bits representation as a string of 0's and 1's.
|
||||||
|
@ -41,9 +41,12 @@ def a2bits(chars: str) -> str:
|
||||||
>>> a2bits("Hello World!")
|
>>> a2bits("Hello World!")
|
||||||
'010010000110010101101100011011000110111100100000010101110110111101110010011011000110010000100001'
|
'010010000110010101101100011011000110111100100000010101110110111101110010011011000110010000100001'
|
||||||
"""
|
"""
|
||||||
return bin(reduce(lambda x, y : (x<<8)+y, (ord(c) for c in chars), 1))[3:]
|
return bin(reduce(lambda x, y: (x << 8) + y, (ord(c) for c in chars), 1))[
|
||||||
|
3:
|
||||||
|
]
|
||||||
|
|
||||||
def a2bits_list(chars: 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.
|
"""Convert a string to its bits representation as a list of 0's and 1's.
|
||||||
|
|
||||||
>>> a2bits_list("Hello World!")
|
>>> a2bits_list("Hello World!")
|
||||||
|
@ -62,20 +65,24 @@ def a2bits_list(chars: str, encoding: str ='UTF-8') -> List[str]:
|
||||||
>>> "".join(a2bits_list("Hello World!"))
|
>>> "".join(a2bits_list("Hello World!"))
|
||||||
'010010000110010101101100011011000110111100100000010101110110111101110010011011000110010000100001'
|
'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:
|
def bs(s: int) -> str:
|
||||||
"""Converts an int to its bits representation as a string of 0's and 1's.
|
"""Converts an int to its bits representation as a string of 0's and 1's.
|
||||||
"""
|
"""
|
||||||
return str(s) if s<=1 else bs(s>>1) + str(s&1)
|
return str(s) if s <= 1 else bs(s >> 1) + str(s & 1)
|
||||||
|
|
||||||
|
|
||||||
def setlsb(component: int, bit: str) -> int:
|
def setlsb(component: int, bit: str) -> int:
|
||||||
"""Set Least Significant Bit of a colour component.
|
"""Set Least Significant Bit of a colour component.
|
||||||
"""
|
"""
|
||||||
return component & ~1 | int(bit)
|
return component & ~1 | int(bit)
|
||||||
|
|
||||||
def n_at_a_time(items: 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.
|
"""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
|
||||||
|
|
||||||
|
@ -85,6 +92,7 @@ def n_at_a_time(items: List[int], n: int, fillvalue: str) \
|
||||||
it = iter(items)
|
it = iter(items)
|
||||||
return itertools.zip_longest(*[it] * n, fillvalue=fillvalue)
|
return itertools.zip_longest(*[it] * n, fillvalue=fillvalue)
|
||||||
|
|
||||||
|
|
||||||
def binary2base64(binary_file: str) -> str:
|
def binary2base64(binary_file: str) -> str:
|
||||||
"""Convert a binary file (OGG, executable, etc.) to a
|
"""Convert a binary file (OGG, executable, etc.) to a
|
||||||
printable string.
|
printable string.
|
||||||
|
@ -94,8 +102,21 @@ def binary2base64(binary_file: str) -> str:
|
||||||
encoded_string = base64.b64encode(bin_file.read())
|
encoded_string = base64.b64encode(bin_file.read())
|
||||||
return encoded_string.decode()
|
return encoded_string.decode()
|
||||||
|
|
||||||
|
|
||||||
def base642binary(b64_fname: str) -> bytes:
|
def base642binary(b64_fname: str) -> bytes:
|
||||||
"""Convert a printable string to a binary file.
|
"""Convert a printable string to a binary file.
|
||||||
"""
|
"""
|
||||||
b64_fname += '==='
|
b64_fname += "==="
|
||||||
return base64.b64decode(b64_fname)
|
return base64.b64decode(b64_fname)
|
||||||
|
|
||||||
|
|
||||||
|
def open_image(fname_or_instance: Union[str, IO[bytes]]):
|
||||||
|
"""Opens a Image and returns it.
|
||||||
|
|
||||||
|
:param fname_or_instance: Can either be the location of the image as a
|
||||||
|
string or the Image.Image instance itself.
|
||||||
|
"""
|
||||||
|
if isinstance(fname_or_instance, Image.Image):
|
||||||
|
return fname_or_instance
|
||||||
|
|
||||||
|
return Image.open(fname_or_instance)
|
||||||
|
|
Loading…
Add table
Reference in a new issue