# GPLv3 https://www.gnu.org/licenses/gpl-3.0.html |
|
import os |
import numpy as np |
subpixels = [ |
np.array([[0,0],[1,1]]), |
np.array([[0,1],[0,1]]), |
np.array([[0,1],[1,0]]), |
np.array([[1,1],[0,0]]), |
np.array([[1,0],[1,0]]), |
np.array([[1,0],[0,1]]) |
] |
|
# standard (2,2) visual secret sharing scheme for binary images |
def create_shares(img): |
# empty shares |
shares = [np.zeros((img.shape[0] * 2, img.shape[1] * 2), dtype=np.uint8 |
) for _ in range(2)] |
|
# create shares |
r = os.urandom(img.shape[0] * img.shape[1]) |
for i in range(img.shape[0]): |
for j in range(img.shape[1]): |
shares[0][2*i:2*i+2,2*j:2*j+2] = subpixels[r[i*img.shape[1]+j] % 6] |
if img[i,j] == 255: |
shares[1][2*i:2*i+2,2*j:2*j+2] = shares[0][2*i:2*i+2,2*j:2*j+2] |
else: |
shares[1][2*i:2*i+2,2*j:2*j+2] = (shares[0][2*i:2*i+2,2*j:2*j+2] |
+ 1) % 2 |
|
return shares |
|
# fold subliminal channel |
def fold(regular_img, secret_img, fdir, fold): |
# check dimensions |
if 2 * secret_img.shape[0] > regular_img.shape[0] or 2 * secret_img. |
shape[1] > regular_img.shape[1]: |
raise Exception(f’The shape of the secret image {secret_img.shape} |
cannot exceed half of the regular image {regular_img.shape}!’) |
# check fold line placing |
if not fdir and (fold < 2 * secret_img.shape[0] or fold > 2 * |
regular_img.shape[0] // 2): |
raise Exception(f’The secret image could not be recovered with shapes |
{regular_img.shape} (regular), {secret_img.shape} (secret) and |
horizontal fold line on {fold}!’) |
elif fdir and (fold < 2 * secret_img.shape[1] or fold > 2 * regular_img |
.shape[1] // 2): |
raise Exception(f’The secret image could not be recovered with shapes |
{regular_img.shape} (regular), {secret_img.shape} (secret) and |
vertical fold line on {fold}!’) |
|
# empty shares for the regular image |
shares = [np.zeros((regular_img.shape[0] * 2, regular_img.shape[1] * 2) |
, dtype=np.uint8) for _ in range(2)] |
# create shares for the secret image |
secret_shares = create_shares(secret_img) |
|
# paste secret shares |
shares[0][:2*secret_img.shape[0],:2*secret_img.shape[1]] = |
secret_shares[0] |
if not fdir: |
shares[0][2*fold-2*secret_img.shape[0]:2*fold,:2*secret_img.shape[1]] |
= np.flipud(secret_shares[1]) |
else: |
shares[0][:2*secret_img.shape[0],2*fold-2*secret_img.shape[1]:2*fold] |
= np.fliplr(secret_shares[1]) |
|
# create regular shares |
r = os.urandom(regular_img.shape[0] * regular_img.shape[1]) |
for i in range(regular_img.shape[0]): |
for j in range(regular_img.shape[1]): |
# fit subpixels of share2 for existing parts of share1 |
if np.any(shares[0][2*i:2*i+2,2*j:2*j+2]): |
if regular_img[i,j] == 255: |
shares[1][2*i:2*i+2,2*j:2*j+2] = shares[0][2*i:2*i+2,2*j:2*j+2] |
else: |
shares[1][2*i:2*i+2,2*j:2*j+2] = (shares[0][2*i:2*i+2,2*j:2*j |
+2] + 1) % 2 |
# for unused parts create shares normally |
else: |
shares[0][2*i:2*i+2,2*j:2*j+2] = subpixels[r[i*regular_img.shape |
[1]+j] % 6 |
if regular_img[i,j] == 255: |
shares[1][2*i:2*i+2,2*j:2*j+2] = shares[0][2*i:2*i+2,2*j:2*j+2] |
else: |
shares[1][2*i:2*i+2,2*j:2*j+2] = (shares[0][2*i:2*i+2,2*j:2*j |
+2] + 1) % 2 |
|
return shares |
|
# encryption subliminal channel with a text (negative) |
def encryption_text(regular_img, message, iv, key): |
if len(message) * 8 > regular_img.shape[0] * regular_img.shape[1]: |
raise Exception(f’The message is too long ({len(message) * 8} bits), |
the capacity is {regular_img.shape[0] * regular_img.shape[1]} |
bits!’) |
|
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, |
modes |
message += b"x00" |
if len(message) % 16 != 0: |
message += bytes(16-(len(message) % 16)) |
# encrypt secret message |
cipher = Cipher(algorithms.AES(key), modes.CBC(iv)) |
encryptor = cipher.encryptor() |
ct = encryptor.update(message) + encryptor.finalize() |
|
# create shares |
shares = [np.zeros((regular_img.shape[0] * 2, regular_img.shape[1] * 2) |
, dtype=np.uint8) for _ in range(2)] |
r = os.urandom(regular_img.shape[0] * regular_img.shape[1]) |
l = 0 |
for i in range(regular_img.shape[0]): |
for j in range(regular_img.shape[1]): |
if l < len(ct)*8: |
bit = (ct[l//8] >> (7–(l%8))) & 1 |
shares[0][2*i:2*i+2,2*j:2*j+2] = subpixels[(r[l] % 3) + 3*bit] |
if regular_img[i][j] == 255: |
shares[1][2*i:2*i+2,2*j:2*j+2] = shares[0][2*i:2*i+2,2*j:2*j+2] |
else: |
shares[1][2*i:2*i+2,2*j:2*j+2] = (shares[0][2*i:2*i+2,2*j:2*j |
+2] + 1) % 2 |
l += 1 |
else: |
shares[0][2*i:2*i+2,2*j:2*j+2] = subpixels[r[i*regular_img.shape |
[1]+j] % 6] |
if regular_img[i,j] == 255: |
shares[1][2*i:2*i+2,2*j:2*j+2] = shares[0][2*i:2*i+2,2*j:2*j+2] |
else: |
shares[1][2*i:2*i+2,2*j:2*j+2] = (shares[0][2*i:2*i+2,2*j:2*j |
+2] + 1) % 2 |
|
return shares |
|
# encryption subliminal channel with a binary image (negative) |
def encryption(regular_img, secret_img, iv, key): |
if secret_img.shape[0] != regular_img.shape[0] or secret_img.shape[1] |
!= regular_img.shape[1]: |
raise Exception(f’The shapes of secret and regular images should be |
equal, are: {secret_img.shape} and {regular_img.shape}!’) |
|
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, |
modes |
from functools import reduce |
# create secret message |
secret_img = secret_img.flatten() // 255 |
secret = bytes([reduce(lambda x,y: (x << 1) + y, secret_img[8*i:8*i+8]) |
for i in range(len(secret_img)//8)]) |
if len(secret) % 16 != 0: |
secret += bytes(16-(len(secret) % 16)) |
# encrypt secret message |
cipher = Cipher(algorithms.AES(key), modes.CBC(iv)) |
encryptor = cipher.encryptor() |
ct = encryptor.update(secret) + encryptor.finalize() |
|
# create shares |
shares = [np.zeros((regular_img.shape[0] * 2, regular_img.shape[1] * 2) |
, dtype=np.uint8) for _ in range(2)] |
r = os.urandom(regular_img.shape[0] * regular_img.shape[1]) |
l = 0 |
for i in range(regular_img.shape[0]): |
for j in range(regular_img.shape[1]): |
bit = (ct[l//8] >> (7-(l%8))) & 1 |
shares[0][2*i:2*i+2,2*j:2*j+2] = subpixels[(r[l] % 3) + 3*bit] |
if regular_img[i][j] == 255: |
shares[1][2*i:2*i+2,2*j:2*j+2] = shares[0][2*i:2*i+2,2*j:2*j+2] |
else: |
shares[1][2*i:2*i+2,2*j:2*j+2] = (shares[0][2*i:2*i+2,2*j:2*j+2] |
+ 1) % 2 |
l += 1 |
|
return shares |
|
# overlapping subliminal channel without offset (single image) |
def overlapping1(regular_img, secret_img): |
# check dimensions |
if 2 * secret_img.shape[0] > regular_img.shape[0] or 2 * secret_img. |
shape[1] > regular_img.shape[1]: |
raise Exception(f’The shape of the secret image {secret_img.shape} |
cannot exceed half of the regular image {regular_img.shape}!’) |
|
# empty shares for the regular image |
shares = [np.zeros((regular_img.shape[0] * 2, regular_img.shape[1] * 2) |
, dtype=np.uint8) for _ in range(2)] |
# create shares for the secret image |
secret_shares = create_shares(secret_img) |
|
# paste secret shares in corners |
shares[0][:secret_shares[0].shape[0],:secret_shares[0].shape[1]] = |
secret_shares[0] |
shares[0][-secret_shares[1].shape[0]:,-secret_shares[1].shape[1]:] = |
secret_shares[1] |
|
# create regular shares |
r = os.urandom(regular_img.shape[0] * regular_img.shape[1]) |
for i in range(regular_img.shape[0]): |
for j in range(regular_img.shape[1]): |
# fit subpixels of share2 for existing parts of share1 |
if np.any(shares[0][2*i:2*i+2,2*j:2*j+2]): |
if regular_img[i,j] == 255: |
shares[1][2*i:2*i+2,2*j:2*j+2] = shares[0][2*i:2*i+2,2*j:2*j+2] |
else: |
shares[1][2*i:2*i+2,2*j:2*j+2] = (shares[0][2*i:2*i+2,2*j:2*j |
+2] + 1) % 2 |
# for unused parts create shares normally |
else: |
shares[0][2*i:2*i+2,2*j:2*j+2] = subpixels[r[i*regular_img.shape |
[1]+j] % 6] |
if regular_img[i,j] == 255: |
shares[1][2*i:2*i+2,2*j:2*j+2] = shares[0][2*i:2*i+2,2*j:2*j+2] |
else: |
shares[1][2*i:2*i+2,2*j:2*j+2] = (shares[0][2*i:2*i+2,2*j:2*j |
+2] + 1) % 2 |
|
return shares |
|
# overlapping subliminal channel without offset (two images) |
def overlapping2(regular_img, secret_img): |
# check dimensions |
if 2 * secret_img.shape[0] > regular_img.shape[0] or 2 * secret_img. |
shape[1] > regular_img.shape[1]: |
raise Exception(f’The shape of the secret image {secret_img.shape} |
cannot exceed half of the regular image {regular_img.shape}!’) |
|
# empty shares for the regular image |
shares = [np.zeros((regular_img.shape[0] * 2, regular_img.shape[1] * 2) |
, dtype=np.uint8) for _ in range(2)] |
# create shares for the secret image |
secret_shares = create_shares(secret_img) |
|
# paste secret shares in corners |
shares[0][:secret_shares[0].shape[0],:secret_shares[0].shape[1]] = |
secret_shares[0] |
shares[1][-secret_shares[1].shape[0]:,-secret_shares[1].shape[1]:] = |
secret_shares[1] |
|
# create regular shares |
r = os.urandom(regular_img.shape[0] * regular_img.shape[1]) |
for i in range(regular_img.shape[0]): |
for j in range(regular_img.shape[1]): |
# fit subpixels of share2 for existing parts of share1 |
if np.any(shares[0][2*i:2*i+2,2*j:2*j+2]): |
if regular_img[i,j] == 255: |
shares[1][2*i:2*i+2,2*j:2*j+2] = shares[0][2*i:2*i+2,2*j:2*j+2] |
else: |
shares[1][2*i:2*i+2,2*j:2*j+2] = (shares[0][2*i:2*i+2,2*j:2*j |
+2] + 1) % 2 |
# fit subpixels of share1 for existing parts of share2 |
elif np.any(shares[1][2*i:2*i+2,2*j:2*j+2]): |
if regular_img[i,j] == 255: |
shares[0][2*i:2*i+2,2*j:2*j+2] = shares[1][2*i:2*i+2,2*j:2*j+2] |
else: |
shares[0][2*i:2*i+2,2*j:2*j+2] = (shares[1][2*i:2*i+2,2*j:2*j |
+2] + 1) % 2 |
# for unused parts create shares normally |
else: |
shares[0][2*i:2*i+2,2*j:2*j+2] = subpixels[r[i*regular_img.shape |
[1]+j] % 6] |
if regular_img[i,j] == 255: |
shares[1][2*i:2*i+2,2*j:2*j+2] = shares[0][2*i:2*i+2,2*j:2*j+2] |
else: |
shares[1][2*i:2*i+2,2*j:2*j+2] = (shares[0][2*i:2*i+2,2*j:2*j |
+2] + 1) % 2 |
|
return shares |
|