隱寫術是一種將保密信息隱藏在公開信息中的技術,利用圖像文件的特性,咱們能夠把一些想要刻意隱藏的信息或者證實身份、版權的信息隱藏在圖像文件中。好比早期流行的將一些下載連接、種子文件隱藏在圖片文件中進行傳播,再好比某互聯網公司內部論壇「月餅事件」中經過員工截圖精準定位我的信息的技術,均可以歸爲圖像隱寫技術(Image Steganography)。本文主要介紹一些常見的圖像隱寫技術及 Python 實現方法。python
圖像是由像素組成的,但圖像文件除了保存像素信息以外,還須要存儲一些額外的描述信息。以常見的 JPEG 圖像爲例,文件格式規定了一些特定的字符用以標誌特定的元數據起點位,以下圖所示:git
其中經常使用於存儲拍攝設備信息的 EXIF 標記即存儲在 APPn 標記位。以二進制格式讀取一張 JPEG 圖像,能夠看到文件的起始(SOI)、終止(EOI)符號:github
with open("input.jpg", "rb") as f:
f_bytes = f.read()
print( f_bytes[:2] )
print( f_bytes[-2:] )
# OUTPUT
""" b'\xff\xd8' b'\xff\xd9' """
複製代碼
經過二進制格式,能夠直接在圖像文件後面追加信息:web
txt = '你好 PyHub!'
# 編碼
with open("out_append.jpg", "wb") as f:
with open("input.jpg", "rb") as ff:
f.write(f_bytes[sos:])
f.write('你好 PyHub!'.encode())
# 解碼
with open("out_append.jpg", "rb") as f:
content = f.read()
eoi = content.find(b'\xff\xd9')
print(content[eoi+2:].decode())
# OUTPUT
""" 你好 PyHub! """
複製代碼
元數據修改的方法能夠作到不損失任何圖像質量,但同時也最容易被攻擊,如今大部分圖像上傳應用會對圖像內容進行清洗,去除沒必要要的元數據以保護用戶隱私。markdown
第二種方法則是針對具體的圖像數據進行修改,其原理就是利用圖像自己豐富的信息量,在進行少許修改(篡改)的狀況下,不會影響總體視覺效果。最多見的方法是最低有效位(Least Significant Bit, LSB),這種方法有不少變種,但其核心思想就是利用最低位對像素值影響不大,篡改後中像素視覺效果影響也不大。網絡
好比咱們能夠將一張簡單的水印圖片進行二值化,獲得一張只有 0/1
表示的圖片:app
from PIL import Image
import numpy as np
from matplotlib import pyplot as plt
logo = Image.open("logo.jpg").convert("L")
logo_bin = np.where( np.array(logo) > 128, 1, 0).astype(np.uint8)
複製代碼
base = Image.open("input.jpg")
# 最低位變爲 0
base_lsb = np.bitwise_and(base, 0xFE)
# 將 logo 拼接到最低位(其中一個通道,也能夠保留3個通道)
logo_h, logo_w = logo_bin.shape
base_lsb[:logo_h, :logo_w, 0] += logo_bin
# 解碼
base_decode = np.bitwise_and(base_lsb[:,:,0], 1)
複製代碼
上面的例子僅僅只是佔用了原圖單通道最低位 1bit
的信息,也能夠原圖和隱藏圖片各佔一半,原理是同樣的。ide
能夠參考:github.com/kelvins/ste…oop
對圖像數據進行比特操做的方法有不少,好比能夠經過修改最低位的奇偶值,直接存儲二進制編碼(www.geeksforgeeks.org/image-based…);或者按比特位對圖像進行從新分割,等等。學習
很顯然,經過操做圖像數據進行隱寫會在必定程度上修改圖像的視覺質量,並且對於內容自己不夠豐富的圖像(例如大面積純色背景),這種方法就很容易露出馬腳。
除了對像素(空間域)進行操做外,還能夠在頻率域進行操做,其原理就是進行傅里葉變換後,對頻率域的數據加水印:
# 原圖,1-1
base = Image.open("input.jpg").convert("L")
base_fft = np.fft.fft2(np.array(base))
base_ffs = np.fft.fftshift(base_fft)
# 繪製水印
fnt = ImageFont.truetype("Cyberway Riders.otf", 40)
wm = Image.new("L", base.size, (0))
ImageDraw.Draw(wm).text((0,0), "PyHub", font=fnt, fill=(255))
wm_arr = np.array(wm)
# 反轉生成中心對稱,圖1-2
fft_wm_arr = (np.flip(wm_arr) + wm_arr).astype(np.uint8)
# 疊加水印,圖1-3
base_ffs.real[fft_wm_arr == 255] = 255
# 傅里葉逆變換,圖2-1
base_reversed = np.real( np.fft.ifft2( np.fft.ifftshift(base_ffs) ) )
# 對逆變換(編碼)後的圖進行解碼,圖2-2
base_decode = np.clip(np.fft.fftshift( np.fft.fft2(base_reversed)).real, 0, 255)
# 編碼後的圖與原圖的差別,圖2-2
base_reversed - np.array(base)
複製代碼
以上的方法比較簡單粗暴,直接對頻域的特定區域疊加了水印,實際效果可能會對原始圖片形成較大損害,更準確的方法應該是對水印圖片進行編碼,讓水印圖像均勻地分佈在各個頻率,具體能夠參考:github.com/guofei9987/…。對頻域進行修改的方法對圖像視覺效果影響更小,在面對各類圖像改寫攻擊時的還原效果更好。
一切皆可深度學習。
經過 PapersWithCode 網站能夠看到一些嘗試用深度學習的方法進行圖像隱寫的研究,例如這篇採用對抗生成網絡(GAN)模型,將數據Data
編碼到Image
中:
能夠經過 pip install steganogan
安裝做者預訓練的模型:
steganogan encode input.jpg "Hi PyHub" -o out_gan.jpg
複製代碼
效果以下: