--- title: Python圖片隱寫 tags: python, picture, cyber security, steganography --- # Python圖片隱寫 > [TOC] > > Reference Website: > - [Python LSB圖片隱寫技術](https://www.itread01.com/content/1541820687.html) > - [如何在圖片中隱藏信息?Python3實現圖片隱寫術](https://kknews.cc/zh-tw/other/p8nq5l2.html) > - [隱寫技巧 - PNG文件中的隱寫技巧](https://3gstudent.github.io/%E9%9A%90%E5%86%99%E6%8A%80%E5%B7%A7-PNG%E6%96%87%E4%BB%B6%E4%B8%AD%E7%9A%84LSB%E9%9A%90%E5%86%99/) > - [隱寫術之圖片隱寫](https://zhuanlan.zhihu.com/p/62895080) > - [Steganography Tutorial - Hiding Text inside an Image](https://www.youtube.com/watch?v=q3eOOMx5qoo) > - [Steganography Tutorial | How To Hide Text Inside The Image | Cybersecurity Training | Edureka](https://www.youtube.com/watch?v=xepNoHgNj0w) > [Steganography Tutorial – A Complete Guide For Beginners](https://www.edureka.co/blog/steganography-tutorial) > - [Image based Steganography using Python?](https://www.tutorialspoint.com/image-based-steganography-using-python) > - [Image based Steganography using Python](https://www.geeksforgeeks.org/image-based-steganography-using-python/) > - [steganography](https://searchsecurity.techtarget.com/definition/steganography) *** # ***What is Steganography?*** > **隱寫術 (Steganography)** 是一個避免訊息被偵測的技術,它將訊息隱藏在一般的非秘密文件中。 * 隱寫術 (Steganography) * Steganography 源自希臘文中的 steganos 和 graphein。 * steganos 代表 hidden or covered (隱藏) 的意思。 * graph 代表 to write (書寫) 的意思。 * 隱寫術 可被用來隱藏任何類型的數位內容。 * 隱寫術的類型,亦即訊息可隱藏於以下的載體中: * 文字 * 它可以是利用改變字型的顯示格式來隱藏訊息。 * 如:[美團隊研發「字型加密術」](https://buzzorange.com/techorange/2018/05/18/font-encryption-system/) * 圖像 * 圖像被廣泛運用於隱寫術上,因為圖像由大量的bit所組成,方便訊息的隱藏。 * 方法: * ==Least Significant Bit Insertion== * Masking and Filtering * Redundant Pattern Encoding * Encrypt and Scatter * Coding and Cosine Transformation * 音檔 * 將訊息嵌入音頻訊號中,藉由改變音頻訊號相應的二進位序列來隱藏訊息。 * 影片 * 可以視為圖像隱寫術和音檔隱寫術的結合。 * 網路(Protocol Steganography) * 在資料傳輸中,將訊息嵌入網路協定(如:TCP、UDP、ICMP...)的技術。 * 如:你可以在TCP/IP封包的標頭中隱藏訊息。 * 應用: * 【實際案例】 > 2012年,大眾點評和食神網的競爭非常激烈,後者開始大規模地爬取前者的數據,主要是圖片。 > 大眾點評沒有走中國的司法流程,而是直接向APP STORE提交了證據,使食神的APP下架2次。 > 這些證據就是食神爬取的圖片,圖片中用隱寫術嵌入了大眾點評的版權資訊! * 【合法利用】 > 添加商標的浮水印 (Watermark),以識別未經授權許可的文件。 * 【非法利用】 > 惡意軟件開發人員使用隱寫術來掩蓋惡意程式碼的傳輸。 # *History of Steganography* > **隱寫術** 在古代時便已存在,數千年來以各種形式實踐,以保持通信隱密性。 * **Wax Tablet** * 隱寫術的第一次使用,可追溯到公元前440年,當時古希臘人在木頭上寫下訊息,再使用蠟覆蓋。 * **Invisible Inks** * 羅馬人使用各種形式的隱形墨水來隱藏訊息,並使用光或熱來破譯。 * [How to Make Your Own Invisible Ink?](https://www.thoughtco.com/make-your-own-invisible-ink-605973) * **Microdots** * 二次世界大戰期間,德國人引進了微點 (Microdots),這些微點可以是完整的文件、圖片或計劃。 * 亦即將原始正常大小的完整文件縮小到一個點的大小,並附在正常的文書工作上。 * **Null Ciphers** * 空密碼,也稱為隱藏密碼,是一種古老的加密形式,被視為一種簡單的隱寫術形式。 * 將密文隱藏於看似普通的明文中。解密時,必須將明文中的字元丟棄後,才能看到訊息。 * 密文:Wikipedia > It's important **w**e allow anyone **i**nterested in gaining **k**nowledge access to **i**nformation which is **p**ublished freely. There **e**xists a website **d**evoted to this **i**dea, and you **a**re on it! # *Steganography vs. Cryptography* > **Steganography** 和 **Cryptography** 是兩種不同的概念。但是,有著相同的目標,保護訊息不被第三方得知。 | | STEGANOGRAPHY | CRYPTOGRAPHY | | -------- | -------- | -------- | | **Definition** | It is a technique to hide the existence of communication | It’s a technique to convert data into an incomprehensible form | | **Purpose** | Keep communication secure | Provide data protection | | **Data Visibility** |Never | Always | | **Data Structure** | Doesn’t alter the overall structure of data | Alters the overall structure of data | | **Key** | Optional, but offers more security if used | Necessary requirement | | **Failure** | Once the presence of a secret message is discovered, anyone can use the secret data | If you possess the decryption key, then you can figure out original message from the ciphertext | * Steganography 可與 Encryption 相結合,來作為隱藏或保護資料的額外步驟。 * 做法: * 先加密秘密訊息,再使用隱寫術將加密的秘密訊息嵌入圖片或其他載體中。 # *Image-based Steganography* > Hiding the data by taking the cover object as the image is known as **image steganography**. ## [Pixels & Bits](https://www.youtube.com/watch?v=15aqFQQVBWU) ![](https://i.imgur.com/cX87LqF.png) * 每張圖像是由 pixels (像素) 所組成,pixel 為影像顯示的基本單位。 * 每個 pixels (像素),儲存的是由 RGB 三原色組成的顏色值。亦即 Red, Green, Blue 組成每張圖像的每一個 pixel (像素)。 > Every image is made up of pixels and every pixel contains 3-values (red, green, blue). * Red, Green, Blue 各自使用 8-bit 來儲存顏色值。 * 若以十進位表示 * 最大值:255 (二進位:11111111) * 最小值:0 (二進位:00000000) ## MSB vs. LSB ![](https://i.imgur.com/ArSWwYo.png) * 最高有效位 (MSB,Most Significance Bit) * 指二進位中最高值的 bit。MSB 是最高加權位,具有最大的影響力。 * MSB位於二進位數的最左側。 ![](https://i.imgur.com/jARF2ns.png) * 最低有效位 (LSB,Least Significance Bit) * 指二進位中最低值的bit。LSB 是最低加權位,具有最小的影響力。 * LSB位於二進位數的最右側。 ![](https://i.imgur.com/5FoPjaE.png) ## ==**LSB Steganography**== * 藉由修改RGB之二進位顏色值的最低有效位(LSB)來隱藏訊息。 ### Concept * Every byte of data is converted to its 8-bit binary code using ==ASCII== values. * Now pixels are read from left to right in a group of 3 containing a total of 9 values. * The first 8-values are used to store the binary data. * The value is made odd, ==if 1 occurs and even, if 0 occurs==. ### Encode the data: * Suppose the message to be hidden is '**Hii**'. Since the message is of **3-bytes**, therefore, pixels required to encode the data is 3 x 3 = 9. * Consider a 4 x 3 image with total 12-pixels, which are sufficient to encode the given data. > [==(27, 64, 164), (248, 244, 194), (174, 246, 250)==, (149, 95, 232), > (188, 156, 169), (71, 167, 127), (132, 173, 97), (113, 69, 206), > (255, 29, 213), (53, 153, 220), (246, 225, 229), (142, 82, 175)] * ASCII value of '**H**' is **72** whose binary equivalent is **01001000**. **Taking first 3-pixels (27, 64, 164), (248, 244, 194), (174, 246, 250) to encode.** * Now ==change the pixel to odd for 1 and even for 0==. So, the modifies pixels are (26, 63, 164), (248, 243, 194), (174, 246, 250). The new image will look like: > [==(26, 63, 164), (248, 243, 194), (174, 246, 250)==, (148, 95, 231), ``` (26, 63, 164), (248, 243, 194), (174, 246, 250) 0 1 0 0 1 0 0 0 0 => H ``` > (188, 155, 168), (70, 167, 126), (132, 173, 97), (112, 69, 206), > (254, 29, 213), (53, 153, 220), (246, 225, 229), (142, 82, 175)] ### 程式碼 #### 程式流程 先確認使用者要用何種功能(隱寫or解密) * 隱寫 1. 開圖檔 2. 將訊息轉換為ASCII的二進位編碼,並加上`1111111111111110`作為結尾 3. 讀取圖片pixel的RGBA值(紅、綠、藍、透明度) 4. 將訊息二進位編碼一個bit一個bit寫入R or G or B or A 5. 將更改後的RGBA編碼重新存檔為圖片 * 解密 1. 開圖檔 2. 讀取pixel的RGBA值 3. 將R or G or B or A值轉換為二進制,並讀取其最後一個bit,將其寫入二進位字串 4. 假設該字串最後16碼為`1111111111111110`則代表訊息結束 5. 停止讀取,將該二進位字串轉換為文字字串 #### 各部分解析 * **str2bin(message)** ```python= def str2bin(message): message_bytes = bytes(message, 'ascii') return "".join(["{:08b}".format(x) for x in message_bytes]) ``` 1. 將字串轉為ascii編碼 2. 將ascii編碼轉為二進位 `{:08b}`這種寫法要分成三部分來看 * `{:8}`: 固定字串長度為8,不足部分以空格補位 * `{:08}`: 同上,但不足部分以0補位 * `{:b}`: 將數字轉為二進位字串 * **bin2str(binary)** ```python= def bin2str(binary): binary_split = [] count = 0 temp = "" for i in range(len(binary)): count += 1 temp += binary[i] if count == 8: binary_split.append(temp) count = 0 temp = "" return "".join([chr(int(b, 2)) for b in binary_split]) ``` 1. 將二進位字串分成八個八個一組 2. `chr(int(b, 2))` * `int(b, 2)` 先將二進位轉為10進位 * `chr()` 再將10進位轉為字串 * **encode(num, digit)** 將num轉為二進位,再將digit插入二進位的最後一位數 ```python= def encode(num, digit): ''' 1. change num to binary. 2. add digit to last digit of num binary. 3. change num binary back to decimal, and return it. ''' bin_num = bin(num) bin_num = bin_num[:-1] + digit return int(bin_num, 2) ``` 1. 將num轉成二進位 2. 將轉換後的二進位字串最後一位數換成digit 3. 將新的二進位字串轉回十進位並回傳 * **decode(num)** 將num轉為二進位,並回傳最後一位數 ```python= def decode(num): return bin(num)[-1] ``` * **hide(filename, message)** ```python= def hide(filename, message): img = Image.open(filename) binary = str2bin(message) + "1111111111111110" if img.mode == 'RGBA': datas = img.getdata() newData = [] count = 0 message_end = False for data in datas: if not message_end: newpix = [] for num in data : if count < len(binary): newpix.append(encode(num, binary[count])) count += 1 else: newpix.append(num) message_end = True newData.append(tuple(newpix)) else: break img.putdata(newData) img.save(filename, "PNG") return "Completed!" else: return "Incorrect Image Mode, couldn't hide :(" ``` 1. 開啟圖檔 2. 將message轉換為二進位 3. 判斷是否為RGBA的顏色編碼 * 是 1. 讀取圖檔每個pixel的RGBA值 2. 用一個newData list來存取更改後的RGBA值 3. 利用count計算目前寫入到第幾位 4. 利用message_end來確認訊息是否已寫完 5. 讀取每個pixel的RGBA值 * 訊息是否還未結束 * 是 * 新增一個newpix的list來存取更改後pixel的RGBA值 * 分別讀取R、G、B與A的值 * 如果訊息還未結束,則將訊息插入R or G or B or A中 * 如果訊息已結束則結束寫入動作 * 否 結束讀取 6. 將修改過後的pixel寫入圖片 * putdata(): 這個函式會將新的pixel寫入圖片中,直到所有資料都寫入,或是原圖已讀取結束 > 參考: https://pillow.readthedocs.io/en/3.3.x/reference/Image.html#PIL.Image.Image.putdata * 否 跟使用者說明圖檔模式不支援 * **retr(filename)** ```python= def retr(filename): img = Image.open(filename) binary = "" if img.mode == 'RGBA': datas = img.getdata() for data in datas: for num in data: binary += decode(num) if binary[-16:] == "1111111111111110": print("Seccuss!") return bin2str(binary[:-16]) return bin2str(binary) return "Incorrect Image Mode, couldn't retrieve :(" ``` 1. 開啟圖片 2. 確認模式是否為RGBA * 是 1. 讀取圖檔每個pixel值 2. 分別讀取R、G、B、A值 3. 將每個值轉換為二進位,並讀取最後一位數字 4. 如果讀取到`1111111111111110`這個結尾符號,則將二進位訊息轉為string輸出,並結束程式 * 否 跟使用者說明圖檔模式不支援 * **main()** ```python= def main(): parser = optparse.OptionParser('python lsbsteg.py ' + '-e/-d <target file>') parser.add_option('-e', dest = 'hide', type='string', help='target pic path to hide text') parser.add_option('-d', dest = 'retr', type='string', help='target pic path to retrieve text') (options, args) = parser.parse_args() if options.hid != None: text = input("Enter a message to hide: ") print(hide(options.hide, text)) elif options.retr != None: print(retr(options.retr)) else: print(parser.usage) quit() ``` 1. 新增parser,這個parser會幫你處裡使用者輸入 2. #### 完整程式碼 ```python= from PIL import Image import optparse def str2bin(message): message_bytes = bytes(message, 'ascii') return "".join(["{:08b}".format(x) for x in message_bytes]) def bin2str(binary): binary_split = [] count = 0 temp = "" for i in range(len(binary)): count += 1 temp += binary[i] if count == 8: binary_split.append(temp) count = 0 temp = "" return "".join([chr(int(b, 2)) for b in binary_split]) def encode(num, digit): ''' 1. change num to binary. 2. add digit to last digit of num binary. 3. change num binary back to decimal, and return it. ''' bin_num = bin(num) bin_num = bin_num[:-1] + digit return int(bin_num, 2) def decode(num): return bin(num)[-1] def hide(filename, message): img = Image.open(filename) binary = str2bin(message) + "1111111111111110" if img.mode == 'RGBA': datas = img.getdata() newData = [] count = 0 message_end = False for data in datas: if not message_end: newpix = [] for num in data : if count < len(binary): newpix.append(encode(num, binary[count])) count += 1 else: newpix.append(num) message_end = True newData.append(tuple(newpix)) else: break img.putdata(newData) img.save(filename, "PNG") return "Completed!" else: return "Incorrect Image Mode, couldn't hide :(" def retr(filename): img = Image.open(filename) binary = "" if img.mode == 'RGBA': datas = img.getdata() for data in datas: for num in data: binary += decode(num) if binary[-16:] == "1111111111111110": print("Seccuss!") return bin2str(binary[:-16]) return bin2str(binary) return "Incorrect Image Mode, couldn't retrieve :(" def main(): parser = optparse.OptionParser('python lsbsteg.py ' + '-e/-d <target file>') parser.add_option('-e', dest = 'hide', type='string', help='target pic path to hide text') parser.add_option('-d', dest = 'retr', type='string', help='target pic path to retrieve text') (options, args) = parser.parse_args() if options.hide != None: text = input("Enter a message to hide: ") print(hide(options.hide, text)) elif options.retr != None: print(retr(options.retr)) else: print(parser.usage) quit() if __name__ == '__main__': main() ``` --- ### Shorter Version ```python= from PIL import Image from stegano import lsb import optparse parser = optparse.OptionParser('usage %prog ' + '-e/-d <target file>') parser.add_option('-e', dest = 'hide', type='string', help='target pic path to hide text') parser.add_option('-d', dest = 'retr', type='string', help='target pic path to retrieve text') (options, args) = parser.parse_args() if options.hide != None: text = input("Enter a message to hide: ") image = Image.open(options.hide) image_stego = lsb.hide(image, text) image_stego.save(options.hide) print("Success") elif options.retr != None: message = lsb.reveal(options.retr) print("Complete") print(message) else: print(parser.usage) quit() ```