Ch7 · 大模型隐写术 · 动手学大模型
隐写术(Steganography)是一门古老的技艺,中文语境里常称之为"密写"——把秘密信息藏在看似寻常的载体之中,让观察者毫无察觉。我国古代早有"以药水书字、见火即显"的密信术,而西方也有在信件放大后才会显出隐藏图案的"隐形墨水"故事。这些古典方法有一个共同特点:信息隐藏的载体本身是经过刻意加工的,发送方和接收方都需要事先约定一种特殊规则。
大语言模型为隐写术打开了一扇全新的大门。语言模型天然地生成人类可读的文本流,如果能在 token 采样的过程中"约束"生成方向,使得每个 token 恰好携带一部分二进制位信息,那么生成出来的文本表面完全正常——没有任何特殊字符、没有语法异常、没有任何人工痕迹——但其中却暗藏了一段完整的秘密信息。接收方只需知道使用了哪个模型和哪段提示词,就能无损地提取出隐藏内容。
本章基于 GPT-2 实现完整的文本隐写系统,涵盖两种编码方案——霍夫曼编码和固定长度编码——的核心原理、实现细节,以及容量与质量之间的权衡分析。
大模型隐写的核心思想极为简洁:既然语言模型在每个生成 step 输出的是下一个 token 的概率分布,我们就可以在这个概率分布上做文章。具体来说,整个隐写-解码过程分为以下四个步骤:
步骤一:秘密信息 → 二进制序列
将待隐藏的文本(例如"Meet me at the library at 8pm")转换为二进制比特流。这一步通常使用 ASCII 或 UTF-8 编码完成。以 ASCII 为例,每个字符对应 8 个比特。
步骤二:编码规则映射比特到 token
设计一套编码规则,将二进制比特序列映射为 token 集合。这里的编码规则既可以是霍夫曼编码(根据 token 频率自适应分配编码长度),也可以是固定长度编码(每个 token 携带固定位数的比特)。
步骤三:约束采样生成隐写文本
在生成过程中,根据待嵌入的比特信息,从模型输出的概率分布中"筛选"出符合编码规则的 token。具体操作是:计算累积分布函数(CDF),找到与目标比特序列对应的 token 索引,然后使用贪婪采样或受控采样策略选择该 token。由于只从合法 token 集合中选取,生成质量不会显著下降。
步骤四:解码
接收方使用完全相同的模型、分词器和编码规则,对隐写文本重新走一遍生成过程(或者仅做概率分布分析),就能逆向提取出隐藏的二进制序列,还原为原始秘密信息。整个过程无需额外传输任何元数据——编码规则本身就是"密钥"的一部分。
值得强调的是,步骤三中的约束采样并不意味着一定要拒绝所有"不合法"的 token。在实际实现中,我们通常是:先计算所有合法 token 的累积概率质量,然后根据待嵌入的比特值在这个概率质量区间内做一次"加权均匀采样"。这样既保证了每个 token 的选择都与目标比特精确对应,又保持了采样的随机性,使输出文本更接近真实分布。
隐写系统有一个关键参数 k,决定了每个生成的 token 最多能携带多少比特的信息。k 的含义是:将 token 按概率从高到低排序,取前 2k 个 token 作为"合法 token 集合"。
- k=1:每个 token 携带 1 bit 信息。需要从概率最高的 2 个 token 中选择。以 GPT-2 的词表大小(50,257)为例,合法 token 集合只有前 2 个——这几乎等价于强制选择 top-1,文本质量严重下降。
- k=2:每个 token 携带 2 bits 信息。合法 token 集合扩展到前 4 个 tokens。这是实践中常用的折中方案——容量提升了一倍,同时仍然有足够的选择空间来维持文本的自然度。
- k=3:合法 token 集合扩展到前 8 个 tokens,容量更高但每步的选择范围也更大,采样过程需要更精确的数值计算。
一个直观的类比:想象你是一位图书管理员,要把情报藏在图书馆的书架上。k=1 意味着你只能在每本书的第一页做标记——选择极其有限,容易被发现且书的外观会被改变。k=2 意味着你可以用第一页或第二页做标记——灵活性增加了一倍,但仍然需要精准操控。如果你不做隐藏(k=0),那么书架看起来完全正常,你可以自由地在任何位置做任何事——这就是普通文本生成。
编码规则是隐写系统的核心构件。本节详细对比两种编码方案的设计思路、优缺点和实现差异。
固定长度编码(FLC)
固定长度编码是最朴素也是最容易理解的方法:将 token 按概率从高到低排序,然后均匀地将前 2k 个 token 划分为 2k 个等大的区间,每个区间对应一个 k-bit 的二进制值。例如,当 k=2 时,前 4 个 token 的区间划分如下:token[0] → "00",token[1] → "01",token[2] → "10",token[3] → "11"。
FLC 的优点是实现极为简单、编解码速度快。但它的缺点也很明显:所有 token 不论其真实出现频率如何,都被分配了相同的编码长度。如果某个 token 在自然语言中极为罕见(例如"fantastic"),而另一个 token 极为常见(例如"the"),FLC 给它们分配了等长的编码,这实际上是一种信息容量的浪费——我们本可以用更短的编码来表示高频 token,从而在同样的文本长度内嵌入更多秘密信息。
霍夫曼编码(Huffman Coding)
霍夫曼编码的核心理念是:根据 token 的实际概率分布,为每个 token 分配变长的编码——概率越高的 token,编码越短。这意味着在相同的 token 生成数量下,高频 token 用较短的编码,整体上能嵌入更多的秘密信息。
具体操作是:首先统计 GPT-2 分词器在大量文本语料上的 token 频率,然后构建霍夫曼树。树的叶子节点对应具体 token,从根到叶子的路径(向左记为 0,向右记为 1)即为该 token 的二进制编码。在隐写过程中,我们根据待嵌入的比特序列,沿着霍夫曼树走到对应的叶子节点,选择该 token。这种方式与 FLC 的本质区别在于:高频 token 的编码长度往往远小于 k,从而节省出比特空间。
用一个具体例子说明两者的差异。假设我们有四个 token A、B、C、D,频率分别为 0.5、0.25、0.125、0.125。FLC(k=2)为每个 token 分配 2 bits 的等长编码,总比特开销为 2×4=8 bits。但如果用霍夫曼编码:A(频率最高)可能只需要 1 bit("0"),B 需要 2 bits("10"),C 和 D 各需 3 bits("110"和"111"),总比特开销为 0.5×1+0.25×2+0.125×3+0.125×3=1.625 bits——远小于 FLC 的 8 bits。这是一个夸张的简化例子,但足以说明问题:在足够长的文本中,霍夫曼编码能比 FLC 节省约 20%-40% 的文本开销。
约束采样是整个隐写系统最关键的技术环节。当我们需要嵌入一个特定的 k-bit 序列时,如何在模型输出的概率分布中找到对应的合法 token?
假设 k=2,我们需要嵌入 "10"(即十进制 2)。我们的策略是:将合法 token 集合(概率最高的 2k=4 个 token)按概率从高到低排序,将 [0, 1] 区间划分为 4 个子区间,每个子区间的宽度正比于该 token 的概率。然后根据目标值 2/4=0.5,在累积概率中找到对应的 token。
更正式地描述:设合法 token 集合为 {t0, t1, ..., tm-1},其中 m=2k,且 P(t0) ≥ P(t1) ≥ ... ≥ P(tm-1)。设目标比特对应的数值为 v(0 ≤ v < m)。计算累积概率阈值 α = v/m。然后找到最小的 i 使得 ∑j=0i P(tj) ≥ α,从 ti 中采样。
这个过程类似于在一条有 m 个"权重格"的尺子上,根据 v 的位置找到落在哪个格子里。如果某个区间的宽度更大(即对应 token 的概率更高),那么目标值落在该区间的概率也更高,这保持了采样的概率正确性。
以下是隐写系统的核心数据结构与函数设计,完整代码请参考 GitHub: chapter7 中的 llm_stega.ipynb。
import torch
import numpy as np
from transformers import GPT2LMHeadModel, GPT2Tokenizer
class Huffman:
"""霍夫曼编码隐写处理器"""
def __init__(self, k, bits):
self.k = k
self.bits = bits # 待嵌入的二进制比特序列
self.alphabet_size = 2 ** k # 编码表大小
def encode(self, model, input_ids):
"""根据当前上下文和待嵌入比特,从模型logits中选取合法token"""
# 获取当前 step 的 logits
logits = model(input_ids).logits[-1]
probs = torch.softmax(logits, dim=-1)
# 取概率最高的 2^k 个 token 作为合法集合
top_k_probs, top_k_indices = torch.topk(probs, self.alphabet_size)
# 计算累积分布,找到与目标比特对应的token
# ...(根据比特序列和累积概率选择)
return selected_token
class FLC:
"""固定长度编码隐写处理器"""
def __init__(self, k, bits):
self.k = k
self.bits = bits
self.alphabet_size = 2 ** k
def encode(self, model, input_ids):
"""固定长度编码:均匀划分概率空间"""
# ...(均匀区间划分与采样)
return selected_token
def generate_text_with_steganography(model, tokenizer, prompt, handle):
"""带隐写功能的文本生成主函数"""
input_ids = tokenizer.encode(prompt, return_tensors='pt')
generated = input_ids.clone()
bits_index = 0
# 逐 token 生成,直到嵌入完所有比特或达到最大长度
for _ in range(max_new_tokens):
# 约束采样
next_token = handle.encode(model, generated)
generated = torch.cat([generated, next_token.unsqueeze(0)], dim=1)
bits_index += handle.k # 每个 token 携带 k bits
if bits_index >= len(handle.bits):
break
stega_text = tokenizer.decode(generated[0])
# 同时生成不带隐写约束的"正常"版本用于对比
normal_output = model.generate(input_ids, max_new_tokens=max_new_tokens)
normal_text = tokenizer.decode(normal_output[0])
return stega_text, normal_text
这个代码框架清晰地展示了隐写系统的三大组件:编码处理器(Handle)、约束采样器(encode 方法)和生成循环。霍夫曼编码和固定长度编码的区别主要体现在 encode() 方法中概率区间的划分策略——霍夫曼编码根据 token 频率动态调整区间宽度,而固定长度编码使用等宽区间。
程序运行后会在 outputs/ 目录下生成两个文件:
outputs-gpt2-stega.txt:带隐写的生成文本,其中每个 token 的选择都受到待嵌入比特的约束。outputs-gpt2-normal.txt:使用标准 top-k 采样或 nucleus 采样生成的"普通"文本,作为对照基准。
比较两个文件,你会发现隐写文本在语法正确性、语义连贯性和风格自然度上与普通文本几乎无法区分。这是因为约束采样只在概率最高的 2k 个 token 中做选择,而这些 token 本身就已经是模型认为最合理的后续词——差异只在极其细微的用词选择上。一个典型的场景:你可能看到隐写版本写的是 "the" 而普通版本写的是 "a",两者在当前语境下都是完全合理的。
大模型隐写术在以下几个场景中具有实际价值:
隐蔽通信
在审查严格的环境中,发送方和接收方可以事先约定好模型、分词器和编码规则。发送方将敏感信息嵌入看似正常的文章中,接收方收到后运行解码程序即可还原信息。整个过程对第三方完全透明——他们看到的只是一段普通文本,不构成任何可疑的"加密通信"行为。
版权追踪
内容创作者可以在生成文本中嵌入唯一标识符(类似数字水印)。如果文本被未授权地复制和传播,创作者可以通过解码确认来源,而复制者甚至不知道水印的存在。这与 Ch5 模型水印的不同之处在于:隐写水印是嵌入文本语义中的,不依赖模型的采样统计特性。
数据泄漏检测
企业可以在内部 AI 助手的输出中嵌入监控信息。如果员工将 AI 生成的内容故意传播到外部,安全团队可以通过解码追踪泄漏源头,实现一种"主动式"的数据泄漏防护机制。
然而,隐写术也面临一些固有限制:
- 容量限制:每个 token 只能携带 k bits,在 k=2 的常见配置下,嵌入一段有意义的信息需要较长的文本。对于短消息,隐写的"性价比"不高。
- 同步问题:发送方和接收方必须使用完全相同的模型、分词器和编码规则。如果模型版本更新或分词器发生变化,解码可能失败。
- 鲁棒性问题:如果隐写文本经过了 paraphrase(改写)、translation(翻译)甚至摘要压缩,隐藏的信息可能被破坏——这与 Ch5 中模型水印面临的鲁棒性挑战类似。
- 检测风险:虽然隐写文本看起来正常,但学术研究表明某些隐写方法产生的文本在 token 分布上存在微弱但可检测的统计异常。如果审查方有足够强的统计工具,隐写行为可能被发现。
基于本章的基础框架,有几个值得探索的进阶方向:
第一,使用更大的模型。 GPT-2 只是一个 774M 参数的模型,词表相对较小(50,257 个 token),且对中文的处理能力有限。如果使用 ChatGPT、Claude 或开源的 LLaMA 系列模型,隐写系统可以在更丰富的语言表达空间中选择合法 token,生成质量更高、隐蔽性更强的隐写文本。
第二,动态 k 值。 固定 k 值意味着每个 token 携带相同数量的比特。但在实际语言中,某些位置的选择空间更大(可以选很多合理的词),某些位置的选择空间更小(几乎只有一个词合理)。一个自适应的方案是:根据当前 step 的熵(entropy)动态调整 k——高熵位置用较大的 k 嵌入更多比特,低熵位置用较小的 k 甚至不嵌入。这可以在不显著损害文本质量的前提下提高整体容量。
第三,多模型协同隐写。 可以设计一种"双层隐写"机制:先用一个模型嵌入第一层信息,再用另一个模型在第一层的基础上嵌入第二层信息。这在多方通信场景中特别有用——每个接收方只能解码属于自己的那一层信息。
本章以 GPT-2 为载体,完整实现了一个基于大语言模型的文本隐写系统。核心要点如下:
- 隐写的本质是在语言模型的概率分布上做"约束采样"——选择符合编码规则的 token,使其携带二进制位信息。
- k 值控制每个 token 携带的比特数,k=2 是容量与质量的最佳折中。
- 霍夫曼编码根据 token 频率自适应分配编码长度,优于固定长度编码,尤其在长文本场景下。
- 隐写文本在语法、语义和风格上与普通文本几乎无法区分,具有极高的隐蔽性。
- 隐写术面临容量限制、同步要求、鲁棒性挑战和潜在的统计检测风险。
本章的内容为后续章节奠定了基础——Ch8 多模态大模型将展示 LLM 如何感知图像、音频、视频等多种模态;而 Ch9 GUI 智能体则将展示 LLM 如何"看懂"屏幕截图并执行具体操作。这些能力都是在语言智能这一核心枢纽上逐步扩展的。
以下内容来自本章 PDF 课件原文,保留了讲义的原始结构与措辞,供深入对照参考。
密码学是"加密"明文,使消息内容不被中间人获取,但消息载体本身可能被截获和篡改。隐写术则是"隐藏"消息传递的事实,将内容隐藏在公开信道上,伪装成一般内容,只有消息收受方能发现隐藏信息,不被中间人察觉。现实案例:藏头诗、夹在书中的小纸条。
- 隐写术:载体是完全正常的文件(如合同),不引起怀疑
- 密码学:密文本身暴露"有秘密",但内容被保护
隐写的下游衍生技术是水印(更重视鲁棒性,愿意被很多人发现并解读出信息的隐写)。
数字媒体载体(图像/视频)有大量冗余空间可插入信息,是隐写的常见选择。文本作为载体冗余度低,但其具有灵活、内容量大等特点,可以比较容易地嵌入其他内容。使用大语言模型进行文本隐写的优势在于能利用大模型性能,输出更自然、更难被发现的隐写内容。
文本生成模型是"序列化"的数据处理过程。当输入上文后,模型返回对 next token 的预测 logits 表(按概率从高到低排列)。正常情况下模型根据参数随机选择一个 token 继续循环生成。
生成式隐写的关键干预点是:在模型推理过程中,根据设置好的规则干预模型对 next token 的选择,从而将秘密信息的二进制位嵌入生成文本。解码时使用相同的模型、上文、参数,保证每次返回与生成时完全相同的编码空间,从而反向提取隐藏信息。
以 3-bit 编码为例:输入"I"后,模型返回前 8 个 token 按概率排列为 have(000)、am(001)、was(010)……当要嵌入"001"时选择"am"作为 next token,循环继续即可嵌入完整信息流。
前述方法使用固定编码(直接按顺序对前 n 个 token 编码)。霍夫曼编码方式根据 token 频率自适应分配编码长度,高频 token 用短编码,从而在同样的 token 空间内嵌入更多信息,提升嵌入率。两种方案的核心权衡是容量 vs 文本自然度。
以下为本章 PDF 课件原文(共13页),按页面顺序呈现,保留讲义的原始措辞与结构。
动手学大模型隐写
张玉龙
大模型在安全通信上的应用
隐写术(Steganography)是一种信息隐藏技术,其核心目的是将信息嵌入到各种载体(如数字图
像、音频、视频或文本)中,以实现隐蔽通信。隐写术与加密技术不同,它不仅隐藏信息的内容,
还隐藏信息传输行为的存在性。这种技术利用人类感知系统对某些信息不敏感的特性,将秘密信息
隐藏在数字载体的冗余信息中,使得信息在表面上看起来与普通载体无异,从而难以被攻击者察觉。
隐写术的应用包括但不限于隐蔽传输、版权保护等。随着技术的发展,隐写术在军事、商业等领域
变得越来越重要,同时也为恶意行为提供了便利,如间谍活动、恐怖袭击等。
隐写的意义
简单来说,隐写是与密码术不同的一种安全信息传输方法。
密码是“加密”明文,使得要传递的消息内容不被中间人获取到,但消息传递载体可能被中间人发现
和获取从而篡改、拦截消息的传递。
隐写则是“隐藏”消息传递的事实,将要传递的消息内容隐藏在公开信道上,伪装成一般内容,目标
是只有消息收受方可以发现隐藏的消息,不被中间人发现。
现实生活中的简单隐写案例:藏头诗、夹在书中的小纸条等等。
隐写的下游衍生技术:水印(可以理解为一种更重视鲁棒性,愿意被很多人发现并解读出信息的隐
写)。
隐写的实例与密码对比
隐写术(文本格式):
• 操作:调整电子文档中字母间距(如0.1pt差异)、字体颜色(#000000 vs010101),用二进制
编码信息(如“间距大=1,正常=0”)1。
• 目标:信息藏于公开文本中,肉眼不可见。
密码学(维吉尼亚密码):
• 操作:使用密钥词重复加密(如密钥"KEY"加密"HELLO"→"RIJVS")。
• 目标:生成乱码密文,需密钥解密7。
• 对比:
• 隐写术:载体是正常文件(如合同),不引起怀疑。
• 密码学:密文本身暴露“有秘密”,但内容保密。
隐写的实例
隐写术(图像LSB):
• 操作:修改图片像素最低有效位(LSB),嵌入秘密数据(如另一张图的二进制)。人眼无法察
觉差异,但工具可提取29。
• 目标:信息藏于普通照片(如旅游照),绕过审查。
密码学(AES-256):
• 操作:用密钥将文件加密为乱码(如z.exe→G8x!gF2*...
• 目标:即使文件被截获,也无法破解内容。
协同用例:
• 先用AES加密敏感数据;
• 再将密文嵌入图片LSB中。
→双重保护:既隐藏存在(像普通图片),又隐藏内容(需密钥解密)
隐写的实例
二维码中的隐写:
原理:设计一个看起来完全正常的二维码(指向一个无害网站),但在其纠错区域或通过精心设
计码点图案,嵌入额外的隐藏信息(另一个URL、文本、小图片)。普通扫码软件只能读出表面信
息,需要定制软件才能读出隐藏层。
示例:餐厅菜单上的二维码,扫码显示菜品介绍(公开层),但用特定APP扫描能获取隐藏的当
日优惠码或内部员工信息。海报上的二维码,普通扫码是活动介绍,隐藏层是VIP邀请函。
生活场景:营销活动(寻宝、解锁优惠),内部信息传递(员工公告),版权保护(在公开二维
码中嵌入所有者信息)。
要点:需要理解二维码结构(尤其是纠错码的冗余性)、需要生成双层的特殊二维码工具。
隐写的模型
目前在数字媒体载体的选择上,隐写一般选择图像或视频,因为有大量的冗余空间可以插入信息。
而文本作为隐写载体的话,冗余度低,但其也具有灵活、内容量大等特点,可以比较容易地嵌入
其他内容中。这次我们用大语言模型来介绍大模型隐写。
隐写(左)与水印(右)各自的过程模型如下图所示。
文本隐写的分类
文本隐写主要包含两类:修改式与生成式。
近年来随着深度学习特别是大模型的发展,生成式文本隐写已经成为主流。
大模型文本隐写1-3
文本生成模型都是“序列化”的数据。
当输入上文和各参数后,模型会返回一个对“next
token”的预测logits表,表内是从最高概率到最低概
率的token ids。
正常情况下,模型会根据参数来随机选择n个token
中的一个来作为下一个token,然后继续循环生成。
而生成式隐写则是在模型推理过程中,根据设置好
的规则干预模型对next token的选择,从而将想要
嵌入的信息在模型推理过程中嵌入到生成文本里。
[1] Yang, Zhong-Liang, et al. "RNN-stega: Linguistic steganography based on recurrent neural networks." IEEE Transactions on Information Forensics and Security 14.5 (2018): 1280-1295.
[2] Tang, Yifan, et al. "Linguistic Steganalysis via LLMs: Two Modes for Efficient Detection of Strongly Concealed Stego." IEEE Signal Processing Letters (2024).
[3] Yang, Zhong-Liang, et al. "VAE-Stega: linguistic steganography based on variational auto-encoder." IEEE Transactions on Information Forensics and Security
16 (2020): 880-895.
大模型文本隐写
如下图为例,假设我要嵌入被转换为的二进制的秘密信息,我可以将每次模型返回的next token的前n
个编码,如当我输入“I”之后,模型返回的前8个token按概率大小顺序排列分别是have(000)、am(001)、
was(010)、can(011)….每个token都能对应一个3位二进制的值,当我要隐写嵌入“001”时,我选择“am”作
为next token,后面循环继续。
大模型文本隐写
这里的本质上方法就是对token候选词的前n个进行编码,构造一个隐式的隐写空间,而当我想要解码
信息的时候,使用相同的模型、上文、参数,那么可以保证每次模型会返回一个跟生成时完全相同
的编码空间,从而反向从token来获取到原来的二进制信息流,比如从“am”反向解码得到001。
大模型文本隐写
当然前述方法使用了非常固定的编码方法(直接按顺序对前n个token编码),下图实际上是使用霍
夫曼编码方式,从而确保尽可能嵌入更多信息,提升嵌入率。
大模型隐写的意义和未来
大模型隐写:使用大模型进行隐写的优势是能利用大模型的性能,输出更自然、更难被发现的隐
写内容。
编码空间:隐写的不同方法很依赖编码方式,如果有新的构造编码空间的方法,就可以创造出一
门新的隐写术。
交叉扩展:大模型生成式文本隐写的本质是“序列化数据的循环编码”,这种技术也可以迁移到其他
媒介,比如DNA也是序列化数据,可以使用仪器配合大模型输出对DNA编码进行隐写,这样可以
实现军事上“把隐藏信息藏在一瓶水、一些细胞、一些毛发上”,从而实现极高水平的隐藏信息传递。
大模型水印:现有大模型水印技术也可以与隐写互相结合和推进。比如著名的KGW水印也是在推
理过程中对next token的选择进行约束。
内容安全:隐写的反制技术“隐写分析(Steganalysis)”可以用于对网络媒体内容进行内容评估和
筛选检测。不触发关键词的“阴阳怪气”、“网暴骂人”本质上也是一种“隐写”,因此也有被人工智能
使用隐写检测的方法自动侦测到的可能性,这将有利于未来人类网络空间安全的维护。
- 编码空间扩展:新的编码空间构造方法可以创造新的隐写术
- 跨媒介扩展:序列化数据的循环编码可迁移到 DNA 等媒介,实现极高水平的隐藏信息传递
- 与大模型水印结合:KGW 水印本质也是在推理过程中对 next token 选择进行约束,与隐写技术互相推进
- 内容安全:隐写分析(Steganalysis)可检测网络媒体中的隐写内容,也可用于识别不触发关键词的"阴阳"内容