字符串隐写加密是将信息隐藏于正常字符串中(如插入零宽字符、利用同形字等),不改变外观和长度,核心目标是“不被发现”;而普通加密(如aes)则通过算法将明文变为显式密文,旨在“不可读”,二者本质区别在于隐蔽性与可识别性。

什么是字符串隐写加密,它和普通加密有什么区别
隐写加密(Steganography)不是把内容变乱,而是把信息藏进看似正常的载体里——比如在字符串末尾插入不可见字符、利用全角/半角空格差异、或混用 Unicode 同形字。它不追求抗破解,只求“不被发现”。和 AES 或 base64 这类显式编码不同,隐写后的字符串长度几乎不变,肉眼无法识别异常。
常见误判是把它当加密用:一旦对方检查字符串的 Unicode 码点或正则匹配空白符,隐藏内容就暴露了。它只适合低对抗场景,比如绕过简单关键词过滤、在日志里埋个调试标记。
用零宽字符实现最简隐写:原理与实操
零宽字符(Zero-Width Characters)是 Unicode 中不占显示宽度、但可被程序读取的控制符,如 u200B(零宽空格 ZWS)、u200C(零宽非连接符 ZWNJ)、u200D(零宽连接符 ZWJ)。它们能组成二进制序列:比如用 u200B 表示 0,u200C 表示 1。
实操建议:
- 只对短消息(≤50 字节)使用,避免在长文本中大量插入导致解析异常
- 隐藏前先将原始字符串转为 UTF-8 字节数组,再逐位编码,比直接处理 Unicode 字符更可控
- 不要把零宽字符插在字符串开头或结尾——某些编辑器或日志系统会自动 trim 掉
- Python / JavaScript 解析时需用
.codePointAt()或bytes模式读取,.length或.size()会漏掉零宽字符
示例(C++ 隐藏单字节):
std::string hide_byte(const std::string& cover, uint8_t data) {
std::string result = cover;
for (int i = 0; i > (7 - i)) & 1;
result += (bit == 0) ? "" : ""; // UTF-8 编码的 u200B 和 u200C
}
return result;
}
为什么 std::string::append("u200B") 在 C++ 中可能失效
C++11 起支持 u8"u200B" 字面量,但很多编译器(尤其 MSVC)默认源文件编码不是 UTF-8,直接写 u200B 会被解释成乱码或编译错误。更稳妥的方式是:
- 用 UTF-8 字节序列硬编码:
""(推荐) - 确保源文件保存为 UTF-8 without BOM,并加
// clang-format off防止格式化工具破坏 - 避免用
std::wstring+wcout输出含零宽字符的字符串——终端通常不渲染,看起来像“凭空少字” - 如果目标环境是 Windows 控制台,
SetConsoleOutputCP(CP_UTF8)必须提前调用,否则输出为空白
提取隐藏数据时最容易忽略的兼容性问题
提取逻辑必须严格按插入时的顺序和规则反向操作,但实际中容易栽在:
- 字符串经过 JSON 序列化/反序列化后,零宽字符可能被某些库(如 nlohmann/json 默认配置)静默丢弃
- HTTP header 或 URL 参数传输时,部分代理或网关会过滤控制字符,导致数据截断
- 用
std::regex匹配时,\s默认不匹配零宽字符,得显式写[u200B-u200D] - macOS 终端和 Linux GNOME Terminal 对零宽字符渲染行为不一致,调试时建议用
xxd或od -x查看真实字节
真正麻烦的不是藏进去,而是藏完之后谁来读、在哪读、中间经过哪些管道——每个环节都可能吃掉你的零宽字符。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











