关于前端:从破解某设计网站谈前端水印详细教程

45次阅读

共计 10704 个字符,预计需要花费 27 分钟才能阅读完成。

从破解某定设计网站谈前端水印(具体教程)

前言

最近在写公众号的时候,经常会本人做首图,并且缓缓地发现沉迷于制作首图,感觉扁平化的设计的真好好看。缓缓地萌发了一个做一个属于本人的首图生成器的想法。

制作呢,当然也不是拍拍脑袋就开始,在开始之前,就去钻研了一下 某在线设计网站(如果有人不晓得的话,能够说一下,这是一个在线制作海报之类的网站 T T 像咱们这种内容创作者用的比拟多),毕竟人家曾经做了很久了,我只是想做个不便集体应用的。毕竟以上用 PS 做着还是有一些废工夫,因为组成的元素都很简略,做一个自动化生成的齐全能够。

然而钻研着钻研着,就看到了 某在线设计网站 的水印,像这种技术支持的网站,最重要的进攻措施就是水印了,水印可能很好的爱护知识产权。

缓缓地路就走偏了,开始对它的水印感兴趣了。不禁发现之前只是大略晓得水印的生成办法,然而素来没有认真钻研过,本文将以以下的路线进行解说。以下所有代码示例均在

https://github.com/hua1995116/node-demo/tree/master/watermark

明水印

水印(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 的形式实现的,所以能够利用这种计划。关上 https://www.gaoding.com/design?id=33931419&simple=1 (仅供举例学习)

关上控制台,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 门路下,办法都封装好了~。

然而理论过程须要更业余的加密形式,例如利用傅里叶变动公式,来进行频域制订数字盲水印,这里就不具体开展讲了,当前钻研完再具体讲~

破解实际

听完上述的介绍,那么某在线设计网站是不是很有可能应用了暗水印呢?

当然啦,通过我对某在线设计网站的剖析,我剖析了以下几种状况,咱们一一来进行测试。

咱们先通过收费下载的图片来进行剖析。关上 https://www.gaoding.com/design?id=13964513159025728&mode=user

通过试验(试验次要是去剖析他各个场景下触发的申请),发现在下载收费图片的时候,发现它都会去向阿里云发送一个 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暗水印进行的加密,解密后的样子为 ” 秋风 ” 字样,咱们别离来测试一下。

加一些元素

后果: 辨认成果不错

截图

后果: 辨认成果不错

大小变动

后果:辨认成果不错

加蒙层

后果:间接就拉胯了。

可见,暗水印的抵制攻击性还是蛮强的,是一种比拟好的抵挡攻打的形式~

最初

以上仅仅为技术交换~ 大家不要在理论的场景自觉应用,商业我的项目违规应用后果自负 ~ 或者期待一下我接下来想搞的这个集体收费首图生成器~ 喜爱文章的小伙伴能够点个赞哦 ~ 欢送关注公众号 秋风的笔记,学习前端不迷路。

参考

https://imm.console.aliyun.com/cn-shenzhen/project?accounttraceid=1280c6af416744a38e9acf63c4e0878cjdet

https://help.aliyun.com/document_detail/138800.html?spm=a2c4g.11186623.6.656.3bd46bb4oglhEr

https://oss.console.aliyun.com/bucket/oss-cn-shenzhen/watermark-shenzheng/object?path=dist%2F

https://juejin.cn/post/6844903650054111246

https://www.zhihu.com/question/50677827/answer/122388524

https://www.zhihu.com/question/50735753

最初

如果我的文章有帮忙到你,心愿你也能帮忙我,欢送关注我的微信公众号 秋风的笔记 ,回复 好友 二次,可加微信并且退出交换群, 秋风的笔记 将始终陪伴你的左右。

正文完
 0