-
Notifications
You must be signed in to change notification settings - Fork 0
/
stegopng.py
134 lines (119 loc) · 5.24 KB
/
stegopng.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
import os
import functools
import random
from PIL import Image
# Generator to yield all coordinate pairs in a loaded Image
def getpixel(img):
for y in range(img.height):
for x in range(img.width):
yield (x,y)
def steg(image, data_file, output_file, BITS_TO_SHIFT=2, BANDS_TO_USE=3):
# Minimum of 30 bits gives you minimum of 1-gig of potential data.
DATA_SIZE = 30
while ((DATA_SIZE % (BITS_TO_SHIFT*BANDS_TO_USE)) != 0):
DATA_SIZE += 1
# Open the image and data files
img = Image.open(image)
with open(data_file, "rb") as f:
data = bytearray(f.read())
data_len = len(data)
# Do some basic error checking, like make sure the data can fit in the image
max_size = (img.height*img.width*BANDS_TO_USE*BITS_TO_SHIFT)-DATA_SIZE
if BITS_TO_SHIFT > 8:
print("[!] BITS_TO_SHIFT is > 8. Setting to 8.")
BITS_TO_SHIFT = 8
if BANDS_TO_USE > 3:
print("[!] BANDS_TO_USE is set higher than 3, this might cause incomplete data")
if data_len >= 2**DATA_SIZE:
print("[*] Data file exceeds max size capable of being stego'd")
return 1
if data_len*8 > max_size:
print("[*] Data file exceeds destination files capability: {} bytes".format(max_size/8))
return 1
# Turn the length of binary data into bits
bin_datalen = str("{0:0" + str(DATA_SIZE) + "b}").format(data_len)[::-1]
bin_datalen = (int(bit) for bit in bin_datalen)
pixels = getpixel(img)
# Checkpoint is used to NOT randomize the length, which is the "key" for decryption
checkpoint = DATA_SIZE / BITS_TO_SHIFT / BANDS_TO_USE
# Setup random seed. This is used to encrypt the binary data
random.seed(data_len)
# Create a bit-generator of all the bits that will be stored in the image
input_bitarray = (int(n) for n in ''.join(map("{0:08b}".format, data)))
# Creat a bitmap and encrypt the data
encrypt_bitarray = (int(random.choice((0,1))==input_bit) for input_bit in input_bitarray)
for i in range(1 + int(((data_len*8) + DATA_SIZE) / BITS_TO_SHIFT / BANDS_TO_USE)):
coord = next(pixels)
pixel = list(img.getpixel(coord))
if i == checkpoint:
checkpoint = 0
for i,byte in enumerate(pixel[:BANDS_TO_USE]):
try:
for s in range(BITS_TO_SHIFT):
if checkpoint:
bit = next(bin_datalen)
else:
bit = next(encrypt_bitarray)
if ((byte >> s) & 1) != bit:
pixel[i] ^= (1 << s)
except StopIteration:
break
img.putpixel(coord,tuple(pixel))
print("Saving file...")
while os.path.isfile(output_file):
check = input if os.sys.version_info.major == 3 else raw_input
c = check("{} exists!\nOverwrite? [y/n] ".format(output_file))
if c.lower() == "y":
break
else:
output_file = check("Enter new file path:\n> ")
img.save(output_file)
print("File saved at: {}".format(output_file))
def desteg(image, savefile, BITS_TO_SHIFT=2, BANDS_TO_USE=3):
while os.path.isfile(savefile):
check = input if os.sys.version_info.major == 3 else raw_input
c = check("{} exists!\nOverwrite? [y/n] ".format(savefile))
if c.lower() == "y":
break
else:
savefile = check("Enter new file path:\n> ")
DATA_SIZE = 30
while ((DATA_SIZE % (BITS_TO_SHIFT*BANDS_TO_USE)) != 0):
DATA_SIZE += 1
# Open image
img = Image.open(image)
pixels = getpixel(img)
output = []
# Get length of stego'd data
for _ in range(int(DATA_SIZE / BITS_TO_SHIFT / BANDS_TO_USE)):
coord = next(pixels)
pixel = list(img.getpixel(coord))
for byte in pixel[:BANDS_TO_USE]:
for s in range(BITS_TO_SHIFT):
output.append((byte >> s) & 1)
# Translate backwards binary list into a number
length = (functools.reduce(lambda x,y: x << 1 | y, output[::-1]))*8
print("Length: ", length)
output = []
# Set the random seed for decrypting the data, and create the bitmask
random.seed(length/8)
encrypt_bitmask = (random.choice((0,1)) for _ in range(length))
# Loop through the file and pull the stego'd bits out
# This also reverses the encryption: bitmask == pixel_bit is True (1) or False (0)
# This 1 or 0 is the bit from the stego'd files value
for _ in range(1 + int(length / BITS_TO_SHIFT / BANDS_TO_USE)):
coord = next(pixels)
pixel = list(img.getpixel(coord))
for byte in pixel[:BANDS_TO_USE]:
try:
for s in range(BITS_TO_SHIFT):
bit = next(encrypt_bitmask)
output.append(int(((byte >> s) & 1) == bit))
except StopIteration:
break
# Turn the binary output into bytes in a bytearray
data = bytearray()
for i in range(int(len(output)/8)):
data.append(functools.reduce(lambda x,y: x << 1 | y, output[i*8:i*8+8]))
with open(savefile, "wb") as f:
f.write(data)