├── requirements.txt ├── example ├── origin.jpg ├── secret.png └── stego.png ├── crypt.py ├── README.md └── lsb.py /requirements.txt: -------------------------------------------------------------------------------- 1 | numpy 2 | matplotlib 3 | pycryptodome 4 | -------------------------------------------------------------------------------- /example/origin.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Grazee/cloacked-pixel-python3/HEAD/example/origin.jpg -------------------------------------------------------------------------------- /example/secret.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Grazee/cloacked-pixel-python3/HEAD/example/secret.png -------------------------------------------------------------------------------- /example/stego.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Grazee/cloacked-pixel-python3/HEAD/example/stego.png -------------------------------------------------------------------------------- /crypt.py: -------------------------------------------------------------------------------- 1 | import hashlib 2 | from Crypto import Random 3 | from Crypto.Cipher import AES 4 | from Crypto.Util.number import long_to_bytes 5 | 6 | ''' 7 | Thanks to 8 | http://stackoverflow.com/questions/12524994/encrypt-decrypt-using-pycrypto-aes-256 9 | ''' 10 | class AESCipher: 11 | def __init__(self, key): 12 | self.bs = 32 # Block size 13 | self.key = hashlib.sha256(key.encode()).digest() # 32 bit digest 14 | 15 | def encrypt(self, raw): 16 | raw = self.pad(raw) 17 | iv = Random.new().read(AES.block_size) 18 | cipher = AES.new(self.key, AES.MODE_CBC, iv) 19 | return iv + cipher.encrypt(raw) 20 | 21 | def decrypt(self, enc): 22 | # if len(enc) % 16 != 0: 23 | # enc = enc[:len(enc) - len(enc)%16] 24 | # print(len(enc)) 25 | iv = enc[:AES.block_size] 26 | cipher = AES.new(self.key, AES.MODE_CBC, iv) 27 | message = cipher.decrypt(enc[AES.block_size:]) 28 | 29 | return message 30 | # return self.unpad() 31 | 32 | def pad(self, s): 33 | return s + (self.bs - len(s) % self.bs) * long_to_bytes(self.bs - len(s) % self.bs) 34 | 35 | def unpad(self, s): 36 | return s[:-ord(s[len(s)-1:])] 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cloacked-pixel-python3 2 | As **NOBODY** is using python2, so. A python3 version of [cloacked-pixel](https://github.com/livz/cloacked-pixel.git). 3 | 4 | # Install 5 | Follow these shell commands: 6 | 7 | ```shell 8 | # clone this repo to local 9 | git clone https://github.com/Grazee/cloacked-pixel-python3.git 10 | 11 | # install python3 dependencies 12 | pip3 install -r requirements.txt 13 | ``` 14 | 15 | # Usage 16 | ## Hide 17 | To hide data or file into an image: 18 | 19 | ```shell 20 | python3 lsb.py hide -i [img_file] -s [payload_file] -o [out_file] -p [password] 21 | ``` 22 | 23 | **For example**, hide `secret.png` into `origin.jpg` with password `1234567`. 24 | 25 | ```shell 26 | python3 lsb.py hide -i example/origin.jpg -s example/secret.png -o stego.png -p 1234567 27 | ``` 28 | 29 | The command above will generate a new file named `stego.png`. 30 | 31 | ## Extract 32 | To extract data or file from an stego image: 33 | 34 | ```shell 35 | python3 lsb.py extract -i [stego_file] -o [out_file] -p [password] 36 | ``` 37 | 38 | **For example**, extract secret data from `stego.jpg` with password `1234567`, and save those data as file `secret.png`: 39 | 40 | ```shell 41 | python3 lsb.py extract -i example/stego.png -o secret.png -p 1234567 42 | ``` 43 | -------------------------------------------------------------------------------- /lsb.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import struct 3 | import numpy 4 | import argparse 5 | import matplotlib.pyplot as plt 6 | from Crypto.Util.number import long_to_bytes 7 | 8 | from PIL import Image 9 | 10 | from crypt import AESCipher 11 | 12 | # Decompose a binary file into an array of bits 13 | def decompose(data): 14 | v = [] 15 | 16 | # Pack file len in 4 bytes 17 | fSize = len(data) 18 | bs = b'' 19 | size_byte = struct.pack('i', fSize) 20 | bs += size_byte 21 | bs += data 22 | 23 | # bs += [ord(b) for b in data] 24 | 25 | for b in bs: 26 | for i in range(7, -1, -1): 27 | v.append((b >> i) & 0x1) 28 | 29 | return v 30 | 31 | # Assemble an array of bits into a binary file 32 | def assemble(v): 33 | bs = b"" 34 | 35 | length = len(v) 36 | for idx in range(0, len(v), 8): 37 | b = 0 38 | for i in range(0, 8): 39 | if (idx + i < length): 40 | b = (b << 1) + v[idx + i] 41 | b = long_to_bytes(b) 42 | bs = bs + b 43 | 44 | # print("debug:", len(bs[:4])) 45 | payload_size = struct.unpack("i", bs[:4])[0] 46 | 47 | return bs[4: payload_size + 4] 48 | 49 | # Set the i-th bit of v to x 50 | def set_bit(n, i, x): 51 | mask = 1 << i 52 | n &= ~mask 53 | if x: 54 | n |= mask 55 | return n 56 | 57 | # Embed payload file into LSB bits of an image 58 | def embed(imgFile, payload, outFile, password): 59 | # Process source image 60 | img = Image.open(imgFile) 61 | (width, height) = img.size 62 | conv = img.convert("RGBA").getdata() 63 | print("[*] Input image size: %dx%d pixels." % (width, height)) 64 | max_size = width*height*3.0/8/1024 # max payload size 65 | print("[*] Usable payload size: %.2f KB." % (max_size)) 66 | 67 | f = open(payload, "rb") 68 | data = f.read() 69 | f.close() 70 | print("[+] Payload size: %.3f KB " % (len(data)/1024.0)) 71 | 72 | # Encypt 73 | cipher = AESCipher(password) 74 | data_enc = cipher.encrypt(data) 75 | 76 | # Process data from payload file 77 | v = decompose(data_enc) 78 | 79 | # Add until multiple of 3 80 | while(len(v)%3): 81 | v.append(0) 82 | payload_size = len(v)/8/1024.0 83 | print("[+] Encrypted payload size: %.3f KB " % (payload_size)) 84 | if (payload_size > max_size - 4): 85 | print("[-] Cannot embed. File too large") 86 | sys.exit() 87 | 88 | # Create output image 89 | steg_img = Image.new('RGBA',(width, height)) 90 | data_img = steg_img.getdata() 91 | idx = 0 92 | for h in range(height): 93 | for w in range(width): 94 | (r, g, b, a) = conv.getpixel((w, h)) 95 | if idx < len(v): 96 | r = set_bit(r, 0, v[idx]) 97 | g = set_bit(g, 0, v[idx+1]) 98 | b = set_bit(b, 0, v[idx+2]) 99 | data_img.putpixel((w,h), (r, g, b, a)) 100 | idx = idx + 3 101 | 102 | steg_img.save(outFile, "PNG") 103 | print("[+] %s embedded successfully!" % payload) 104 | 105 | # Extract data embedded into LSB of the input file 106 | def extract(in_file, out_file, password): 107 | # Process source image 108 | img = Image.open(in_file) 109 | (width, height) = img.size 110 | conv = img.convert("RGBA").getdata() 111 | print("[+] Image size: %dx%d pixels." % (width, height)) 112 | 113 | # Extract LSBs 114 | v = [] 115 | for h in range(height): 116 | for w in range(width): 117 | (r, g, b, a) = conv.getpixel((w, h)) 118 | v.append(r & 1) 119 | v.append(g & 1) 120 | v.append(b & 1) 121 | 122 | data_out = assemble(v) 123 | 124 | # Decrypt 125 | cipher = AESCipher(password) 126 | data_dec = cipher.decrypt(data_out) 127 | 128 | # Write decrypted data 129 | out_f = open(out_file, "wb") 130 | out_f.write(data_dec) 131 | out_f.close() 132 | 133 | print("[+] Written extracted data to %s." % out_file) 134 | 135 | # Statistical analysis of an image to detect LSB steganography 136 | def analyse(in_file): 137 | ''' 138 | - Split the image into blocks. 139 | - Compute the average value of the LSBs for each block. 140 | - The plot of the averages should be around 0.5 for zones that contain 141 | hidden encrypted messages (random data). 142 | ''' 143 | BS = 100 # Block size 144 | img = Image.open(in_file) 145 | (width, height) = img.size 146 | print("[+] Image size: %dx%d pixels." % (width, height)) 147 | conv = img.convert("RGBA").getdata() 148 | 149 | # Extract LSBs 150 | vr = [] # Red LSBs 151 | vg = [] # Green LSBs 152 | vb = [] # LSBs 153 | for h in range(height): 154 | for w in range(width): 155 | (r, g, b, a) = conv.getpixel((w, h)) 156 | vr.append(r & 1) 157 | vg.append(g & 1) 158 | vb.append(b & 1) 159 | 160 | # Average colours' LSB per each block 161 | avgR = [] 162 | avgG = [] 163 | avgB = [] 164 | for i in range(0, len(vr), BS): 165 | avgR.append(numpy.mean(vr[i:i + BS])) 166 | avgG.append(numpy.mean(vg[i:i + BS])) 167 | avgB.append(numpy.mean(vb[i:i + BS])) 168 | 169 | # Nice plot 170 | numBlocks = len(avgR) 171 | blocks = [i for i in range(0, numBlocks)] 172 | plt.axis([0, len(avgR), 0, 1]) 173 | plt.ylabel('Average LSB per block') 174 | plt.xlabel('Block number') 175 | 176 | # plt.plot(blocks, avgR, 'r.') 177 | # plt.plot(blocks, avgG, 'g') 178 | plt.plot(blocks, avgB, 'bo') 179 | 180 | plt.show() 181 | 182 | # def show_usage(): 183 | # sys.exit() 184 | 185 | if __name__ == "__main__": 186 | usage_text = """ 187 | LSB steganogprahy. Hide files within least significant bits of images. 188 | 189 | example: 190 | python3 lsb.py hide -i [img_file] -s [payload_file] -o [out_file] -p [password] 191 | python3 lsb.py extract -i [stego_file] -o [out_file] -p [password] 192 | python3 lsb.py analyse -i [stego_file] 193 | """ 194 | 195 | parser = argparse.ArgumentParser(usage_text) 196 | parser.add_argument('-i', help='file input', type=str, dest='in_file') 197 | parser.add_argument('-o', help='file output', type=str, dest='out_file', default='out.png') 198 | parser.add_argument('-s', help='file to hide as secret', type=str, dest='secret_file') 199 | parser.add_argument('-p', help='passcode for hide/extract secret', type=str, dest='password') 200 | 201 | if len(sys.argv) <= 1: 202 | parser.print_help() 203 | exit() 204 | 205 | u = sys.argv.pop(1) 206 | 207 | args = parser.parse_args() 208 | 209 | if u == "hide": 210 | if args.in_file and args.secret_file and args.out_file and args.password: 211 | embed(args.in_file, 212 | args.secret_file, 213 | args.out_file, 214 | args.password) 215 | else: 216 | parser.print_help() 217 | elif u == "extract": 218 | if args.in_file and args.out_file and args.password: 219 | extract(args.in_file, 220 | args.out_file, 221 | args.password) 222 | else: 223 | parser.print_help() 224 | elif u == "analyse": 225 | if args.in_file: 226 | analyse(args.in_file) 227 | else: 228 | parser.print_help() 229 | else: 230 | parser.print_help() 231 | --------------------------------------------------------------------------------