作者:fransli,騰訊 PCG 前端開發(fā)工程師
Web 水印技術(shù)在信息安全和版權(quán)保護(hù)等領(lǐng)域有著廣泛的應(yīng)用,對防止信息泄露或知識產(chǎn)品被侵犯有重要意義。水印根據(jù)可見性可分為可見水印和不可見水印(盲水印),本文將分別予以介紹,帶你探秘 web 水印技術(shù)。
可見水印 最簡單的水印
一種比較常見的簡單水印場景是給文章、表格加上logo
水印,用以申明版權(quán)。
這里想要的效果就是一個淺淺的logo
平鋪展示。實(shí)現(xiàn)起來也比較簡單,只需制作一個半透明的logo
圖片,設(shè)為文章或者表格的背景圖片即可。僅需一行css
聲明。
background-image:url("./logo.png");
實(shí)現(xiàn)圖片平鋪關(guān)鍵的CSS
屬性是background-repeat
,值為repeat
時是平鋪,這也是它的默認(rèn)值,所以可以省略。
全頁面水印
照葫蘆畫瓢,如果要給整個Web
頁面加上水印,是不是給頁面的body
元素設(shè)置背景圖片平鋪展示就可以了呢?
然而通常并不會這么處理,因?yàn)槲恼潞捅砀駜?nèi)容多以文本為主,不會明顯遮擋水印,而一個完整的頁面往往還包含很多其他頁面元素,比如圖片、視頻、控件等等,它們很可能會遮擋住背景圖片,從而影響水印效果。
所以,為了避免被其他元素遮擋,針對頁面的水印一般會使用一個層級比較高且覆蓋整個頁面的元素來承載。
div.watermark{
position: fixed;
left:0;
top:0;
width: 100vw;
height: 100vh;
background-image:url("./logo.png");
opacity: .5;
z-index: 3000;
這樣一來,其他元素就遮擋不住水印了。不過,這個div
反過來可能會遮擋頁面其他元素,影響頁面元素操作。還需要一條關(guān)鍵的CSS
聲明來破解這個問題 :
pointer-events: none;
這個CSS
聲明會使該元素“可穿透”,“看得見、摸不著”,不再影響頁面操作。
動態(tài)水印
很多時候,給頁面加水印的目的并不是申明版權(quán),而是為了支持溯源。此時水印的內(nèi)容并不會只是一個logo
,通常會包含用戶信息,比如用戶名、UID、手機(jī)號等等。
這就意味著,每個用戶的水印內(nèi)容是不同的,無法通過提前準(zhǔn)備好一張圖片來滿足了。這種場景往往需要根據(jù)用戶信息動態(tài)生成圖片。
我們來看下幾種主流的動態(tài)生成水印圖片的方式:
服務(wù)端方案
傳統(tǒng)的方式是在服務(wù)端生成圖片。頁面上發(fā)起的圖片請求中可以附帶用戶信息,服務(wù)端根據(jù)這些參數(shù)動態(tài)生成圖片,并將圖片數(shù)據(jù)作為該請求的響應(yīng)返給頁面,頁面拿到后將其用作水印。
這種方式的優(yōu)點(diǎn)是兼容性好,缺點(diǎn)是需要前后端配合,增加了頁面請求和服務(wù)端資源開銷,防攻擊能力也較差。
Canvas 方案
html5
引入Canvas
特性使得瀏覽器自身具備了繪圖能力。經(jīng)過多年的發(fā)展,主流瀏覽器基本都已可以提供良好的支持。通過Canvas
可以輕松繪制圖片,并可將圖片數(shù)據(jù)導(dǎo)出,用于頁面圖片或背景。
const canvasElement = document.createElement('canvas');
const context = canvasElement.getContext('2d');
canvasElement.width = 200;
canvasElement.height = 200;
context.rotate((-30 * Math.PI) / 180);
context.font = '400 26px Arial';
context.fillStyle = '#B9C0CA';
context.textAlign = 'center';
context.textBaseline = 'middle';
context.fillText('水印文字', 70, 130);
const watermark = canvasElement.toDataURL('image/png');
通過上述示例代碼可拿到水印圖片的data URI
數(shù)據(jù),用作水印承載元素的背景圖片平鋪展示即可。
這種方式不需要服務(wù)端配合,在前端就可以完成,且有助于減少請求和服務(wù)端資源開銷。曾經(jīng)面臨的瀏覽器兼容問題現(xiàn)在也不再是問題,該方案已逐漸流行起來。
SVG 方案
對于純文字的水印來說,有沒有辦法不生成圖片而直接實(shí)現(xiàn)平鋪呢?
動態(tài)創(chuàng)建大量DOM
節(jié)點(diǎn),通過CSS
控制排列當(dāng)然可以實(shí)現(xiàn),但是繁瑣且性能差,優(yōu)雅更無從談起。
不妨換個角度思考,有沒有辦法讓文字不轉(zhuǎn)成圖片就可以用作background-image
屬性的值呢?這樣就可以利用background-repeat
實(shí)現(xiàn)平鋪效果了。
這時候可以考慮使用SVG
,因?yàn)?code>SVG具有文本和圖像的雙重特性。看上去是文本,然而在很多場景可以當(dāng)做圖片使用。
我們可以通過SVG
的相關(guān)屬性精準(zhǔn)控制字體位置、大小、顏色、透明度和旋轉(zhuǎn)角度等參數(shù)。如:
水印文字text>
svg>
考慮到瀏覽器兼容性,用作背景圖片時,建議將 SVG 編碼為 Base64(或轉(zhuǎn)義特定字符):
background-image: url("data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48dGV4dCB4PSI1MCUiIHk9IjUwJSIgZm9udC1zaXplPSIzMCIgZmlsbD0iI2EyYTliNiIgZmlsbC1vcGFjaXR5PSIwLjMiIGZvbnQtZmFtaWx5PSJzeXN0ZW0tdWksIHNhbnMtc2VyaWYiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGRvbWluYW50LWJhc2VsaW5lPSJtaWRkbGUiIHRyYW5zZm9ybT0ncm90YXRlKC00NSwgMTAwIDEwMCknPuawtOWNsOaWh+WtlzwvdGV4dD48L3N2Zz4=");
水印安全
水印是用來保護(hù)信息安全的。信息要安全,首先要確保水印自身的安全,提高水印的防攻擊(篡改、刪除等)能力。
可見水印大都是基于DOM
的,找到這個DOM
節(jié)點(diǎn),通過瀏覽器插件、抓包工具等在頁面上注入一段JAVAScript
或者CSS
代碼對其進(jìn)行篡改或刪除并不困難。
防止外部代碼篡改,一種思路是把水印元素封裝起來,與外部環(huán)境進(jìn)行隔離。
Shadow DOM
在Chrome
逐步統(tǒng)治瀏覽器江湖之后,谷歌正野心勃勃的推廣Web Components
技術(shù)。該技術(shù)允許在Web
中創(chuàng)建可重用的小部件或組件。組件化開發(fā)在前端業(yè)界已經(jīng)流行相當(dāng)長一段時間了,這主要得益于前端三大框架的推崇,但具體組件標(biāo)準(zhǔn)是由框架各自制定的,而Web Components
可理解為Web
標(biāo)準(zhǔn)化的組件。
Web Components
的一個重要特性就是“封裝”
,即可以將標(biāo)記結(jié)構(gòu)、樣式和行為隱藏起來,并與頁面上的其他代碼相隔離。比如我們熟悉的video
元素,它的進(jìn)度條、按鈕等控件都已被封裝。
Shadow DOM
接口是“封裝”
特性的關(guān)鍵所在,它可以將一個隱藏的、獨(dú)立的DOM
附加到一個元素上。
為了提高web
水印的隱蔽性,同時避免受外部代碼影響,從而在一定程度上防止篡改,可以考慮把水印元素放在Shadow DOM
中。
來看下Shadow DOM
的基本用法。使用Element.attachShadow()
方法來將一個shadow root
附加到任何一個元素上。它接受一個配置對象作為參數(shù),該對象有一個mode
屬性,值可以是open
或者closed
。open
表示可以通過頁面內(nèi)的JavaScript
方法來獲取Shadow DOM
。而closed
則表示不可以從外部獲取Shadow DOM
。
Element.attachShadow({mode: 'closed'});
樣式怎么隔離呢?Shadow DOM
中的樣式本身就是隔離的,除非主動使用CSS
變量、part
屬性等暴露,外部樣式是不會影響到組件內(nèi)的。
Mutation Observer
Shadow DOM
提高了水印的隱蔽性,同時可以防止外部代碼修改。除此之外,還有一種常見的攻擊場景——人為修改,比如在瀏覽器控制臺直接修改或刪除對應(yīng)的DOM
元素。
可以考慮“監(jiān)聽”
這種行為,一旦發(fā)生就馬上修復(fù),比如重新插入一個。那怎么實(shí)現(xiàn)這種“監(jiān)聽”
呢?現(xiàn)代瀏覽器中有多種觀察者(Observer
),比如IntersectionObserver
、PerformanceObserver
、ResizeObserver
、ReportingObserver
、MutationObserver
等。其中,MutationObserver
就可以用來監(jiān)聽DOM
變動,DOM
的任何變動,比如節(jié)點(diǎn)的增減、屬性的變動、文本內(nèi)容的變動,通過該API
都可以得到通知。
所以可以使用MutationObserver API
來監(jiān)聽水印元素DOM
變化,一旦監(jiān)聽到DOM
元素被修改或者刪除,就立即重新插入一個。
不可見水印(盲水印)
不可見水印也叫盲水印、隱水印,顧名思義是一種看不到的水印,看不到還要它做什么呢?其實(shí),不可見水印在一些對安全性要求較高的場景意義還是蠻大的。不可見水印通常具有比可見水印更好的隱蔽性和抗攻擊性。雖不可見,但通過一定的技術(shù)手段是可以將水印信息從其載體上提取出來的,這就使得其載體具備了溯源能力,在關(guān)鍵時刻往往能發(fā)揮大作用。
我總結(jié)不可見水印相對可見水印至少有以下三個明顯的優(yōu)勢:
-
更好的觀感。可見水印總給人一種“膏藥感”,甚至?xí)鸩糠秩说牟贿m,而不可見水印則不會有這個問題。
-
更佳的隱蔽性。用戶基本感知不到水印的存在。
-
更強(qiáng)的抗攻擊性。可見水印更容易受到攻擊,而不可見水印除了隱蔽性比較強(qiáng)之外,其自身往往還具備比較強(qiáng)的抗攻擊能力。
不可見水印(盲水印)屬于信息隱匿技術(shù)(也叫隱寫術(shù)),歷史悠久,手段繁多。在現(xiàn)代,隨著計(jì)算機(jī)網(wǎng)絡(luò)技術(shù)的發(fā)展,數(shù)字產(chǎn)品的信息安全和版權(quán)保護(hù)也已成為信息隱匿技術(shù)的一個重要課題。隱寫術(shù)在數(shù)字音頻、數(shù)字視頻和數(shù)字圖像領(lǐng)域有著非常廣泛的應(yīng)用。
Web
上基于DOM
的盲水印大都不靠譜,而另一方面數(shù)字圖像是信息隱藏和數(shù)字水印領(lǐng)域研究最多和最早的一種載體,相較于 Web,數(shù)字圖像領(lǐng)域有著更為成熟的數(shù)字水印算法。我們不妨先來看下數(shù)字圖像領(lǐng)域的常見盲水印技術(shù)。
在說數(shù)字水印之前,這里介紹一些數(shù)字圖像的基礎(chǔ)知識。
數(shù)字圖像(位圖)是由像素(pixel
)組成。
-
非黑即白的二值圖像,
1
個bit
即可表示1
個像素(黑白兩種狀態(tài))。所以1
個字節(jié)(byte
)可以表示8
個像素。 -
灰度圖,
1
個像素有256
種狀態(tài)(2
的8
次方),需要8
個bit
,即1
個字節(jié)。 -
彩色的
RGB
圖,有R
/G
/B
三個通道,每個通道256
種狀態(tài),使用1
個字節(jié)表示,共需3
個字節(jié)表示1
個像素。 -
RGBA
圖,在R
/G
/B
三個通道基礎(chǔ)上增加了一個透明度通道,256
種狀態(tài),額外需要1
個字節(jié),共需要4
個字節(jié)表示一個像素。
通常,考慮到計(jì)算速度和性能,圖像處理和圖像識別大都會將圖像轉(zhuǎn)成灰度圖或者選擇其中一個通道進(jìn)行。
LSB 水印
如上文所述,灰度圖像的一個像素有256
種狀態(tài),通常用灰度值(0-255
)表示,0
表示黑色,255
表示白色,灰度值越大表示亮度越高。
灰度可用一個字節(jié),即8
比特二進(jìn)制數(shù)表示,其中最高位對圖像的貢獻(xiàn)最大,最低位對圖像的貢獻(xiàn)最小,稱為最低比特位(Least Significant Bit
,LSB
)。
如果將一個圖像所有像素的比特位抽出來,就構(gòu)成了8
個不同的位平面,從LSB
(最低有效位0
)到MSB
(最高有效位7
)。位平面從低位到高位,圖像的特征逐漸變得復(fù)雜,細(xì)節(jié)不斷增加,相鄰比特的相關(guān)性也越強(qiáng)。而比特位越低包含的圖像信息就越少,最低位平面類似于隨機(jī)噪聲。因此,改變低位對圖像的成像質(zhì)量影響不大。
LSB
水印就是利用了這一點(diǎn),用水印信息替換載體圖像的最低比特位,這樣原圖像的7
個高位平面就與表示水印信息的最低位平面組成了新的圖像。
LSB
水印魯棒性(防攻擊性)較差,水印信息容易被抹去。
頻域水印
將數(shù)字圖像用一個矩陣來表示,是圖像的空間域表示方法,LSB
就是在圖像的空間域隱藏信息,魯棒性較差。而在圖像信號的頻域(變換域)中隱藏信息要比在空間域中隱藏信息具有更好的魯棒性。那么如何把圖像信號從空間域轉(zhuǎn)換到頻域呢?這里就需要用到大名鼎鼎的傅里葉變換
了。
法國數(shù)學(xué)家傅里葉大家一定不陌生,高數(shù)里就有傅里葉級數(shù)。
傅里葉提出的傅里葉變換(Fourier transform
)理論,表示能將滿足一定條件的某個函數(shù)表示成三角函數(shù)(正弦和/或余弦函數(shù))或者它們的積分的線性組合,可用于把信號從時間域(或空間域)變換到頻率域。
在此之前人們對信號的分析主要集中在空間域,傅里葉變換的提出為頻域分析奠定了基礎(chǔ),有助于解決許多圖像的問題。
傅里葉變換在數(shù)字圖像處理領(lǐng)域有著極為重要的應(yīng)用,圖像領(lǐng)域變換的實(shí)質(zhì)是把圖像函數(shù)展開成具有不同空間頻率的正、余弦信號的疊加,也就是說任何圖像都可以分解為若干個頻率不同的亮度呈正弦變化的圖像之和。把圖像從空間域變換到頻率域后,就能夠?qū)崿F(xiàn)對圖像數(shù)據(jù)進(jìn)行不同頻率成分的提取。對于圖像信號來說,可以把灰度(亮度)看做頻率,傅里葉變換可作為圖像灰度值形成的空間域與其頻率域的橋梁。
在頻域中隱藏信息就是傅里葉變換在數(shù)字圖像處理領(lǐng)域的一個典型應(yīng)用場景。通常多選擇在圖像頻域的中頻部分嵌入信息,因?yàn)楦哳l部分易于被各種信號處理方法破壞,而人的視覺又對低頻部分比較敏感,容易察覺低頻部分的變化。
在圖像頻域嵌入水印信息的大致流程為:把原始圖像通過離散傅里葉變換轉(zhuǎn)換到頻域(變換域),把水印文字信息混入,再經(jīng)過離散傅里葉變換的逆變換,便得到了載有水印信息的圖像。水印信息隱匿性較好。
光說不練假把式,操練起來。
下圖是我隨手拍的鵝廠北京總部大樓一角。
對上圖的一個通道進(jìn)行離散傅里葉變換,在其變換域(頻域)加入水印文字(fransli)后,再進(jìn)行離散傅里葉變換的逆變換,便得到了下圖。怎么樣,看不到水印信息吧?
對上圖進(jìn)行離散傅里葉變換,將圖片轉(zhuǎn)換到頻域(變換域),我們可以清楚的看到嵌入的水印文字(下圖)。
頻域盲水印具有比較好的防攻擊性,我們來測試一下。
我們截取圖像中的一部分并重新采樣,然后嘗試提取水印信息。
可以看到還是有很大概率可以提取到有效水印信息的。
Web 中的數(shù)字水印應(yīng)用
上面介紹了幾種常見的不可見水印(盲水印)實(shí)現(xiàn)方式,其中魯棒性比較好的是基于頻域的數(shù)字圖像盲水印,但這種水印主要是針對數(shù)字圖像,而 Web 上的內(nèi)容載體并不一定都是圖片,常見的需要加水印的載體除了圖片還有文本、表格等,這些場景該如何應(yīng)用頻域盲水印呢?
或許,Canvas
就是答案。
Reference
?????
-
《數(shù)字圖像隱寫分析》
-
《數(shù)字圖像處理原理與實(shí)踐》
-
《數(shù)據(jù)隱藏技術(shù)揭秘》