1. 程式人生 > 實用技巧 >前端也能玩的圖片隱寫術

前端也能玩的圖片隱寫術

不能說的祕密——前端也能玩的圖片隱寫術

上個月在千里碼刷題的時候,碰到了比較有意思的一道題—— 隱寫術。既然感覺有意思,又很久沒有玩過 canvas,所以今天結合這兩塊內容帶大家探索一下。

隱寫術算是一種加密技術,權威的 wiki 說法是“ 隱寫術是一門關於資訊隱藏的技巧與科學,所謂資訊隱藏指的是不讓除預期的接收者之外的任何人知曉資訊的傳遞事件或者資訊的內容。” 這看似高大上的定義,並不是近代新誕生的技術,早在 13 世紀末德國人 Trithemius 就寫出了《隱寫術》的著作,學過密碼學的同學可能知道。好了,說了這麼多,隱寫術到底是什麼技術,讓我們看一個例子。

下面是一張看似普通的圖片,但其中卻藏有另一個肉眼無法識別的影象哦。

這是如果把上圖每個色彩空間和數字 3 進行邏輯與運算,再把亮度增強 85 倍,可以得到下圖。

簡單的說,上述的處理過程可以理解為對圖片畫素的處理,也就是說,加密的資訊散佈在每個畫素點上。可是,13 世紀還沒有“ 畫素” 這個概念吧?!沒錯,上面這個例子只是隱寫術的一個現代技術實現,隱藏資訊的手段有很多,我們日常的鈔票防偽也算是隱寫術的一種,所以標題上也限定了我們的討論範圍—— 圖片隱寫術。

(電子水印與隱寫術有一些共通點)

聚焦到載體為圖片的隱寫術,一起來從前端角度分析其技術原理。

我們知道圖片的畫素資訊裡儲存著 RGB 的色值,R、G、B 分別為該畫素的紅、綠、藍通道,每個通道的分量值範圍在 0~255,16 進位制則是 00~FF。在 CSS 中經常使用其 16 進位制形式,比如指定部落格頭部背景色為 #A9D5F4。其中 R(紅色)的 16 進位制值為 A9,換算成十進位制為 169。這時候,對 R 分量的值+1,即為 170,整個畫素 RGB 值為 #AAD5F4,別說你看不出差別,就連火眼金金的“ 畫素眼” 設計師都察覺不出來呢。於此同時,修改 G、B 的分量值,也是我們無法察覺的。因此可以得出重要結論:RGB 分量值的小量變動,是肉眼無法分辨的,不影響對圖片的識別。

有了這個結論,那就給我們了利用空間,常用手段的就是對二進位制最低位進行操作,下面就用 canvas 來演示一下。

解開圖中的祕密

這是一張我們當家美女小蘭師姐的照片,為了讓例子足夠簡單,裡面的 R 通道分量被我加入了文字資訊,想知道其中的資訊,可以跟我用 canvas 程式碼來解開。

首先在頁面加入一個 canvas 標籤,並獲取到其上下文。

1 <canvas id="canvas"width="256"height="256"></canvas>

1 varctx=document.getElementById('canvas').getContext('2d');

接著將圖片先繪製在畫布上,然後獲取其畫素資料。

1 2 3 4 5 6 7 8 9 varimg=newImage(); varoriginalData; img.onload=function(){ ctx.drawImage(img,0,0); // 獲取指定區域的canvas畫素資訊 originalData=ctx.getImageData(0,0,ctx.canvas.width,ctx.canvas.height); console.log(originalData); }; img.src='xiaolan.png';

打印出資料,會看到有一個非常大的陣列。

這個一維陣列儲存了所有的畫素資訊,一共有 256 * 256 * 4 = 262144 個值。其中 4 個值一組,為什麼呢?在瀏覽器中解析圖片,除了 RGB 值外,每組第 4 個值為透明度值,即畫素資訊實際為大家熟知的 rgba 值。

這裡的解密規則是對 R 通道進行處理,R 的分量最低位為 1 則該畫素設為紅色,R 的分量最低位為 0 則該畫素設為黑色,直接看程式碼實現,完成後我們再繪製到 canvas,即可看到結果。

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 varprocessData=function(originalData){ vardata=originalData.data; for(vari=0;i<data.length;i++){ if(i%4==0){ // 紅色分量 if(data[i]%2==0){ data[i]=0; }else{ data[i]=255; } }elseif(i%4==3){ // alpha通道不做處理 continue; }else{ // 關閉其他分量,不關閉也不影響答案,甚至更美觀 o(^▽^)o data[i]=0; } } // 將結果繪製到畫布 ctx.putImageData(originalData,0,0); }

在 img onload 事件中呼叫 processData 方法,就可以看到結果啦。

得到的結果可能是這個樣子的。

在圖片中隱藏資訊

講了基礎的解密過程,再來反向說說加密過程。

既然要在圖片中加入文字資訊,那麼首先要獲取文字的畫素資訊,這裡我先用 canvas 在畫布上列印文字,獲取畫素資訊。

1 2 3 4 5 vartextData; // 這些canvas API,好久沒用,需要查API文件了T_T ctx.font='30px Microsoft Yahei'; ctx.fillText('廣告位招租u',60,130); textData=ctx.getImageData(0,0,ctx.canvas.width,ctx.canvas.height).data;

先儲存文字的畫素資訊,接著載入圖片獲取其畫素資訊,然後對兩組畫素進行處理,我在這裡抽離了一個公共方法。

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 varmergeData=function(newData,color){ varoData=originalData.data; varbit,offset;// offset的作用是找到alpha通道值,這裡需要大家自己動動腦筋 switch(color){ case'R': bit=0; offset=3; break; case'G': bit=1; offset=2; break; case'B': bit=2; offset=1; break; } for(vari=0;i<oData.length;i++){ if(i%4==bit){ // 只處理目標通道 if(newData[i+offset]===0&&(oData[i]%2===1)){ // 沒有資訊的畫素,該通道最低位置0,但不要越界 if(oData[i]===255){ oData[i]--; }else{ oData[i]++; } }elseif(newData[i+offset]!==0&&(oData[i]%2===0)){ // // 有資訊的畫素,該通道最低位置1,可以想想上面的斑點效果是怎麼實現的 if(oData[i]===255){ oData[i]--; }else{ oData[i]++; } } } } ctx.putImageData(originalData,0,0); }

上述程式碼做的是,接受要隱藏的資料以及隱藏的顏色通道,然後對原圖進行操作,修改圖片該通道分量的最低位,如果有文字資訊,則最低位置為 1,否則為 0。從最文章開頭的結論知道,RGB 的三個通道可以分別隱藏不同資訊。

在 img.onload 中呼叫 mergeData(textData, 'R'),處理好影象後,只要在瀏覽器中的 canvas 上右鍵儲存圖片即可。

這裡的例子比較簡單,只展示了基本的最低位隱藏文字資訊,像二維碼這些簡單圖形也可以這麼處理。現實中隱藏畫中畫則需要更專業的影象處理演算法,這裡就不再展開了。

應用價值

圖片隱寫術的應用價值很廣泛,比如程式設計師之間的表白(不限男女),不失為一種浪漫的方式~

上面的案例中我沒有放出師姐的原片,這意味著如果盜用上面的圖片,我是有辦法識別出來的,起到了簡單的一種簽名作用。當然你也有辦法消除掉裡面的資訊,而前提是你需要知道我的加密方式,可是實際應用中絕不會這麼簡單哦。有個成功案例就是大眾點評通過這種方式,成功證明食神 app 對其圖片的盜用,為自己的合法權益進行了有效維護。

好的,感謝閱讀到最後,作為回報,我將福利隱藏在了師姐的圖片中,請自行發現吧~

出處:http://www.alloyteam.com/2016/03/image-steganography/

=======================================================================================

從破解某設計網站談前端水印(詳細教程)

前言

最近在寫公眾號的時候,常常會自己做首圖,並且慢慢地發現沉迷於製作首圖,感覺扁平化的設計的真好好看。慢慢地萌生了一個做一個屬於自己的首圖生成器的想法。

製作呢,當然也不是拍拍腦袋就開始,在開始之前,就去研究了一下某線上設計網站(如果有人不知道的話,可以說一下,這是一個線上製作海報之類的網站 T T 像我們這種內容創作者用的比較多),畢竟人家已經做了很久了,我只是想做個方便個人使用的。畢竟以上用 PS 做著還是有一些廢時間,由於組成的元素都很簡單,做一個自動化生成的完全可以。

但是研究著研究著,就看到了某線上設計網站的水印,像這種技術支援的網站,最重要的防禦措施就是水印了,水印能夠很好的保護智慧財產權。

慢慢地路就走偏了,開始對它的水印感興趣了。不禁發現之前只是大概知道水印的生成方法,但是從來沒有仔細研究過,本文將以以下的路線進行講解。以下所有程式碼示例均在

github.com/hua1995116/…

明水印

水印(watermark)是一種容易識別、被夾於內,能夠透過光線穿過從而顯現出各種不同陰影的技術。

水印的型別有很多,有一些是整圖覆蓋在圖層上的水印,還有一些是在角落。

那麼這個水印怎麼實現呢?熟悉 PS 的朋友,都知道 PS 有個叫做圖層的概念。

網頁也是如此。我們可以通過絕對定位,來將水印覆蓋到我們的頁面之上。

最終變成了這個樣子。

等等,但是發現少了點什麼。直接覆蓋上去,就好像是一個蒙層,我都知道這樣是無法觸發底下圖層的事件的,此時就要介紹一個css屬性pointer-events

pointer-events CSS 屬性指定在什麼情況下 (如果有) 某個特定的圖形元素可以成為滑鼠事件的 target

當它的被設定為 none 的時候,能讓元素實體虛化,雖然存在這個元素,但是該元素不會觸發滑鼠事件。詳情可以檢視 CSS3 pointer-events:none應用舉例及擴充套件 « 張鑫旭-鑫空間-鑫生活

這下理清了實現原理,等於成功了一半了!

明水印的生成

明水印的生成方式主要可以歸為兩類,一種是 純 html 元素(純div),另一種則為背景圖(canvas/svg)。

下面我分別來介紹一下,兩種方式。

div實現

我們首先來講比較簡單的 div 生成的方式。就按照我們剛才說的。

// 文字內容
<div class="app">
        <h1>秋風</h1>
        <p>hello</p>
</div>
複製程式碼

首先我們來生成一個水印塊,就是上面的 一個個秋風的筆記。這裡主要有一點就是設定一個透明度(為了讓水印看起來不是那麼明顯,從而不遮擋我們的主要頁面),另一個就是一個旋轉,如果是正的水平會顯得不是那麼好看,最後一點就是使用 userSelect 屬性,讓此時的文字無法被選中。

userSelect

CSS 屬性 user-select 控制使用者能否選中文字。除了文字框內,它對被載入為 chrome 的內容沒有影響。

function cssHelper(el, prototype) {
  for (let i in prototype) {
    el.style[i] = prototype[i]
  }
}
const item = document.createElement('div')
item.innerHTML = '秋風的筆記'
cssHelper(item, {
  position: 'absolute',
  top: `50px`,
  left: `50px`,
  fontSize: `16px`,
  color: '#000',
  lineHeight: 1.5,
  opacity: 0.1,
  transform: `rotate(-15deg)`,
  transformOrigin: '0 0',
  userSelect: 'none',
  whiteSpace: 'nowrap',
  overflow: 'hidden',
})
複製程式碼

有了一個水印片,我們就可以通過計算螢幕的寬高,以及水印的大小來計算我們需要生成的水印個數。

const waterHeight = 100;
const waterWidth = 180;
const { clientWidth, clientHeight } = document.documentElement || document.body;
const column = Math.ceil(clientWidth / waterWidth);
const rows = Math.ceil(clientHeight / waterHeight);
for (let i = 0; i < column * rows; i++) {
    const wrap = document.createElement('div');
    cssHelper(wrap, Object.create({
        position: 'relative',
        width: `${waterWidth}px`,
        height: `${waterHeight}px`,
        flex: `0 0 ${waterWidth}px`,
        overflow: 'hidden',
    }));
    wrap.appendChild(createItem());
    waterWrapper.appendChild(wrap)
}
document.body.appendChild(waterWrapper)
複製程式碼

這樣子我們就完美地實現了上面我們給出的思路的樣子啦。

背景圖實現

canvas

canvas的實現很簡單,主要是利用canvas 繪製一個水印,然後將它轉化為 base64 的圖片,通過canvas.toDataURL() 來拿到檔案流的 url ,關於檔案流相關轉化可以參考我之前寫的文章一文帶你層層解鎖「檔案下載」的奧祕, 然後將獲取的 url 填充在一個元素的背景中,然後我們設定背景圖片的屬性為重複。

.watermark {
    position: fixed;
    top: 0px;
    right: 0px;
    bottom: 0px;
    left: 0px;
    pointer-events: none;
    background-repeat: repeat;
}
複製程式碼
function createWaterMark() {
  const angle = -20;
  const txt = '秋風的筆記'
  const canvas = document.createElement('canvas');
  canvas.width = 180;
  canvas.height = 100;
  const ctx = canvas.getContext('2d');
  ctx.clearRect(0, 0, 180, 100);
  ctx.fillStyle = '#000';
  ctx.globalAlpha = 0.1;
  ctx.font = `16px serif`
  ctx.rotate(Math.PI / 180 * angle);
  ctx.fillText(txt, 0, 50);
  return canvas.toDataURL();
}
const watermakr = document.createElement('div');
watermakr.className = 'watermark';
watermakr.style.backgroundImage = `url(${createWaterMark()})`
document.body.appendChild(watermakr);
複製程式碼

svg

svg 和 canvas 類似,主要還是生成背景圖片。

function createWaterMark() {
  const svgStr =
    `<svg xmlns="http://www.w3.org/2000/svg" width="180px" height="100px">
      <text x="0px" y="30px" dy="16px"
      text-anchor="start"
      stroke="#000"
      stroke-opacity="0.1"
      fill="none"
      transform="rotate(-20)"
      font-weight="100"
      font-size="16"
      >
      	秋風的筆記
      </text>
    </svg>`;
  return `data:image/svg+xml;base64,${window.btoa(unescape(encodeURIComponent(svgStr)))}`;
}
const watermakr = document.createElement('div');
watermakr.className = 'watermark';
watermakr.style.backgroundImage = `url(${createWaterMark()})`
document.body.appendChild(watermakr);
複製程式碼

明水印的破解一

以上就很快實現了水印的幾種方案。但是對於有心之人來說,肯定會想著破解,以上破解也很簡單。

打開了 Chrome Devtools 找到對應的元素,直接按 delete 即可刪除。

明水印的防禦

這樣子的水印對於大概知道控制檯操作的小白就可以輕鬆破解,那麼有什麼辦法能防禦住這樣的操作呢?

答案是肯定的,js 有一個方法叫做 MutationObserver,能夠監控元素的改動。

MutationObserver 對現代瀏覽的相容性還是不錯的,MutationObserver是元素觀察器,字面上就可以理解這是用來觀察Node(節點)變化的。MutationObserver是在DOM4規範中定義的,它的前身是MutationEvent事件,最低支援版本為 ie9 ,目前已經被棄用。

在這裡我們主要觀察的有三點

  • 水印元素本身是否被移除
  • 水印元素屬性是否被篡改(display: none ...)
  • 水印元素的子元素是否被移除和篡改 (element生成的方式 )

來通過 MDN 檢視該方法的使用示例。

const targetNode = document.getElementById('some-id');

// 觀察器的配置(需要觀察什麼變動)
const config = { attributes: true, childList: true, subtree: true };

// 當觀察到變動時執行的回撥函式
const callback = function(mutationsList, observer) {
    // Use traditional 'for loops' for IE 11
    for(let mutation of mutationsList) {
        if (mutation.type === 'childList') {
            console.log('A child node has been added or removed.');
        }
        else if (mutation.type === 'attributes') {
            console.log('The ' + mutation.attributeName + ' attribute was modified.');
        }
    }
};

// 建立一個觀察器例項並傳入回撥函式
const observer = new MutationObserver(callback);

// 以上述配置開始觀察目標節點
observer.observe(targetNode, config);
複製程式碼

MutationObserver主要是監聽子元素的改動,因此我們的監聽物件為 document.body, 一旦監聽到我們的水印元素被刪除,或者屬性修改,我們就重新生成一個。通過以上示例,加上我們的思路,很快我們就寫一個監聽刪除元素的示例。(監聽屬性修改也是類似就不一一展示了)

// 觀察器的配置(需要觀察什麼變動)
const config = { attributes: true, childList: true, subtree: true };
// 當觀察到變動時執行的回撥函式
const callback = function (mutationsList, observer) {
// Use traditional 'for loops' for IE 11
  for (let mutation of mutationsList) {
    mutation.removedNodes.forEach(function (item) {
      if (item === watermakr) {
      	document.body.appendChild(watermakr);
      }
    });
  }
};
// 監聽元素
const targetNode = document.body;
// 建立一個觀察器例項並傳入回撥函式
const observer = new MutationObserver(callback);
// 以上述配置開始觀察目標節點
observer.observe(targetNode, config);
複製程式碼

我們開啟控制檯來檢驗一下。

這回完美了,能夠完美抵禦一些開發小白了。

那麼這樣就萬無一失了嗎?顯然,道高一尺魔高一丈,畢竟前端的一切都是不安全的。

明水印的破解二

在一個高階前端工程師面前,一切都是紙老虎。接下來我就隨便介紹三種破解的方式。

第一種

開啟 Chrome Devtools,點選設定 - Debugger - Disabled JavaScript .

然後再開啟頁面,delete我們的水印元素。

第二種

複製一個 body 元素,然後將原來 body 元素的刪除。

第三種

開啟一個代理工具,例如 charles,將生成水印相關的程式碼刪除。

破解實踐

接下來我們實戰一下,通過預先分析,我們看到某設計網站的內容是以 div 的方式實現的,所以可以利用這種方案。

開啟控制檯,Ctrl + F 搜尋 watermark 相關字眼。(這一步是作為一個程式設計師的直覺,基本上你要找什麼,搜尋對應的英文就可以 ~)

很快我們就找到了水印圖。發現直接刪除,沒有辦法刪除水印元素,根據我們剛才學習的,肯定是利用了MutationObserver 方法。我們使用我們的第一個破解方法,將 JavaScript 禁用,再將元素刪除。

水印已經消失了。

但是這樣真的就萬事大吉了嗎?

不知道你有沒有聽過一種東西,看不見摸不著,但是它卻真實存在,他的名字叫做暗水印,我們將時間倒流到 16 年間的月餅門事件,因為有員工將內網站點截圖了,但是很快被定位出是誰截圖了。

雖然你將一些可見的水印去除了,但是還會存在一些不可見的保護版權的水印。(這就是防止一些壞人拿去作另外的用途)

暗水印

暗水印是一種肉眼不可見的水印方式,可以保持圖片美觀的同時,保護你的資源版權。

暗水印的生成方式有很多,常見的為通過修改RGB 分量值的小量變動、DWT、DCT 和 FFT 等等方法。

通過介紹前端實現 RGB 分量值的小量變動 來揭祕其中的奧祕,主要參考 不能說的祕密——前端也能玩的圖片隱寫術 | AlloyTeam

我們都知道圖片都是有一個個畫素點構成的,每個畫素點都是由 RGB 三種元素構成。當我們把其中的一個分量修改,人的肉眼是很難看出其中的變化,甚至是畫素眼的設計師也很難分辨出。

你能看出其中的差別嗎?根據這個原理,我們就來實踐吧。(女孩子可以掌握方法後可以拿以下圖片進行試驗測試)

首先拿到以上圖片,我們先來講解解碼方式,解碼其實很簡單,我們需要建立一個規律,再通過我們的規律去解碼。現在假設的規律為,我們將所有畫素的 R 通道的值為奇數的時候我們建立的通道密碼,舉個簡單的例子。

例如我們把以上當做是一個圖形,加入他要和一箇中文的 "一" 放進影象,例如我們將 "一" 放入第二行。按照我們的演算法,我們的影象會變成這個樣子。

解碼的時候,我們拿到所有的奇數畫素將它渲染出來,例如這裡的 '5779' 是不是正好是一個 "一",下面就轉化為實踐。

解碼過程

首先建立一個 canvas 標籤。

 <canvas id="canvas" width="256" height="256"></canvas>
複製程式碼
var ctx = document.getElementById('canvas').getContext('2d');
var img = new Image();
var originalData;
img.onload = function () {
  // canvas畫素資訊
  ctx.drawImage(img, 0, 0);
  originalData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height);
  console.log()
  processData(ctx, originalData)
};
img.src = 'qiufeng-super.png';
複製程式碼

我們打印出這個陣列,會有一個非常大的陣列,一共有 256 * 256 * 4 = 262144 個值。因為每個畫素除了 RGB 外還有一個 alpha 通道,也就是我們常用的透明度。

上面也說了,我們的 R 通道為奇數的時候 ,就我們的解密密碼。因此我們只需要所有的畫素點的 R 通道為奇數的時候,將它填填充,不為奇數的時候就不填充,很快我們就能得到我們的隱藏影象。

var processData = function (ctx, originalData) {
    var data = originalData.data;
    for (var i = 0; i < data.length; i++) {
        if (i % 4 == 0) {
            // R分量
            if (data[i] % 2 == 0) {
                data[i] = 0;
            } else {
                data[i] = 255;
            }
        } else if (i % 4 == 3) {
            // alpha通道不做處理
            continue;
        } else {
            // 關閉其他分量,不關閉也不影響答案
            data[i] = 0;
        }
    }
    // 將結果繪製到畫布
    ctx.putImageData(originalData, 0, 0);
}
processData(ctx, originalData)
複製程式碼

解密完會出現類似於以下這個樣子。

那我們如何加密的,那就相反的方式就可以啦。(這裡都用了 不能說的祕密——前端也能玩的圖片隱寫術 中的例子,= = 我也能寫出一個例子,但是覺得沒必要,別人已經寫得很好了,我們只是講述這個方法,需要程式碼來舉例而已)

編碼過程

加密呢,首先我們需要獲取加密的影象資訊。

var textData;
var ctx = document.getElementById('canvas').getContext('2d');
ctx.font = '30px Microsoft Yahei';
ctx.fillText('秋風的筆記', 60, 130);
textData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height).data;
複製程式碼

然後提取加密資訊在待加密的圖片上進行處理。

var mergeData = function (ctx, newData, color, originalData) {
    var oData = originalData.data;
    var bit, offset;  // offset的作用是找到alpha通道值,這裡需要大家自己動動腦筋

    switch (color) {
        case 'R':
            bit = 0;
            offset = 3;
            break;
        case 'G':
            bit = 1;
            offset = 2;
            break;
        case 'B':
            bit = 2;
            offset = 1;
            break;
    }

    for (var i = 0; i < oData.length; i++) {
        if (i % 4 == bit) {
            // 只處理目標通道
            if (newData[i + offset] === 0 && (oData[i] % 2 === 1)) {
                // 沒有資訊的畫素,該通道最低位置0,但不要越界
                if (oData[i] === 255) {
                    oData[i]--;
                } else {
                    oData[i]++;
                }
            } else if (newData[i + offset] !== 0 && (oData[i] % 2 === 0)) {
                // // 有資訊的畫素,該通道最低位置1,可以想想上面的斑點效果是怎麼實現的
                oData[i]++;
            }
        }
    }
    ctx.putImageData(originalData, 0, 0);
}
複製程式碼

主要的思路還是我一開始所講的,在有畫素資訊的點,將 R 偶數的通道+1。在沒有畫素點的地方將 R 通道轉化成偶數,最後在 img.onload 呼叫 processData(ctx, originalData)

img.onload = function () {
  // 獲取指定區域的canvas畫素資訊
  ctx.drawImage(img, 0, 0);
  originalData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height);
  console.log(originalData)
	processData(ctx, originalData)
};
複製程式碼

以上方法就是一種比較簡單的加密方式。以上程式碼都放到了倉庫 watermark/demo/canvas-dark-watermark.html 路徑下,方法都封裝好了~。

但是實際過程需要更專業的加密方式,例如利用傅立葉變化公式,來進行頻域制定數字盲水印,這裡就不詳細展開講了,以後研究完再詳細講~

破解實踐

聽完上述的介紹,那麼某設計網站是不是很有可能使用了暗水印呢?

當然啦,通過我對某設計網站的分析,我分析了以下幾種情況,我們一一來進行測試。

我們先通過免費下載的圖片來進行分析。開啟 www.xxx.com/design?id=1…

通過實驗(實驗主要是去分析他各個場景下觸發的請求),發現在下載免費圖片的時候,發現它都會去向阿里雲傳送一個 POST 請求,這熟悉的請求域名以及熟悉的資料封裝方式,這不就是 阿里雲 OSS 客戶端上傳方式嘛。這就好辦了,我們去查詢一下阿里雲是否有生成暗水印的相關方式,從而來看看某設計網站是否含有暗水印。很快我們就從官方文件搜尋到了相關的文件,且對於低 QPS 是免費的。(這就是最好理解的連帶效應,例如我們覺得耐克阿迪啥賣運動類服飾,你買了他的鞋子,可能還會想買他的衣服)

const { RPCClient } = require("@alicloud/pop-core");
var client = new RPCClient({
  endpoint: "http://imm.cn-shenzhen.aliyuncs.com",
  accessKeyId: 'xxx',
  accessKeySecret: 'xxx',
  apiVersion: "2017-09-06",
});
(async () => {
  try {
        var params = {
          Project: "test-project",
          ImageUri: "oss://watermark-shenzheng/source/20201009-182331-fd5a.png",
            TargetUri: "oss://watermark-shenzheng/dist/20201009-182331-fd5a-out.jpg",
            Model: "DWT"
        };
        var result = await client.request("DecodeBlindWatermark", params);
        
        console.log(result);
      } catch (err) {
        console.log(err);
      }
})()
複製程式碼

我們寫了一個demo進行了測試。由於阿里雲含有多種暗水印加密方式,為啥我使用了 DWT 呢?因為其他幾種都需要原圖,而我們剛才的測試,他上傳只會上傳一個檔案到 OSS ,因此大致上排除了需要原圖的方案。

但是我們的結果卻沒有發現任何加密的跡象。

為什麼我們會去猜想阿里雲的圖片暗水印的方式?因為從上傳的角度來考慮,我們上傳的圖片 key 的地址即是我們下載的圖片,也就是現在存在兩種情況,一就是通過阿里雲的盲水印方案,另一種就是上傳前進行了水印的植入。現在看來不是阿里雲水印的方案,那麼只是能是上傳前就有了水印。

這個過程就有兩種情況,一是生成的過程中加入的水印,前端加入的水印。二是物料圖含有水印。

對於第一種情況,我們可以通過 dom-to-image 這個庫,在前端直接進行下載,或者使用截圖的方式。目前通過直接下載和通過站點內生成,發現元素略有不同。

第一個為我通過 dom-to-image 的方式下載,第二種為站點內下載,明顯大了一些。(有點懷疑他在圖片生成中可能做了什麼手腳)

但是感覺前端加密的方式比較容易破解,最壞的情況想到了對素材進行了加密,但是這樣的話就無從破解了(但是查閱了一些資料,由於某設計稿網站站點素材大多是透明背景的,這種加密效果可能會弱一些,以後牛逼了再來補充)。目前這一塊暫時還不清楚,探究止步於此了。

攻擊實驗

那如果一張圖經過暗水印加密,他的抵抗攻擊性又是如何呢?

這是一張通過阿里雲 DWT暗水印進行的加密,解密後的樣子為"秋風"字樣,我們分別來測試一下。

加一些元素

結果: 識別效果不錯

截圖

結果: 識別效果不錯

大小變化

結果:識別效果不錯

加蒙層

結果: 直接就拉胯了。

可見,暗水印的抵抗攻擊性還是蠻強的,是一種比較好的抵禦攻擊的方式~

最後

以上僅僅為技術交流~ 大家不要在實際的場景盲目使用,使用正規的途徑 ~ 或者期待一下我接下來想搞的這個個人免費首圖生成器~ 喜歡文章的小夥伴可以點個贊哦 ~ 歡迎關注公眾號 秋風的筆記 ,學習前端不迷路。

參考

imm.console.aliyun.com/cn-shenzhen…

help.aliyun.com/document_de…

oss.console.aliyun.com/bucket/oss-…

juejin.cn/post/684490…

www.zhihu.com/question/50…

www.zhihu.com/question/50…

出處:https://juejin.cn/post/6900713052270755847