从零构建文本隐形水印:一种基于Unicode零宽度字符的隐写实践

发布时间:2026/7/15 19:28:17
从零构建文本隐形水印:一种基于Unicode零宽度字符的隐写实践 1. 什么是文本隐形水印你可能见过图片上的半透明logo但文本也能藏水印——而且完全看不见。文本隐形水印是一种通过修改文本的底层编码结构嵌入隐藏信息的技术。就像用隐形墨水在纸上写字平时看不出痕迹但用特殊方法就能显现。这种技术主要依赖Unicode中的零宽度字符——这些字符在显示时不会占据任何视觉空间。比如U200B零宽度空格U200C零宽度非连接符U200D零宽度连接符实际场景中你可以在合同、小说甚至代码里嵌入作者信息或版权标识。即使文本被复制粘贴多次水印依然存在。去年有个案例某科技公司发现内部代码泄露通过提取文本中的零宽度字符水印最终锁定了泄露员工。2. 零宽度字符的工作原理2.1 Unicode的隐藏彩蛋Unicode标准中有一组特殊字符它们没有宽度、不可见但能被计算机识别。比如在字符串Hello中插入U200B显示仍是Hello但实际存储的是H‌e‌l‌l‌o这里用‌模拟零宽度字符。这些字符的二进制表示如下字符名称Unicode编码二进制表示零宽度空格U200B11100000 10000000 10001011零宽度非连接符U200C11100000 10000000 10001100零宽度连接符U200D11100000 10000000 100011012.2 编码规则设计假设我们要嵌入二进制水印1011将原文每个字符后插入一个零宽度字符用U200B代表0U200D代表1按水印位循环插入H[1]e[0]l[1]l[1]o提取时只需扫描零宽度字符序列还原二进制流。我实测发现普通文本编辑器如VS Code会保留这些字符但微信、钉钉等IM工具可能会过滤掉它们——这是需要注意的兼容性问题。3. 实战Python实现水印嵌入3.1 基础版本代码def embed_watermark(text, watermark): zero_width_map {0: \u200b, 1: \u200d} watermarked [] for i, char in enumerate(text): watermark_bit watermark[i % len(watermark)] watermarked.append(char zero_width_map[watermark_bit]) return .join(watermarked) # 使用示例 original 重要机密文件 watermark 110101 # 版权标识的二进制形式 hidden_text embed_watermark(original, watermark) print(f看似普通的文本{hidden_text}) print(f实际长度{len(hidden_text)} vs 原始长度{len(original)})这段代码会在每个字符后插入水印位。用hexdump查看输出能看到交替出现的E2 80 8B和E2 80 8D零宽度字符的UTF-8编码。3.2 增强鲁棒性单纯插入字符容易被批量删除我们可以随机化插入位置不是每个字符后都插入添加校验码在水印末尾加CRC校验加密水印先用AES加密再嵌入改进后的嵌入逻辑import random from Crypto.Cipher import AES def robust_embed(text, watermark, key): # AES加密水印 cipher AES.new(key, AES.MODE_ECB) encrypted cipher.encrypt(watermark.ljust(16).encode()) # 转换为二进制串 binary_wm .join(f{byte:08b} for byte in encrypted) # 随机选择插入位置 positions sorted(random.sample(range(len(text)), len(binary_wm))) # 插入水印 result list(text) for pos, bit in zip(positions, binary_wm): result.insert(pos 1, \u200b if bit 0 else \u200d) return .join(result)4. 水印提取与检测4.1 基础提取器def extract_watermark(watermarked_text): bits [] for char in watermarked_text: if char \u200b: bits.append(0) elif char \u200d: bits.append(1) return .join(bits) # 高级版处理加密水印 def decrypt_watermark(watermarked_text, key): binary_str extract_watermark(watermarked_text) bytes_data [int(binary_str[i:i8], 2) for i in range(0, len(binary_str), 8)] cipher AES.new(key, AES.MODE_ECB) return cipher.decrypt(bytes(bytes_data)).decode().strip()4.2 对抗文本编辑文本可能被修改导致水印损坏。我们可以冗余编码重复嵌入3次水印取多数表决位置散列用HMAC算法确定插入位置错误校正添加Reed-Solomon编码from hashlib import sha256 def hmac_position(text, watermark, secret): hmac sha256((text secret).encode()).hexdigest() positions [] for i in range(len(watermark)): pos int(hmac[i*2:i*22], 16) % len(text) positions.append(pos) return sorted(positions)5. 应用场景与局限性5.1 典型使用案例代码版权保护在开源代码中嵌入开发者信息文档溯源政府公文添加部门标识聊天审计IM自动在消息中插入发送者ID防抄袭检测论文预印本嵌入指纹去年某开源项目发现有人去除License后二次销售通过提取代码中的零宽度字符水印成功证明了原始著作权。5.2 技术限制编辑器兼容性保留水印VS Code、Sublime Text可能丢失Word、WPS粘贴时完全过滤大多数网页文本框攻击手段重新输入文本OCR无法提取转PDF再转文本使用strings命令提取可见字符容量限制每100字符约可嵌入20-30位水印信息我在实际项目中发现结合自然语言水印同义词替换和零宽度字符能显著提高抗攻击能力。例如同时用快速和迅速来编码二进制位即使零宽度字符被过滤仍有部分信息保留。