今天在跟同事朋友聊这个脉脉上的一些事,我就突然想到之前很多人喜欢截图各种APP页面发一些比较敏感的内容出来,于是就写了这篇文章
从今年看到这个新闻开始,我就开始警觉了。
前端有一种东西叫水印
,特别当你浏览一些内部系统的时候,大部分都会有,例如现在的企业微信,担心你截图发出去内部的重要信息。
小伙伴们会发现,水印
大部分都是我们使用者可以看见的,例如企业微信的,这种我们叫明水印
可是还有一种,叫暗水印
。
就是你肉眼一眼看不到的水印,不影响你的浏览页面内容,无感知的把水印加进去。
这样一些敏感的内容,一旦泄露出去,就能通过网上的图片,解码找出来是谁的身份泄漏出去,而且往往一般的泄漏者不知道原来会有这个东西
所以有人会出现,截个图发给朋友,然后网上疯传,最后被定位到是他发的图片。这就是暗水印的一种使用
所以大家不要乱截图APP和各种页面,特别是比较敏感的内容
使用canvas生成,然后使用backgroud-repea至需要展示水印的区域~
function __canvasWM({
container = document.body,
width = '250px',
height = '200px',
textAlign = 'center',
textBaseline = 'middle',
font = '15px microsoft yahei',
fillStyle = 'rgba(184, 184, 184, 0.8)',
content = '请勿外传',
rotate = '30',
}) {
const canvas = document.createElement('canvas');
canvas.setAttribute('width', width);
canvas.setAttribute('height', height);
const ctx = canvas.getContext('2d');
ctx.textAlign = textAlign;
ctx.textBaseline = textBaseline;
ctx.font = font;
ctx.fillStyle = fillStyle;
ctx.rotate((Math.PI / 180) * rotate);
ctx.fillText(content, parseFloat(width) / 2, parseFloat(height) / 2);
const base = canvas.toDataURL();
const watermarkDiv = document.createElement('div');
watermarkDiv.setAttribute(
'style',
`
position:fixed;
top:0;
left:0;
width:100%;
height:100%;
z-index:${9999999999};
pointer-events:none;
background-repeat:repeat;
background-image:url('${base}');
color:grey;
opacity: 0.5`
);
container.style.position = 'relative';
container.insertBefore(watermarkDiv, container.firstChild);
}
容易被找到对应的dom元素然后delete,我们有两种方式防止被删除:
1.setInterval去不断检测水印,如果不在了就继续生成
2.使用MutationObserver监测dom的变化,如果水印的dom节点变化了,就要继续生成
但是前端安全是一个比较麻烦的事,还是可以在控制台禁用javascript来做到去除的目的。
明水印肯定还有其他实现和防止去除的手段,安全是一个很深的知识点,这里点到为止
同样,暗水印的实现,是多种的。我看过网上就有很多种,这里简单举两个例子
例如:https://juejin.cn/post/7023712210899697671这篇文章里面提到的
即在页面最顶部加上透明蒙层,蒙层携带文字信息,文字颜色也为透明,截图后文字信息肉眼不可见,通过ps等图片处理工具处理截图,才会将文字信息展示出来
通过PS设置,将看不见的水印内容展示出来
还有一种是业内通用的方案:
图片是由多个像素点组成的,而每个像素点轻微的变化,肉眼是无法辨别的
网上挺多这种文章的,我就从https://juejin.cn/post/7064201037321601032搞点代码过来了。时间原因,不自己写了
代码:
function createBackgroundImage(content, proportion, tiltAngle) {
const can = document.createElement("canvas");
can.width = document.body.clientWidth / proportion;
can.height = document.body.clientHeight / proportion;
const context = can.getContext("2d");
context.rotate((-25 * Math.PI) / 180);
context.font = "800 30px Microsoft JhengHei";
context.fillStyle = "#000";
context.textAlign = "center";
context.textBaseline = "Middle";
context.fillText(content, 100, 100);
console.log(context.getImageData(0, 0, can.width, can.height));
return can.toDataURL("image/png");
}
const div = document.getElementById("root");
div.style.backgroundImage = `url(${createBackgroundImage(
"前端巅峰",
6,
10
)})`;
效果:
其实这个效果背后是,root节点给了一个背景图:
那么我们获取下这个背景图的信息看看
console.log((createBackgroundImage("前端巅峰", 6, 10)));
对于 ImageData 对象中的每个像素,都存在着四方面的信息,即 RGBA 值:
red=imgData.data[0];
green=imgData.data[1];
blue=imgData.data[2];
alpha=imgData.data[3];
Uint8ClampedArray则是对应的像素点,每四个表征一个像素点,然后从左往右,从上往下的顺序进行排列
我们只需要把水印内容塞进图片中即可
function getImageData(image) {
const img = new Image();
img.src = image;
const myCanvas = document.createElement("canvas");
myCanvas.width = img.width;
myCanvas.height = img.height;
const myContext = myCanvas.getContext("2d");
myContext.drawImage(img, 0, 0);
return myContext.getImageData(0, 0, myCanvas.width, myCanvas.height);
}
function mergeData(rawImageSrc, watermarkImageSrc) {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = function () {
const myCanvas = document.createElement("canvas");
myCanvas.width = img.width;
myCanvas.height = img.height;
const ctx = myCanvas.getContext("2d");
const bit = 0;
const offset = 3;
const oImageData = getImageData(rawImageSrc);
const oData = oImageData.data;
const newData = getImageData(watermarkImageSrc).data;
for (let i = 0; i < oData.length; i++) {
if (i % 4 === bit) {
// 只修改目标通道
if (newData[i + offset] === 0 && oData[i] % 2 === 1) {
// 没有信息的像素,将目标通道的奇数像素改为偶数
if (oData[i] === 255) {
oData[i]--;
} else {
oData[i]++;
}
} else if (newData[i + offset] !== 0 && oData[i] % 2 === 0) {
// 有信息的像素
oData[i]++;
}
}
}
ctx.putImageData(oImageData, 0, 0);
resolve(myCanvas.toDataURL("image/png"));
};
img.src = rawImageSrc;
});
}
然后把水印内容从图片抽取出来:
function decrypt(watermarkImage) {
return new Promise((resolve, reject) => {
const img = new Image()
img.onload = function () {
const myCanvas = document.createElement("canvas");
myCanvas.width = img.width;
myCanvas.height = img.height;
const ctx = myCanvas.getContext("2d")
const imageData = getImageData(watermarkImage)
var data = imageData.data;
for (var i = 0; i < data.length; i++) {
if (i % 4 == 0) {
// 红色分量
if (data[i] % 2 == 0) {
data[i] = 0;
} else {
data[i] = 255;
}
} else if (i % 4 == 3) {
// alpha通道不做处理
continue;
} else {
// 关闭其他分量,不关闭也不影响答案,甚至更美观 o(^▽^)o
data[i] = 0;
}
}
ctx.putImageData(imageData, 0, 0)
resolve(myCanvas.toDataURL("image/png"))
}
img.src = watermarkImage
})
}
最终使用:
const image1 = createBackgroundImage('前端巅峰', 3, 10)
const image2 = createBackgroundImage('Peter666', 3, 10)
mergeData(image1, image2).then(res => {
console.log('res', res)
decrypt(image2).then(res => {
console.log('finalImage', res)
})
})
我使用webContainer技术的给大家生成了一个demo
项目源代码链接:https://stackblitz.com/edit/react-a9ehdm?file=src/App.js
预览链接(打开控制台可以看到效果):https://react-a9ehdm.stackblitz.io
stackblitz上面可能会有报错, 大家可以把代码搞到本地跑起来
所以大家没事不要乱截图,如果是敏感内容,暗水印实现方式很多种方式,总有一种你不知道的,会找到你。
Copyright© 2013-2020
All Rights Reserved 京ICP备2023019179号-8