数字水印实践总结

一、写在前面

水印分为明水印和暗水印,明水印即肉眼可见,暗水印则是将水印透明度降低到肉眼不可识别范围,如果有暗水印当有用户将图片或截图转发到其他网络平台,这时对图片进行一些操作可以水印显示出来,从而追踪到泄露消息的人,起到保护信息安全作用。ImageMagick/graphicsmagick/gm已多年不维护。 本文主要梳理数字水印的相关介绍与实现方式,并添附代码片段可开箱即用,业务中根据实际需求场景参考。

二、关于数字水印

包括明暗水印、融合水印、浮雕水印等。是一种信息保护技术,利用人体感官的限制将数字信号,如图像、文字、符号、数字等可以作为标记、标识的信息与原始数据(如图像、文档、音频、视频)紧密结合并隐藏或显示其中,且可以经过一些不破坏源数据的操作保存下来,从而保护信息。

  • 如果业务需求是给图片添加通用水印,可在node环境中基于Images生成。
  • 如果业务安全要求较高的图像素材则考虑通过频域添加,是指通过某种变换手段(傅里叶变换,离散余弦变换,离散小波变换等)将图像变换到频域,在频域对图像添加水印即从编码根源处理且不影响视觉,再通过逆变换解析其中信息。频域手段隐匿性更强,抗攻击性更高。
  • 鲁棒性,即健壮性,指水印在被处理过后仍能被识别到,如图像压缩、滤波、尺寸发生变化等。
  • 防止被去除,水印最终都以dom元素呈现,要防止浏览器控制台被恶意修改去除。

三、实现方案

方案一  JS实现

通过canvas(或svg)+水印文案生成base64的编码图片data,结合Mutation API以背景图显示到需要水印范围的dom容器中,此处以canvas为例。

优点:实现便捷,不易被去除。

缺点:将资源文件暴露在前端,安全性较低。

实现如下:首先封装watermark方法,在react中执行使用。

// watermark.js
const watermark = (props) => {
  const {
    container = document.body, 
    width = '300px',  
    height = '240px',
    textAlign = 'center', 
    verticalAlign = 'middle', 
    font = "18px Microsoft Yahei",
    fillStyle = 'rgba(100, 100, 100, 0.2)',//水印颜色
    content = '机密内容',
    rotate = -20,
  } = props;

  const canvas = document.createElement('canvas');
  const ctx = canvas.getContext("2d");

  canvas.setAttribute('width', width);
  canvas.setAttribute('height', height);

  ctx.textAlign = textAlign;
  ctx.verticalAlign = verticalAlign;
  ctx.font = font;
  ctx.fillStyle = fillStyle;
  ctx.rotate(Math.PI / 180 * rotate);
  ctx.fillText(content, parseFloat(width) / 2, parseFloat(height) / 2);

  const base64Url = canvas.toDataURL('image/png', 0.92);
  const _wm = document.querySelector('._wm');
  const watermarkDiv = _wm || document.createElement("div");

  const styleStr = `
      width: 100%;
      height: 100%;
      position: absolute;
      top: 0;
      left: 0;
      background: url('${base64Url}');
      pointer-events: none;`;
      
  watermarkDiv.setAttribute('style', styleStr);
  watermarkDiv.classList.add('_wm');

  if (!_wm) {
    container.style.position = 'relative';
    container.insertBefore(watermarkDiv, container.firstChild);
  }

  const MutationObserver = window.MutationObserver || window.WebKitMutationObserver;    

  if (MutationObserver) {
    let observer = new MutationObserver(() => {
      const _wm = document.querySelector('._wm');
      if ((_wm && _wm.getAttribute('style') !== styleStr) || !_wm) {
        observer.disconnect();
        observer = null;
        watermark(props);
      }
    });
    observer.observe(container, {
      attributes: true,    
      childList: true,     
      characterData: true,  
      subtree: true
    });
  }
}
export default watermark;
复制代码

在react 中使用watermark,

import React, { useEffect } from "react";
import watermark from './water.js';

const Comp = () => {
  useEffect(() => {
    watermark({
      content: '水印测试机密内容',  // 水印文本
      container: document.getElementById('water')  // 水印容器区域
    });
  }, []);
  return (
    <div id="water" style={{width: 800, height: 400}}>
      <div>balabala...</div>
      <div>balabala...</div>
      <div>balabala...</div>
    </div>
  );
};
复制代码

效果如下:

11111.png

注:可用canvas或svg格式,参考实际场景与浏览器兼容性。 MutationObserver接口提供了监视对DOM树所做更改的能力。它被设计为旧的Mutation Events功能的替代品,该功能是DOM3 Events规范的一部分。浏览器兼容性如下

方案二 Node实现

Node 端基于Sharp根据登录用户信息经鉴权认证后生成图片返回到前端。 后端动态生成原始数据提高安全性,但比较纯前端生成性能略显低。 推荐sharp + text-to-svg工具配合,不推荐gm、imagemagick已多年不维护。 实现如下,首先封装添加水印方法,相关API通过Sharp配置。

const sharp = require('sharp');
const TextToSVG = require('text-to-svg');
const textToSVG = TextToSVG.loadSync();

// 添加水印
const addText = async(bgImg, font = {}, filePath) => {
  const { 
    fontSize = 14, 
    text = '机密内容', 
    color = 'rgba(240,241,243, 0.4)', 
    left = 0, 
    top = 0
  } = font;

  // 设置文字属性
  const attributes = {
    fill: color
  };

  const options = {
    fontSize,
    anchor: 'top',
    attributes
  };

  // 文字转svg 再buffer
  const svgTextBuffer = Buffer.from(textToSVG.getSVG(text, options));

  // 写入文字水印
  await sharp(bgImg || {
    create: {
      width: 200,
      height: 200,
      channels: 4,
      background: { r: 255, g: 255, b: 255, alpha: 0 }
    }
  })
  .rotate(0)
  .resize(200, 200)
  .composite([
    {
      input: svgTextBuffer,
      top,
      left,
    }
  ])
  .png()
  .toFile(filePath)
  .catch(err => {
    console.log(err)
  });
};
复制代码

node调用并返回到前端页面,此处在controller中演示。

class PageController extends Controller {
  async test() {
    const { ctx } = this;
    await addText(
      path.join(__dirname, 'bg.jpg'), // 背景图片,如果null则生成透明背景加文字水印
      {
        // fontSize: 20,
        text: '机密内容',
        left: 100,
        top: 160
      },
      path.join(__dirname, 'out.png')  // 图片生成路径
    );
    ctx.type = 'image/png';
    ctx.status = 200;
    const curPath = path.join(__dirname, 'out.png');
    const file = fs.readFileSync(curPath);
    ctx.body = file;
  }
}
module.exports = PageController;
复制代码

效果图片如下

方案三 暗水印加密优化

为更高提升安全性,可在nodejs中生成明水印和暗水印结合(或可通过集团CRO产品接入文档参阅接入相关产品),一层明水印(用户视觉可见)显示用户名称信息,一层暗水印(用户视觉不可见方式)显示通过md5等加密后的用户信息,前端结合Mutationovserver API防去除。

代码实现方式即方案一方案二的结合优化:

  1. nodejs中 sharp + text-to-svg + md5生成图片后返回到网页端,代码同方案二。
  2. 网页端请求获取图片结合window.MutationObserver的API渲染。
...
axios.get('/watermark')  // 获取图片watermark.png
...

let MutationObserver = window.MutationObserver || window.WebKitMutationObserver;    
let dom =  document.getElementById('root');  // 添加水印dom
let observer = new MutationObserver(mutations => {  
  dom.style.background = "url('watermark.png')";
}); 
observer.observe(dom, {
	attributes: true,    
  childList: true,     
  characterData: true,  
  subtree: true
}); 
复制代码

四、总结

增加水印可有效防止信息泄露与知识产权侵犯,其实现中也需业务结合考虑性能资源和安全要求程度等方面。如截图或者去掉水印截图,均可通过PS等图片制作工具将图层颜色覆盖调试显示出水印追踪到个人,但如果图片损坏严重也会对信息追踪增大难度甚至获取不到,所以也需要在日常中提高信息安全的重视。
参考文章:
ImageMagick图像处理 www.imagemagick.com.cn/
Web API接口 developer.mozilla.org/zh-CN/docs/…
Sharp 图像合成 sharp.pixelplumbing.com/api-composi…

Supongo que te gusta

Origin juejin.im/post/7074838573421330463
Recomendado
Clasificación