神奇的前端“隐写术”:零宽字符

591 阅读3分钟

零宽字符是一类在视觉上不可见,但在文本中确实存在的 Unicode 字符。它们不会占据空间,也不会显示任何图形,但可以被程序识别和处理。

常见的零宽字符有:

  • 零宽空格 (ZWSP) - U+200B,HTML:​
  • 零宽非连接符 (ZWNJ) - U+200C,HTML:‌
  • 零宽连接符 (ZWJ) - U+200D,HTML:‍
  • 零宽无断空格 (NBSP) - U+FEFF,HTML:
  • 零宽断行符 (Word Joiner) - U+2060,HTML:⁠

可以直接使用 Unicode 转义或 HTML 实体在 HTML 中插入零宽字符。

<body>
  <p>这是一个零宽空格&#8203;示例</p>
  <p>这是另一个零宽空格&#x200B;示例</p>
  <div id="demo"></div>
  <script>
    const str = "这是一个零宽空格\u200B示例";
    document.getElementById("demo").innerHTML = str;
  </script>
</body>

零宽字符因其不可见性,常被用于一些特殊的需求场景:

  1. 隐写术(Steganography):在文本中隐藏信息(如知识产权水印ID)。
  2. 反爬虫/反作弊:在页面中插入零宽字符干扰爬虫或 OCR。
  3. 验证码绕过:在输入中插入零宽字符绕过简单的字符串匹配
  4. 社交平台刷热度:在评论中插入零宽字符制造“不同内容”假象

💡 零宽字符常用操作

// 过滤零宽字符
const newStr = str.replace(/[\u200b-\u200f\uFEFF\u202a-\u202e]/g, "");

// 隐藏和加密信息
// 例如,让一条链接带上隐形水印 Duskstar
// 1. 将 Duskstar 转换成二进制字符串
let name = 'Duskstar';
const textToBinary = (text) => {
	return text.split('').map(char => {
		// 确保每个字符编码都是8位(不足补前导0)
		return char.charCodeAt(0).toString(2).padStart(8, '0');
	}).join('');	// 合并为完整的二进制字符串
};
//'0100010001110101011100110110101101110011011101000110000101110010'
let binaryStr = textToBinary(name);
// 2. 将二进制字符串换成零宽字符替代
const binaryToZeroWidth = (binary) => {
	return binary.split('').map(binaryNum => {
    // 映射规则:
    // 1 → 零宽空格 (U+200B)
    // 0 → 零宽非断空格 (U+FEFF)
    return binaryNum === '1' ? '\u200B' : '\uFEFF';
	}).join('');	// // 合并为零宽字符串
}
// 3. 将零宽字符插入到普通文本中
const zeroWidthWatermark = binaryToZeroWidth(binaryStr);
const link = 'https://example.com';
const newLink = zeroWidthWatermark + link;
console.log('带水印的文本:', newLink); // 看起来与原文完全一样

// 解密,提取水印
const extractWatermark = (text) => {
  // 匹配所有零宽字符
  const zeroWidthChars = text.match(/[\u200B\uFEFF]/g);
  if (!zeroWidthChars) return '';
  
  // 转换回二进制
  const binary = zeroWidthChars.map(char => {
    return char === '\u200B' ? '1' : '0';
  }).join('');
  
  // 每8位分割成字符编码
  const chars = [];
  for (let i = 0; i < binary.length; i += 8) {
    const byte = binary.slice(i, i + 8);
    chars.push(String.fromCharCode(parseInt(byte, 2)));
  }
  
  return chars.join('');
};
const extracted = extractWatermark(newLink);
console.log('提取的水印:', extracted); // 输出: Duskstar

写在最后

One day you'll leave this world behind. So live a life you will remember! --- Avicii

我是暮星,一枚有志于在前端领域证道的攻城狮。

优质前端内容持续输出中......,欢迎点赞 + 关注 + 收藏

点赞.jpeg