---
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()
```