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,13957) =>

 

 

 


(74,157232) =>

(218,13956) =>

 

 

 


(74,157232) =>

 

 

 

(74,157232) =>

使用的套件介紹

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

參考資源

Made with Slides.com