| # 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 |
| |