Python 圖片隱寫術
講者:王譽錚
日期:2019/12/26
Outline
什麼是隱寫術?
圖片隱寫術原理
使用的套件介紹
使用的語法介紹
實作
參考資源
什麼是隱寫術?
類型
- 文字
- 透過一定規則來改變文字或閱讀文字
- 圖像
- 寫在圖像中的某位bit中
- 音檔
- 改變音頻訊號相應的二進位序列
- 影片
- 結合圖像與音檔的隱藏法
規則:將字首第一個字移到後面,再加上ay
ex:hello => ellohay
圖片隱寫術原理
RGB

(218,139,56)
11011010
10001011
00111000
pixel
8-bit
8-bit
8-bit
11011010 = 218
11011011 = 219
01011010 = 90
原始數字
更改第一位數字
更改最後一位數字
最高有效位與最低有效位

h >>> 104
舉例:將 "h" 寫入
(218,139,56)
(74,157,232)
(217,210,233)
1. ASCII轉為二進位
104 => 01101000
2. RGB分別轉為二進位
218 => 11011010
139 => 10001011
56 => 00111000
74 => 01001010
157 => 10011101
232 => 11101000
217 => 11011001
210 => 11010010
233 => 11101001
3. 將文字的二進位寫入RGB的二進位最後一碼
11011010 => 11011010
10001011 => 10001011
00111000 => 00111001
01001010 => 01001010
10011101 => 10011101
11101000 => 11101000
11011001 => 11011000
11010010 => 11010010
11101001 => 11101001
11011010 => 218
10001011 => 139
00111001 => 57
01001010 => 74
10011101 => 157
11101000 => 232
11011000 => 216
11010010 => 210
11101001 => 233
4. 將二進位轉回十進位(RGB值)
5. 將更改後的值寫回圖片中
(218,139,57) =>
(74,157,232) =>
(218,139,56) =>
(74,157,232) =>
(74,157,232) =>
前
後
使用的套件介紹
1. optparse 命令列解析模組

2. PIL ( Python Imaging Library )
Image
- 打開檔案
- 取得pixel內的RGB
- 寫入圖片
- 存檔
- .......
pip install pillow
from PIL import Image
使用的語法介紹
list

array = [0, 1, 2, 3, 4, 5, 6, 7]
函式
函式名叫做 total
需要給他一個參數
最後會回傳 s 給呼叫他的人
def total(n):
s = 0
for i in range(1, n + 1):
s = s + i
return s




實作
1. 將字串轉為二進位
def str2bin(message):
# 將字串轉為ASCII編碼
message_bytes = bytes(message, 'ascii')
# 將ASCII編碼轉為二進位
# 08 >> 字串必為8格,不足部分以零補上
# b >> 轉為二進位
return "".join(["{:08b}".format(x) for x in message_bytes])
2. 加密
def encode(num, digit):
bin_num = bin(num)
#最後一位數改成digit
bin_num = bin_num[:-1] + digit
return int(bin_num, 2)
3. 隱藏
def hide(filename, message):
img = Image.open(filename)
# 呼叫轉碼函式並加上結束語
binary = str2bin(message) + "1111111111111110"
if img.mode == 'RGB':
# 讀取每一pixel的rgb值
datas = img.getdata()
# 存放變更過的rgb值
newData = []
count = 0
# 確認訊息是否都放入圖片中
message_end = False
for data in datas:
if not message_end:
newpix = []
# 分別讀取R,G,B值
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)
# 存檔,副檔名為png
img.save(filename, "PNG")
return "Completed!"
else:
return "Incorrect Image Mode, couldn't hide :("
4. 取出加密過的部分
def decode(num):
return bin(num)[-1]
5. 二進位轉為文字
def bin2str(binary):
binary_split = []
count = 0
temp = ""
for i in range(len(binary)):
count += 1
temp += binary[i]
# 每八位數放入binary_split中
if count == 8:
binary_split.append(temp)
count = 0
temp = ""
# 回傳轉回文字的結果
return "".join([chr(int(b, 2)) for b in binary_split])
6. 解密
def retr(filename):
img = Image.open(filename)
binary = ""
if img.mode == 'RGB':
datas = img.getdata()
for data in datas:
for num in data:
# 把所以num的最後一位數存起來
binary += decode(num)
if binary[-16:] == "1111111111111110":
print("Seccuss!")
# 回傳除了結束語的解碼
return bin2str(binary[:-16])
# 如果沒有結束語,整串回傳
return bin2str(binary)
return "Incorrect Image Mode, couldn't retrieve :("
7. 主程式
from PIL import Image
import optparse
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')
# 沒有使用-d /-e 的話,a\檔名會傳入args
(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:
#輸出提示訊息:'python lsbsteg.py -e/-d <target file>'
print(parser.usage)
quit()
if __name__ == '__main__':
main()
完整程式碼
from PIL import Image
import optparse
def str2bin(message):
# 將字串轉為ASCII編碼
message_bytes = bytes(message, 'ascii')
# 將ASCII編碼轉為二進位
# 08 >> 字串必為8格,不足部分以零補上
# b >> 轉為二進位
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]
# 每八位數放入binary_split中
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)
#最後一位數改成digit
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 == 'RGB':
# 讀取每一pixel的rgb值
datas = img.getdata()
# 存放變更過的rgb值
newData = []
count = 0
# 確認訊息是否都放入圖片中
message_end = False
for data in datas:
if not message_end:
newpix = []
# 分別讀取R,G,B值
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)
# 存檔,副檔名為png
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 == 'RGB':
datas = img.getdata()
for data in datas:
for num in data:
# 把所以num的最後一位數存起來
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')
# 沒有使用-d /-e 的話,a\檔名會傳入args
(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:
#輸出提示訊息:'python lsbsteg.py -e/-d <target file>'
print(parser.usage)
quit()
if __name__ == '__main__':
main()
參考資源
python隱寫術:https://hackmd.io/@NIghTcAt/ByKr8jxGH
python optparse命令解析模組:https://www.itread01.com/p/506878.html
Stegano:https://pypi.org/project/stegano/
Python: OptionParser的使用:http://hai-kuo.blogspot.com/2008/06/python-optionparser.html
python隱寫術
By arashi
python隱寫術
- 80