|
|
|
|
|
import sys |
|
import os.path |
|
import math |
|
import random |
|
import struct |
|
import hashlib |
|
from PIL import Image |
|
from ctypes import * |
|
from Crypto.Cipher import AES |
|
from Crypto.Random import get_random_bytes |
|
from Crypto.Util.Padding import pad, unpad |
|
|
|
def prepare_message(text, password): |
|
|
|
content_data = text.encode('utf-8') |
|
|
|
|
|
content_ver=struct.pack("B", 1) |
|
content_len=struct.pack("!I", len(content_data)) |
|
content=content_ver+content_len+content_data |
|
|
|
|
|
enc = encrypt(content, password) |
|
|
|
array=[] |
|
for b in enc: |
|
for i in range(8): |
|
array.append((b >> i) & 1) |
|
return array |
|
|
|
|
|
|
|
def encrypt(plain_text, password): |
|
|
|
salt = get_random_bytes(AES.block_size) |
|
|
|
|
|
private_key = hashlib.scrypt( |
|
password.encode(), salt=salt, n=2**14, r=8, p=1, dklen=32) |
|
|
|
cipher = AES.new(private_key, AES.MODE_CBC) |
|
cipher_text = cipher.encrypt(pad(plain_text, AES.block_size)) |
|
enc = salt+cipher.iv+cipher_text |
|
|
|
return enc |
|
|
|
|
|
|
|
def decrypt(cipher_text, password): |
|
|
|
salt = cipher_text[:AES.block_size] |
|
iv = cipher_text[AES.block_size:AES.block_size*2] |
|
cipher_text = cipher_text[AES.block_size*2:] |
|
|
|
|
|
mxlen = len(cipher_text)-(len(cipher_text)%AES.block_size) |
|
cipher_text = cipher_text[:mxlen] |
|
|
|
private_key = hashlib.scrypt( |
|
password.encode(), salt=salt, n=2**14, r=8, p=1, dklen=32) |
|
|
|
cipher = AES.new(private_key, AES.MODE_CBC, iv=iv) |
|
decrypted = cipher.decrypt(cipher_text) |
|
|
|
|
|
return decrypted |
|
|
|
|
|
|
|
|
|
|
|
def embed(input_img_path, cost_matrix, msg_file_path, password, output_img_path, payload=0.10): |
|
|
|
me = os.path.abspath(os.path.dirname(__file__)) |
|
lib = cdll.LoadLibrary(os.path.join(me, "lib", "stc.so")) |
|
|
|
|
|
im=Image.open(input_img_path) |
|
if im.mode in ['L']: |
|
width, height = im.size |
|
if im.mode in ['RGB', 'RGBA', 'RGBX']: |
|
pass |
|
I = im.load() |
|
cover = (c_int*(width*height))() |
|
idx=0 |
|
for j in range(height): |
|
for i in range(width): |
|
cover[idx] = I[i, j] |
|
idx += 1 |
|
|
|
|
|
|
|
INF = 2**31-1 |
|
costs = (c_float*(width*height*3))() |
|
idx=0 |
|
for j in range(height): |
|
for i in range(width): |
|
if cover[idx]==0: |
|
costs[3*idx+0] = INF |
|
costs[3*idx+1] = 0 |
|
costs[3*idx+2] = cost_matrix[j, i] |
|
elif cover[idx]==255: |
|
costs[3*idx+0] = cost_matrix[j, i] |
|
costs[3*idx+1] = 0 |
|
costs[3*idx+2] = INF |
|
else: |
|
costs[3*idx+0] = cost_matrix[j, i] |
|
costs[3*idx+1] = 0 |
|
costs[3*idx+2] = cost_matrix[j, i] |
|
idx += 1 |
|
|
|
|
|
msg_bits = prepare_message(msg_file_path, password) |
|
if len(msg_bits)>width*height*payload: |
|
print("Message too long") |
|
sys.exit(0) |
|
m = int(width*height*payload) |
|
message = (c_ubyte*m)() |
|
for i in range(m): |
|
if i<len(msg_bits): |
|
message[i] = msg_bits[i] |
|
else: |
|
|
|
message[i] = 0 |
|
|
|
stego = (c_int*(width*height))() |
|
a = lib.stc_hide(width*height, cover, costs, m, message, stego) |
|
|
|
|
|
idx=0 |
|
for j in range(height): |
|
for i in range(width): |
|
im.putpixel((i, j), stego[idx]) |
|
idx += 1 |
|
im.save(output_img_path) |
|
im.close() |
|
|
|
|
|
|
|
def extract(stego_img_path, password, output_msg_path, payload=0.10): |
|
|
|
me = os.path.abspath(os.path.dirname(__file__)) |
|
lib = cdll.LoadLibrary(os.path.join(me, "lib", "stc.so")) |
|
|
|
|
|
im=Image.open(stego_img_path) |
|
if im.mode in ['L']: |
|
width, height = im.size |
|
if im.mode in ['RGB', 'RGBA', 'RGBX']: |
|
pass |
|
I = im.load() |
|
stego = (c_int*(width*height))() |
|
idx=0 |
|
for j in range(height): |
|
for i in range(width): |
|
stego[idx] = I[i, j] |
|
idx += 1 |
|
|
|
|
|
n = width*height; |
|
m = int(n*payload) |
|
extracted_message = (c_ubyte*m)() |
|
s = lib.stc_unhide(n, stego, m, extracted_message) |
|
|
|
|
|
enc = bytearray() |
|
idx=0 |
|
bitidx=0 |
|
bitval=0 |
|
for b in extracted_message: |
|
if bitidx==8: |
|
enc.append(bitval) |
|
bitidx=0 |
|
bitval=0 |
|
bitval |= b<<bitidx |
|
bitidx+=1 |
|
if bitidx==8: |
|
enc.append(bitval) |
|
|
|
|
|
cleartext = decrypt(enc, password) |
|
|
|
|
|
content_ver=struct.unpack_from("B", cleartext, 0) |
|
content_len=struct.unpack_from("!I", cleartext, 1) |
|
content=cleartext[5:content_len[0]+5] |
|
|
|
f = open(output_msg_path, 'w') |
|
f.write(content.decode()) |
|
f.close() |
|
|