使用Unicode为私密文本添加数字水印

865 阅读4分钟

如果你觉得可以,请多点赞,鼓励我写出更精彩的文章🙏。
如果你感觉有问题,也欢迎在评论区评论,三人行,必有我师焉

TL;DR

  • 前言
  • 思路解析
  • 相关知识点介绍
  • 代码解析

前言

随着数字时代的到来,可以说一切的生活,都是基于网络的。而在有些特定的场景中,涉及到私密信息的时候,总是会有心怀鬼胎的人去捏造事实和进行造谣,变成三人成虎的一个尴尬的局面。

所以就需要一种技术去识别私密文本的发布者。从而找出真凶

思路解析

我们是针对文本,来追本溯源找到始作俑者,而文本在页面中是瞬息万变的一种存在。所以我们需要将能够辨识复制文本的操作人的信息冗余到文本中,但是不能显示的展现出来。

换句话说,我们需要将代表操作人的信息悄无声息的和待复制文本结合到一起。

首先,有一点需要明确:在计算机中,无论是何种开发语言定义何种变量,最终在内存中都是基于二进制形式存贮。同时,二进制中的01都是存储一定的信息的占位符。也就是我们还需要将二进制中的信息用特定的语言方式进行替换并显示隐藏

所以我们可以将水印过程设计思路大致归为如下

新增水印

  • 用户信息二进制化
  • 二进制信息显示隐藏
  • 用户信息与文本信息冗余

解码水印

  • 获取水印信息
  • 转二进制化
  • 解析加密信息

相关知识点介绍

Unicode

What is Unicode

Unicode provides a unique number for every character, no matter what the platform, no matter what the program, no matter what the language.

Unicode零宽空格和零宽连接符、零宽非连接符

Unicode中存在一些数码是无法在页面显示,但是他们用于特定场景。 例如我们平常指定文本换行用的\n用Unicode来表示就是U+000A

而零宽空格和零宽连接符、零宽非连接符也是类似的,都无法在页面中显示,但是存在实际意义

代码解析

新增水印

用户信息二进制化

const zeroPad = num => '00000000'.slice(String(num).length) + num;
const textToBinary = username => (
  username.split('').map(char =>
    zeroPad(char.charCodeAt(0).toString(2))).join(' ')
);

这里有几点需要解释: 上文中我们说到,只需要将用户信息进行二进制处理就行。按正常思路只需要将文本进行char.charCodeAt(0).toString(2)即可。

但是在字符进行二进制换行的时候,如果二进制是以0开头的,会自动将冗余的0进行剔除。所以,我们需要在将文本转为二进制之后,需要将缺失0进行补全 const zeroPad = num => '00000000'.slice(String(num).length) + num;

例如a的二进制为1100001是不够8位的。所以,需要将其补成01100001,方便后面解码用。

信息隐藏


const binaryToZeroWidth = binary => (
  binary.split('').map((binaryNum) => {
    const num = parseInt(binaryNum, 10);
    if (num === 1) {
      return '\u200b'; // 零宽空格
    } else if (num === 0) {
      return '\u200C'; // 零宽非连接符
    }
    return '\u200D'; // 零宽连接符
  }).join('\uFEFF') // 零宽度非换行空格
);

用户信息与文本信息冗余

let encryptionText = '我是一个萌萌哒的汉子'+ binaryToZeroWidth('北宸南蓁')

解码水印

获取水印信息

const zeroWidthChar = [
  '\u200B',  //零宽空格
  '\u200C',  //零宽非连接符
  '\u200D',  //零宽连接符
  '\uFEFF', //零宽度非换行空格
]
const charArr = string.match(zeroWidthCharReg);
const binaryArr = charArr.join('').split(zeroWidthChar[2]);//加密信息是由零宽连接符所分割
const mark = binaryArr.map(binary => {
const binaryString = binary.split('').map(b => zeroWidthChar.indexOf(b)).join('');
    const utf16 = parseInt(binaryString, 2);
    return String.fromCharCode(utf16)
  }).join('')

加密信息二进制化

const zeroWidthToBinary = string => (
  string.split('\uFEFF').map((char) => { // 零宽度非换行空格
    if (char === '\u200b') { // 零宽空格
      return '1';
    } else if (char === '\u200C') {  //零宽非连接符
      return '0';
    }
    return ' '; // 加密信息自己的文本分割
  }).join('')
);

该操作可以认为是加密的的逆向操作。

解密加密信息

const binaryToText = string => (
  string.split(' ').map(num =>
    String.fromCharCode(parseInt(num, 2))).join('')
);

tips

虽然我们所说的零宽空格也好还是零宽连接符也好,它这是表面上的不显示,但是如果用代码来查询字符串的长度,他的长度为0 '\u200A'.length ===1 //true