unicode 零宽字符

unicode 官网:https://home.unicode.org/
unicode 各种类型详细介绍:https://www.compart.com/en/unicode/category/Cf
对零宽度字符完全没有头绪的可以先玩下这个Demo
在綫解密:https://330k.github.io/misc_tools/unicode_steganography.html

什么是零宽度字符?

零宽字符是一种特殊的 Unicode 字符,正如字面意思,它的宽度为零,也就是在文本中不占用任何显示空间。

它们存在于页面中主要用于调整字符的显示格式,下面就是一些常见的零宽度字符及它们的unicode码和原本用途:

  1. 零宽度空格 (zero-width space, ZWSP) U+200B : 用于较长单词的换行分隔

    • 就是一个显示宽度为零的空格,但是会强制换行或分隔单词
  2. 零宽度非断空格符 (zero width no-break space,BOM) U+FEFF : 用于阻止特定位置的换行分隔

    • 就是一个显示宽度为零的空格,但是不可以换行
  3. 零宽度断字符 (zero-width non-joiner,ZWNJ) U+200C : 用于阿拉伯文,德文,印度语系等文字中,阻止会发生连字的字符间的连字效果

    • 例如在德语中,字母组合“ff”应该被视为两个单独的字母,而不是一个单独的字符。
  4. 零宽度连字符 (zero-width joiner,ZWJ) U+200D : 用于阿拉伯文与印度语系等文字中,使不会发生连字的字符间产生连字效果

    • 它通常用于控制文本的排版和显示。常见的复杂Emoji表情即用到了该字符,用于表示多字符关系从而合成复杂新字符

零宽度字符能做什么?

1. 传递隐密信息

利用零宽度字符不可见的特性,我们可以用零宽度字符在任何未对零宽度字符做过滤的网页内插入不可见的隐形文本。下面是一个简单的利用零宽度字符对文本进行加密解密JavaScript例子:

加密

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 为了代码的简洁与易读性,以下代码会忽略性能方面考量
const text = '123😀';
// Array.from 能让我们正确读取宽度为2的Unicode字符,例:😀
const textArray = Array.from(text);
// 用codePointAt读取所有字符的十进制Unicode码
// 用toString将十进制Unicode码转化成二进制(除了二进制,我们也可以使用更大的进制来缩短加密后的信息长度,以此提升效率)
const binarify = textArray.map(c => c.codePointAt(0).toString(2));
// 此时binarify中的值是 ["110001", "110010", "110011", "11111011000000000"],下一步我们需要将"1","0"和分隔符映射到响应的零宽度字符上去
// 我们用零宽度连字符来代表1,零宽度断字符来代表0,零宽度空格符来代表分隔符
// 下面的''看上去像是空字符串,但其实都是长度为1,包含零宽度字符的字符串
const encoded = binarify.map(c => Array.from(c).map(b => b === '1' ? '‍' : '‌').join('')).join('');
// 此时encoded中包含的就是一串不可见的加密文本了
```

> 注:在使用零宽度字符进行`加密`时,请尽量避免将加密后的隐形文本插入在明文的开头或者结尾处,以此来避免隐形文本在复制时被遗漏


#### 解密

// 接着上面的encoded
// 用分隔符(零宽度空格符)提取加密文本中的字符
const split = encoded.split(‘’);
// 将文本转回成二进制数组
const binary = split.map(c => Array.from(c).map(z => z === ‘‍’ ? ‘1’ : ‘0’).join(‘’));
// 此时binary中的值再次回到开始的 [“110001”, “110010”, “110011”, “11111011000000000”]
// 最后一部只需要将二进制文本转回十进制,再使用 String.fromCodePoint 就可以得到原文本了
const decoded = binary.map(b => String.fromCodePoint(parseInt(b, 2))).join(‘’);
// 此时decoded中的值即是 “123😀”

1
2
3
4
5
6
7
8
9
10
11
12
13
14


#### 应用

1. 隐形水印

通过零宽度字符我们可以对内部文件添加`隐形水印`。在浏览者登录页面对内部文件进行浏览时,我们可以在文件的各处插入使用零宽度字符加密的浏览者信息,如果浏览者又恰好使用复制粘贴的方式在公共媒体上匿名分享了这个文件,我们就能通过嵌入在文件中的`隐形水印`轻松找到分享者了。

2. 加密信息分享

通过零宽度字符我们可以在任何网站上分享任何信息。敏感信息的审核与过滤在当今的互联网社区中扮演着至关重要的角色,但是零宽度字符却能如入无人之境一般轻松地穿透这两层`信息分享`的屏障。对比明文哈希表加密信息的方式,零宽度字符加密在网上的隐蔽性可以说是达到了一个新的高度。仅仅需要一个简单的识别/解密零宽度字符的浏览器插件,任何网站都可以成为`信息分享`的游乐场。


### 2. 逃脱词匹配

// 利用零宽度字符来分隔敏感词
const censored = ‘敏感词’;
let censor = censored.replace(/敏感词/g, ‘’); // ‘’
// 使用零宽度空格符对字符串进行分隔
const uncensored = Array.from(censored).join(‘’);
censor = uncensored.replace(/敏感词/g, ‘’); // ‘敏感词’


#### 应用

1.  逃脱敏感词过滤
    
    通过零宽度字符我们可以轻松逃脱敏感词过滤。敏感词自动过滤是维持互联网社区秩序的一项重要工具,只需倒入敏感词库和匹配相应敏感词,即可将大量的非法词汇拒之门外。使用谐音与拼音来`逃脱敏感词过滤`会让语言传递信息的效率降低,而使用零宽度字符可以在`逃脱敏感词过滤`的同时将词义原封不动地传达给接受者,大大提高信息传播者与接受者之间交流的效率。

unicode 零宽字符
https://flepeng.github.io/字符集相关-unicode-零宽字符/
作者
Lepeng
发布于
2021年3月8日
许可协议