├── message.png ├── secret.png ├── overview.png ├── .gitignore ├── README.md └── visual_crypto.py /message.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ageron/visual_crypto/HEAD/message.png -------------------------------------------------------------------------------- /secret.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ageron/visual_crypto/HEAD/secret.png -------------------------------------------------------------------------------- /overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ageron/visual_crypto/HEAD/overview.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[co] 2 | 3 | # Packages 4 | *.egg 5 | *.egg-info 6 | dist 7 | build 8 | eggs 9 | parts 10 | bin 11 | var 12 | sdist 13 | develop-eggs 14 | .installed.cfg 15 | 16 | # Installer logs 17 | pip-log.txt 18 | 19 | # Unit test / coverage reports 20 | .coverage 21 | .tox 22 | 23 | #Translations 24 | *.mo 25 | 26 | #Mr Developer 27 | .mr.developer.cfg 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Visual cryptography tool 2 | 3 | A fun visual cryptography tool that will amaze your kids. 4 | 5 | ![overview](overview.png) 6 | 7 | ## Usage 8 | 9 | $ python visual_crypto.py -vvv --message message.png --secret secret.png 10 | 11 | This produces the ciphered image (`ciphered.png`) that looks completely random, just like the random secret image (`secret.png`). 12 | 13 | Print `secret.png` on a transparent paper, and `ciphered.png` on regular paper, then place the transparent paper on top of the regular paper, and see the original image suddenly appear in front of you. Make sure to print at exactly the same scale and align both papers perfectly: the image only appears when alignment is perfect. 14 | 15 | ## Requirements 16 | This program requires the Python Image Library (PIL). The easiest way to install it is using pip: 17 | 18 | $ pip install --upgrade Pillow 19 | 20 | ## More info 21 | This is a visual variant of the one-time-pad algorithm which is one of the rare crypto algorithms that has been proven to be completely unbreakable. The great thing about it is that you don't need a computer to decipher the messages you receive (but you do need one to generate the ciphered messages). If you were to use this seriously, you would first generate many random secret images and share them securely with the person you want to communicate with, then later you must use one different secret image for each message you want to send. Never reuse a secret image. 22 | 23 | Check out the Wikipedia article on [Visual Cryptography](https://en.wikipedia.org/wiki/Visual_cryptography) for more details. 24 | 25 | Have fun! -------------------------------------------------------------------------------- /visual_crypto.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | from __future__ import division, print_function, unicode_literals 4 | 5 | import os 6 | import sys 7 | import random 8 | from PIL import Image 9 | import argparse 10 | import logging 11 | 12 | __version__ = "0.1" 13 | 14 | def get_options(): 15 | parser = argparse.ArgumentParser(description='Visual cipher image generator.') 16 | parser.add_argument('--message', '-m', required = True, metavar = "MESSAGE_IMAGE_FILE_PATH", help='message image') 17 | parser.add_argument('--secret', '-s', metavar = "SECRET_IMAGE_FILE_PATH", default = "secret.png", help='secret image (will be created if it does not exist)') 18 | parser.add_argument('--ciphered', '-c', metavar = "CIPHERED_IMAGE_FILE_PATH", default = "ciphered.png", help='ciphered image (to be generated)') 19 | parser.add_argument('--resize', '-r', metavar = "WIDTH,HEIGHT", help='resize message image (defaults to message image size). If width or height is larger than that of the secret image, the secret image will be enlarged appropriately.') 20 | parser.add_argument('--prepared_message', '-p', metavar = "PREPARED_MESSAGE_IMAGE_FILE_PATH", help='if present, the prepared message image is saved to this path') 21 | parser.add_argument('--display', '-d', action='store_true') 22 | parser.add_argument('--verbose', '-v', action='count', default=0) 23 | args = parser.parse_args() 24 | if args.resize: 25 | try: 26 | width, height = [int(i.strip()) for i in args.resize.strip().split(",")] 27 | args.resize = (width, height) 28 | except: 29 | parser.error("Invalid format for resize option.") 30 | else: 31 | if width <= 0: 32 | parser.error("Resize width should be > 0.") 33 | if height <= 0: 34 | parser.error("Resize height should be > 0.") 35 | 36 | return args 37 | 38 | def load_image(name): 39 | return Image.open(name) 40 | 41 | def prepare_message_image(image, size): 42 | if size != image.size: 43 | image = image.resize(size, Image.ANTIALIAS) 44 | return image.convert("1") 45 | 46 | def generate_secret(size, secret_image = None): 47 | width, height = size 48 | new_secret_image = Image.new(mode = "1", size = (width * 2, height * 2)) 49 | if secret_image: 50 | old_width, old_height = secret_image.size 51 | else: 52 | old_width, old_height = (-1, -1) 53 | 54 | for x in range(0, 2 * width, 2): 55 | for y in range(0, 2 * height, 2): 56 | if x < old_width and y < old_height: 57 | color = secret_image.getpixel((x, y)) 58 | else: 59 | color = random.getrandbits(1) 60 | new_secret_image.putpixel((x, y), color) 61 | new_secret_image.putpixel((x+1,y), 1-color) 62 | new_secret_image.putpixel((x, y+1), 1-color) 63 | new_secret_image.putpixel((x+1,y+1), color) 64 | return new_secret_image 65 | 66 | def generate_ciphered_image(secret_image, prepared_image): 67 | width, height = prepared_image.size 68 | ciphered_image = Image.new(mode = "1", size = (width * 2, height * 2)) 69 | for x in range(0, width*2, 2): 70 | for y in range(0, height*2, 2): 71 | secret = secret_image.getpixel((x,y)) 72 | message = prepared_image.getpixel((x/2,y/2)) 73 | if (message > 0 and secret > 0) or (message == 0 and secret == 0): 74 | color = 0 75 | else: 76 | color = 1 77 | ciphered_image.putpixel((x, y), 1-color) 78 | ciphered_image.putpixel((x+1,y), color) 79 | ciphered_image.putpixel((x, y+1), color) 80 | ciphered_image.putpixel((x+1,y+1), 1-color) 81 | return ciphered_image 82 | 83 | def main(): 84 | logging.basicConfig() 85 | 86 | args = get_options() 87 | if args.verbose > 2: 88 | logging.getLogger().setLevel(logging.DEBUG) 89 | elif args.verbose > 1: 90 | logging.getLogger().setLevel(logging.INFO) 91 | elif args.verbose > 0: 92 | logging.getLogger().setLevel(logging.WARNING) 93 | else: 94 | logging.getLogger().setLevel(logging.ERROR) 95 | 96 | logging.info("Cipher image generator version %s" % __version__) 97 | 98 | try: 99 | logging.debug("Loading message image '%s'" % (args.message)) 100 | message_image = load_image(args.message) 101 | except IOError as e: 102 | logging.fatal("Fatal error: I/O error while loading message image '%s' (%s)" % (args.message, str(e))) 103 | sys.exit(1) 104 | 105 | if args.resize is None: 106 | size = message_image.size 107 | else: 108 | size = args.resize 109 | 110 | width, height = size 111 | 112 | save_secret = False 113 | 114 | if os.path.isfile(args.secret): 115 | try: 116 | logging.debug("Loading secret image '%s'" % (args.secret)) 117 | secret_image = load_image(args.secret) 118 | secret_width, secret_height = secret_image.size 119 | if secret_width < width or secret_height < height: 120 | logging.info("Enlarging secret image to fit message size") 121 | secret_image = generate_secret(size, secret_image = secret_image) 122 | save_secret = True 123 | except IOError: 124 | logging.fatal("I/O error while loading secret image '%s' (%s)" % (args.secret, str(e))) 125 | sys.exit(2) 126 | else: 127 | logging.info("Generating secret image '%s'" % (args.secret)) 128 | secret_image = generate_secret(size) 129 | save_secret = True 130 | 131 | prepared_image = prepare_message_image(message_image, size) 132 | ciphered_image = generate_ciphered_image(secret_image, prepared_image) 133 | 134 | if save_secret: 135 | logging.debug("Saving secret image '%s'" % (args.secret)) 136 | try: 137 | secret_image.save(args.secret) 138 | except IOError as e: 139 | logging.error("I/O error while saving secret image '%s' (%s)" % (args.secret, str(e))) 140 | 141 | if args.prepared_message: 142 | logging.debug("Saving prepared message image '%s'" % (args.prepared_message)) 143 | try: 144 | prepared_image.save(args.prepared_message) 145 | except IOError as e: 146 | logging.error("I/O error while saving prepared message image '%s' (%s)" % (args.prepared_message, str(e))) 147 | 148 | try: 149 | ciphered_image.save(args.ciphered) 150 | except IOError as e: 151 | logging.fatal("I/O error while saving ciphered image '%s' (%s)" % (args.ciphered, str(e))) 152 | sys.exit(3) 153 | 154 | if args.display: 155 | prepared_image.show() 156 | secret_image.show() 157 | ciphered_image.show() 158 | 159 | if __name__ == '__main__': 160 | main() 161 | --------------------------------------------------------------------------------