CTF盲水印詳解
前言
在CTF雜項題型中,盲水印的出現頻率是相當高的,但大多數人處于只會用腳本的階段,沒有對原理進行深入理解,這篇文章主要把盲水印的原理和解題過程總結一下。
基本知識
盲水印的基本原理如圖所示:

主要的知識點在于傅里葉變換把原圖變為頻譜圖,再疊加水印,將含水印的頻譜圖進行傅里葉逆變換得到含水印的圖像。那么思考傅里葉變換如何將原圖變為頻譜圖,再將頻譜圖變為圖像呢?下面對傅里葉變換進行分析。
傅里葉分析
傅里葉變換涉及的知識點太多了,第一次接觸傅里葉分析的讀者可以先參考一下這篇文章,傅里葉分析之掐死教程(完整版),附鏈接
https://zhuanlan.zhihu.com/p/19763358
下面介紹一下傅里葉變換的一些前置知識。
傅里葉變換基礎知識
介紹一下頻域,時域。以音樂為來舉例子,時域就是我們觀察到鋼琴的琴弦一會上一會下的擺動,就如同一支股票的走勢;而在頻域,只有那一個永恒的音符。任何周期函數,都可以看作是不同振幅,不同相位正弦波的疊加。以音樂為例,利用對不同琴鍵不同力度,不同時間點的敲擊,可以組合出任何一首樂曲。
時域的基本單元就是“1秒”,如果我們將一個角頻率為的正弦波看作基礎,那么頻域的基本單元就是有了“1”,頻域的“0”就是一個周期無限長的正弦波,也就是一條直線!所以在頻域,0頻率也被稱為直流分量,在傅里葉級數的疊加中,它僅僅影響全部波形相對于數軸整體向上或是向下而不改變波的形狀,鑒于正弦波是周期的,我們需要設定一個用來標記正弦波位置的東西。也就是相位。
最后借用文章的一張圖:

傅里葉變換
傅里葉的原理表明,任何連續測量的時序或信號,都可以表示為不同頻率的正弦波信號的無限疊加。利用傅立葉變換算法直接測量原始信號,以累加方式來計算該信號中不同正弦波信號的頻率、振幅和相位就可以表示原始信號。其等價關系可以表示為:
再來看看圖像的傅里葉變換,上述舉得例子是一維信號的傅里葉變換,并且信號是連續的,我們知道圖像是二維離散的,連續與離散都可以用傅里葉進行變換,那么二維信號就是在方向與方向都進行一次一維的傅里葉變換得到。下面看看二維傅里葉變換,一個圖像為的圖像進過離散傅里葉變換得到,那么一般的公式為:
它的反變換就是
反變換就可以實現將頻域圖像恢復到時域圖像。對正變換分析,當u=v=0時,那么:
這個式子f(x,y)就是時域里面圖像的灰度。其實是整個圖像的灰度求平均。在計算機領域中,灰度數字圖像是每個像素只有一個采樣顏色的圖像。這類圖像通常顯示為從最暗黑色到最亮的白色的灰度,盡管理論上這個采樣可以任何顏色的不同深淺,甚至可以是不同亮度上的不同顏色。灰度圖像與黑白圖像不同,在計算機圖像領域中黑白圖像只有黑白兩種顏色,灰度圖像在黑色與白色之間還有許多級的顏色深度。那么F(0,0)在頻域內稱為直流分量,其他的所有F稱為交流分量,直流分量可以看到是在0處獲得的,所以很明顯是存在于低頻分量下的。為了便于觀察,我們常常使直流成分出現在窗口的中央,可采取換位方法,變換后中心為低頻,向外是高頻。

在分析圖像信號的頻率特性時,對于一幅圖像,直流分量表示預想的平均灰度,低頻分量代表了大面積背景區域和緩慢變化部分,高頻部分代表了它的邊緣,細節,跳躍部分以及顆粒噪聲等。
盲水印原理
對于水印和原圖,我們很容易得到時域表示 ,為了讓水印的信息能在頻域上盡量平均分布,引入隨機變換 來對時域上的水印進行變換,即
同時我們對原圖進行二維離散傅里葉變換
接下來我們引入能量系數對水印和原圖頻域合成得到
然后逆變換我們就得到了加了盲水印的圖片(時域表示)
如果想解水印,那么首先對 變換得到
然后減去原圖的頻域并且做隨機變換 的逆變換就得到了水印的時域表示
這就是圖片盲水印的基本原理。
盲水印特征
水印對稱
因為 和 是共軛的,的它們的實部應該是一樣的,如果實部不一樣的話逆變換回去勢必在虛部上會損失一部分信息(我們寫入圖片的時候只用實部),考慮到我們想盡量提高盲水印的隱匿性,所以我們的水印應該是關于圖片中心對稱的。
隱匿性
由于在頻域疊加的時候我們一般的策略是讓水印信息盡可能隨機分布在原圖頻域的所有分信號上,因此在逆變換回時域后整體圖片其實差別并不大。
加密性
在前面推導盲水印的過程中,我們實際上有一個密匙 ,因為只有知道了和原圖,才能完成解水印的過程, 這相當于完成了簡單的對稱式加密,也就是說即使攻擊者知道圖片加了盲水印,由于沒有 (和原圖),也無法去掉盲水印或者替換盲水印。
魯棒性
有時候攻擊者可能對圖片進行處理比如剪裁、壓縮等,由于盲水印已經將信息分布在了頻域的各個分信號上,因此可以有效抵抗這些攻擊。
python實踐
現在我們用合天網安的圖標來演示:

import cv2
import numpy as np
import matplotlib.pyplot as plt
img = cv2.imread('hetian.jpg',0) #直接讀為灰度圖像
f = np.fft.fft2(img)
fshift = np.fft.fftshift(f)#將圖像中的低頻部分移動到圖像的中心
#取絕對值:將復數變化成實數
#取對數的目的為了將數據變化到較小的范圍(比如0-255)
s1 = np.log(np.abs(f))
s2 = np.log(np.abs(fshift))
plt.subplot(121),plt.imshow(s1,'gray'),plt.title('original')
plt.subplot(122),plt.imshow(s2,'gray'),plt.title('center')
plt.show()
運行結果如下所示:

頻域變換到時域的操作就是逆向傅里葉變換再走一遍(比如先反中心化,在逆變換)。
import cv2
import numpy as np
import matplotlib.pyplot as plt
img = cv2.imread('hetian.jpg',0) #直接讀為灰度圖像
f = np.fft.fft2(img)
fshift = np.fft.fftshift(f)
#取絕對值:將復數變化成實數
#取對數的目的為了將數據變化到0-255
s1 = np.log(np.abs(fshift))
plt.subplot(131),plt.imshow(img,'gray'),plt.title('original')
plt.subplot(132),plt.imshow(s1,'gray'),plt.title('center')
# 逆變換
f1shift = np.fft.ifftshift(fshift)
img_back = np.fft.ifft2(f1shift)
#出來的是復數,無法顯示,取絕對值
img_back = np.abs(img_back)
plt.subplot(133),plt.imshow(img_back,'gray'),plt.title('img back')

盲水印實踐
到這里我們已經理解完盲水印的基本原理了,下面我們開始實踐。在CTF比賽中,遇到盲水印的題目我會使用github上的盲水印腳本。我們利用該腳本進行盲水印加密和解密。
我們在當前目錄下有三個文件,分別為腳本文件,原圖(hetian.png)和水印(flag.png)。

運行完生成hetin2.png和flag2.png。

其中flag2.png可以很清楚的看到水印。

接下來我們對盲水印的隱匿性進行探究,首先我們對hetian2.png加上合天的文字,如圖所示:

然后進行盲水印解密

可以看到解出來處理的水印變得模糊,但還是可以清楚地看出水印的內容,說明盲水印魯棒性比較好。