# Subliminal Channels in Visual Cryptography

^{*}

## Abstract

**:**

## 1. Introduction

## 2. Literature Review

## 3. Motivations and Contributions

## 4. Materials and Methods

#### 4.1. Background

#### 4.2. Model

#### 4.3. Fold Method

Algorithm 1:Share generation with fold subliminal channel |

#### 4.4. Encryption Method

Algorithm 2:Share generation with encryption subliminal channel |

#### 4.5. Overlapping Method

Algorithm 3:Share generation with overlapping subliminal channel (version for a single share) |

Algorithm 4:Share generation with overlapping subliminal channel (version for multiple shares) |

## 5. Results

#### 5.1. Fold Method

#### 5.2. Encryption Method

#### 5.3. Overlapping Method

## 6. Discussion

## 7. Conclusions

## Author Contributions

## Funding

## Institutional Review Board Statement

## Informed Consent Statement

## Data Availability Statement

## Acknowledgments

## Conflicts of Interest

## Abbreviations

RGB | red, green, blue |

CMY | cyan, magenta, yellow |

## Appendix A. Exemplary Implementations

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

## References

- Simmons, G.J. The subliminal channel and digital signature. In Proceedings of the Eurocrypt 84 Workshop on Advances in Cryptology: Theory and Application of Cryptographic Techniques, Lecture Notes in Computer Science, Paris, France, 9–11 April 1984; Volume 209, pp. 364–378. [Google Scholar]
- Naor, M.; Shamir, A. Visual Cryptography. Advances in Cryptology–EUROCRYPT ’94, Proceedings; Springer: Perugia, Italy, 1994; Volume 950, pp. 1–12. [Google Scholar]
- Shamir, A. How to Share a Secret. Commun. ACM
**1979**, 22, 612–613. [Google Scholar] [CrossRef] - Yourmaran, R.; Adler, A.; Miri, A. An Improved Visual Cryptography Scheme For Secret Hiding. In Proceedings of the 23rd Biennial Symposium on Communications, Kingston, ON, Canada, 30 May–1 June 2006; pp. 340–343. [Google Scholar]
- Rao, Y.S.; Sukonkina, Y.; Bhagwati, C.; Singh, U.K. Fingerprint based authentication application using visual cryptography methods. In Proceedings of the TENCON 2008—2008 IEEE Region 10 Conference, Hyderabad, India, 19–21 November 2008; pp. 1–5. [Google Scholar]
- Ateniese, G.; Blundo, C.; De Santis, A.; Stinson, D.R. Extended capabilities for visual cryptography. Theor. Comput. Sci.
**2001**, 250, 143–161. [Google Scholar] [CrossRef] - Hou, Y.C. Visual cryptography for color images. Pattern Recognit.
**2003**, 36, 1619–1629. [Google Scholar] [CrossRef] - De Prisco, R.; De Santis, A. Color visual cryptography schemes for black and white secret images. Theor. Comput. Sci.
**2013**, 510, 62–86. [Google Scholar] [CrossRef] - Verheul, E.; van Tilborg, H. Constructions and Properties of k out of n visual secret sharing schemes. Des. Codes Cryptogr.
**1997**, 11, 179–196. [Google Scholar] [CrossRef] - Weir, J.P. Visual Cryptography and Its Applications; BookBoon: London, UK, 2012. [Google Scholar]
- Chen, T.; Wu, C.S.; Lee, W.B. A Novel Subliminal Channel Found in Visual Cryptography and Its Application to Image Hiding. In Proceedings of the Third International Conference on Intelligent Information Hiding and Multimedia Signal Processing (IIH-MSP 2007), Kaohsiung, Taiwan, 26–28 November 2007. [Google Scholar]
- Fang, W.P.; Lin, J.C. Visual cryptography with extra ability of hiding confidential data. J. Electron. Imaging
**2006**, 15, 023020. [Google Scholar] [CrossRef] - Chang, C.C.; Chuang, J.C.; Lin, P.Y. Sharing a secret two-tone image in two gray-level images. In Proceedings of the 11th International Conference on Parallel and Distributed Systems, Fukuoka, Japan, 20–22 July 2005; Volume 2, pp. 300–304. [Google Scholar]
- Crocker, L.D.; Boulay, P.; Morra, M. Digital Halftoning. Computer Lab and Reference Library, June 1991. Available online: https://gist.githubusercontent.com/tcoppex/d6d2f3dad05a806a6068b860750c8895/raw/437843dba782af9dcce89838c0b48b38644fbb5f/DHALF.TXT (accessed on 30 March 2022).
- Koptyra, K.; Ogiela, M.R. Key generation for multi-secret steganography. In Proceedings of the 2nd International Conference on Information Science and Security (ICISS), Seoul, Korea, 14–16 December 2015; pp. 1–4. [Google Scholar]
- Koptyra, K.; Ogiela, M.R. Steganography in IoT: Information Hiding with APDS-9960 Proximity and Gestures Sensor. Sensors
**2022**, 22, 2612. [Google Scholar] [CrossRef] [PubMed] - Castiglione, A.; Santis, A.D.; Pizzolante, R.; Castiglione, A.; Loia, V.; Palmieri, F. On the Protection of fMRI Images in Multi-domain Environments. In Proceedings of the 29th International Conference on Advanced Information Networking and Applications, Gwangju, Korea, 24–27 March 2015; pp. 476–481. [Google Scholar] [CrossRef]
- Pizzolante, R.; Carpentieri, B.; Castiglione, A.; Castiglione, A.; Palmieri, F. Text Compression and Encryption through Smart Devices for Mobile Communication. In Proceedings of the Seventh International Conference on Innovative Mobile and Internet Services in Ubiquitous Computing, Taichung, Taiwan, 3–5 July 2013; pp. 672–677. [Google Scholar] [CrossRef]
- Albano, P.; Bruno, A.; Carpentieri, B.; Castiglione, A.; Castiglione, A.; Palmieri, F.; Pizzolante, R.; You, I. A Secure Distributed Video Surveillance System Based on Portable Devices. In Multidisciplinary Research and Practice for Information Systems; CD-ARES 2012, Lecture Notes in Computer Science; Quirchmayr, G., Basl, J., You, I., Xu, L., Weippl, E., Eds.; Springer: Berlin/Heidelberg, Germany, 2012; Volume 7465. [Google Scholar]

**Figure 1.**Differences between halftoning techniques: (

**a**) Original image. (

**b**) Thresholding method. (

**c**) Dithering method.

**Figure 4.**Images used in tests (dimensions of peppers: $256\times 256$, dimensions of smile: $128\times 128$).

**Figure 7.**Fold method in color visual cryptography: C, M, Y color shares, a mask, stacking result and revealed secret binary image.

**Figure 8.**Fold method in color visual cryptography: colorful shares, stacking result, and revealed secret color image.

**Figure 9.**Regular shares, stacking result, and binary image revealed and decrypted with encryption method.

**Figure 15.**Unencrypted image hidden in a share: (

**a**) Secret image revealed with a template.

**b**) Outline of the image visible in the share.

**Table 1.**Result of stacking subpixels in the (2,2) visual sharing scheme [13].

White | Black | |
---|---|---|

Share 1 | ||

Share 2 | ||

Stacking result |

**Table 2.**Stacking subpixels in visual color sharing scheme [7].

Mask | C, M, Y Components | Share1 (C) | Share2 (M) | Share3 (Y) | Result |
---|---|---|---|---|---|

(0, 0, 0) | |||||

(1, 0, 0) | |||||

(0, 1, 0) | |||||

(0, 0, 1) | |||||

(1, 1, 0) | |||||

(0, 1, 1) | |||||

(1, 0, 1) | |||||

(1, 1, 1) |

Message | Lorem Ipsum Dolor Sit Amet (…) | ||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|

Encrypted message | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 1 | 1 | 1 | 0 | 0 | 0 | 1 | 1 | 0 | 1 | … |

Subpixels | … |

Publisher’s Note: MDPI stays neutral with regard to jurisdictional claims in published maps and institutional affiliations. |

© 2022 by the authors. Licensee MDPI, Basel, Switzerland. This article is an open access article distributed under the terms and conditions of the Creative Commons Attribution (CC BY) license (https://creativecommons.org/licenses/by/4.0/).

## Share and Cite

**MDPI and ACS Style**

Koptyra, K.; Ogiela, M.R.
Subliminal Channels in Visual Cryptography. *Cryptography* **2022**, *6*, 46.
https://doi.org/10.3390/cryptography6030046

**AMA Style**

Koptyra K, Ogiela MR.
Subliminal Channels in Visual Cryptography. *Cryptography*. 2022; 6(3):46.
https://doi.org/10.3390/cryptography6030046

**Chicago/Turabian Style**

Koptyra, Katarzyna, and Marek R. Ogiela.
2022. "Subliminal Channels in Visual Cryptography" *Cryptography* 6, no. 3: 46.
https://doi.org/10.3390/cryptography6030046