import PIL.Image import itertools import sys def get_bitstream(message): for c in message: bin_string = "{:08b}".format(c) for bit in bin_string: yield int(bit) def parse_bitstream(stream): for bits in by_chunk(stream, 8, fillvalue=0): binstr = "".join([str(b) for b in bits]) c = int(binstr, 2) if chr(c) == "\n": break yield c def by_chunk(iterable, size, fillvalue=0): "Collect data into fixed-length chunks or blocks" args = [iter(iterable)] * size return itertools.zip_longest(*args, fillvalue=fillvalue) def set_bit(old_byte, new_bit): b = list(bin(old_byte)) b[-1] = str(new_bit) return int("".join(b), 2) def main_encrypt(): base_name = sys.argv[2] message = (sys.argv[3] + "\n").encode() carrier_name = base_name.replace(".png", ".carrier.png") base_image = PIL.Image.open(base_name) width, height = base_image.size new_image = PIL.Image.new("RGB", (width, height), "white") bitstream = get_bitstream(message) for row in range(height): for col in range(width): r, g, b = base_image.getpixel((col, row)) value = None try: value = next(bitstream) except StopIteration: pass if value is not None: r = set_bit(r, value) new_image.putpixel((col, row), (r, g, b)) new_image.save(carrier_name, "png") print("carrier written to", carrier_name) def yield_bits(carrier_image): width, height = carrier_image.size for row in range(height): for col in range(width): r, g, b = carrier_image.getpixel((col, row)) last_bit = int(bin(r)[-1]) yield last_bit def main_decrypt(): carrier_name = sys.argv[2] carrier_image = PIL.Image.open(carrier_name) encoded = yield_bits(carrier_image) decoded = bytes(parse_bitstream(encoded)) print(decoded.decode()) def main(): if sys.argv[1] == "encrypt": main_encrypt() elif sys.argv[1] == "decrypt": main_decrypt() else: sys.exit("choose from encrypt, decrypt")