密码学是一个很容易让人产生错觉的领域。
一个算法输出看起来"足够随机",经过几轮复杂的非线性运算,做出来的密文也很难直接看出规律,于是我们很容易产生一个直觉:它应该已经足够安全了。
但现代密码学恰恰要求我们克制这种直觉。
AES、ChaCha20 之所以值得信任,并不只是因为它们的输出看起来像随机数,而是因为经过了长期的公开研究、标准化以及大量密码分析。相比之下,如果把神经网络、浮点运算和 ARX 操作组合起来,哪怕实验结果非常漂亮,也不能直接把它称为一个"安全密码算法"。
最近做了一项比较有意思的实验:尝试将 SIREN(正弦激活神经网络)与 ARX 类运算结合,用于文件级加密,然后从统计随机性、输入敏感性和机器学习区分能力几个角度,对这个构造做一次实证安全性评估。
这篇文章不讨论"神经网络密码学是不是未来",而是记录这个方案到底做了什么、实验结果说明了什么,以及更重要的------哪些结论其实不能从实验中得到。
一、为什么想到用神经网络做加密?
传统对称密码已经非常成熟。
AES、ChaCha20 等算法的设计目标非常明确,相关轮函数、密钥调度、差分和线性特性都经过了长期研究。相比之下,把神经网络直接放进密码算法内部,是一个相对新颖、同时也非常容易踩坑的方向。
神经网络确实拥有几个看起来适合密码学的特点:
- 高维非线性;
- 对初始输入比较敏感;
- 多层网络能够产生复杂的函数映射;
- 参数规模通常很大;
- 很难凭直觉分析最终输出。
其中一个比较有意思的网络结构是 SIREN。
SIREN 使用正弦函数作为激活函数。和 ReLU、Sigmoid 这类常见激活函数相比,正弦激活具有周期性,因此特别适合表示具有高频变化的函数。
于是,一个很自然的问题出现了:
如果把 SIREN 当作一个复杂的非线性状态变换,再结合 ARX 运算生成密钥流,会不会得到一个具有较好统计性质的加密构造?
需要特别强调,这里的出发点只是研究假设。
网络复杂,并不等于密码学安全。
SIREN 原论文研究的是隐式神经表示和函数拟合能力,并没有证明 SIREN 可以作为安全的密码学原语。因此,这个项目从一开始就应该被定义为一个实验性构造,而不是新的安全密码算法。
二、整个加密方案是怎么工作的?
整个方案的输入是一个 50 位十进制字符串。
例如:
text
12345678901234567890123456789012345678901234567890
如果这个字符串是真正通过密码学安全随机数生成器独立、均匀产生的,那么理论上的搜索空间为:
1050
换算成二进制熵,大约是:
50log2(10)≈166.10 bit
也就是说,在理想随机生成条件下,它的理论熵超过 128 bit。
整个处理链路可以简单表示成:
text
50 位十进制口令
│
▼
SIREN 神经网络
│
▼
内部状态
│
▼
密钥流生成
│
├─────────────────┐
│ │
▼ ▼
明文 密钥流
│ │
▼ │
read2 字节级扰动 │
│ │
└────── XOR ──────┘
│
▼
密文
│
▼
HMAC-SHA256
文件结构则设计为:
text
[16B Salt][200B IV][Ciphertext][Trailer]
Trailer 中保存:
text
0xFE || 32B HMAC Tag || 0xFF
其中 HMAC 密钥通过 PBKDF2-HMAC-SHA256 从口令派生。
从工程角度看,这是一个典型的"加密 + 完整性校验"的组合思路。
但从密码学角度,真正值得研究的是中间的 密钥流生成器。
三、SIREN 网络到底放在了哪里?
当前网络大致可以描述为:
text
Input: 50
↓
Linear(50, 512)
↓
sin
↓
8 × Linear(512, 512) + sin
↓
Linear(512, 50)
↓
Sigmoid
第一层的权重采用较大的标准差初始化,并且被冻结。
隐藏层则使用正弦激活:
python
x = sin(fc1(x))
for layer in hidden:
x = sin(layer(x))
x = fc_out(x)
return sigmoid(x)
直观来看,这个网络对输入变化确实可能非常敏感。
例如:
text
password A
↓
network
↓
state A
password B
↓
network
↓
state B
即使 A 和 B 只差一个数字,最终状态也可能发生很大的变化。
但这里存在一个非常重要的密码学陷阱:
输入敏感,不等于密码学安全。
一个函数完全可以做到:
text
输入稍微变化
↓
输出完全变化
同时它仍然可能存在碰撞、偏差、周期、相关性或者其他能够被攻击者利用的结构。
所以真正需要测量的不是"看起来复杂不复杂",而是输出到底有什么统计特性。
四、密钥流生成中有一个非常特别的设计
这里是整个实验中比较有意思、同时也是问题比较明显的一部分。
网络最终输出的是 float32。
方案没有直接把这些浮点数再经过标准密码学哈希,而是直接把 float32 的内存表示重新解释成字节:
python
t = nn.view(uint8).reshape(-1, 4)
t = t[..., :1] ^ t[..., 1] ^ t[..., 2] ^ t[..., 3]
简单理解,就是:
text
神经网络输出 float32
↓
读取 IEEE 754 内存表示
↓
拆成 4 个字节
↓
4 个字节 XOR
↓
得到密钥流字节
这种方法很有"实验性"。
但它也立即带来了一个问题:
float32 并不是随机字节。
IEEE 754 浮点数内部包含符号位、指数和尾数,这些字段具有非常明确的结构。
所以神经网络输出看起来很复杂,并不意味着它的内存字节表示就是均匀随机的。
实验结果很快验证了这一点。
五、真正有意思的结果:密钥流自己其实并不随机
对生成出来的密钥流进行统计后,发现:
text
0x00 理论占比:0.3906%
实际密钥流:
0x00 占比:8.3218%
这已经不是很小的统计波动了。
进一步进行卡方检验:
text
χ² ≈ 25,305,518
而对于 256 个字节值而言,自由度为 255,正常均匀分布对应的统计量应该远远小于这个数字。
换句话说:
神经网络输出本身存在明显的字节级结构。
这实际上是这个实验里一个非常有价值的结果。
它说明,如果把机器学习模型输出直接当成密码学随机源,是非常危险的。
"神经网络很复杂"不能代替随机提取器。
六、那为什么最终密文看起来又挺随机?
这里出现了一个很有意思的现象。
在最终密文上进行相同的字节统计:
text
0x00 占比:0.3950%
已经非常接近理论值:
text
1 / 256 ≈ 0.3906%
这主要与 read2 的处理有关。
它在真正的密钥流 XOR 之前,又对明文做了一次字节级伪随机异或:
text
M' = M XOR R
最终:
text
C = M' XOR K
代入:
text
C = M XOR R XOR K
如果 R 真的是独立且均匀的随机字节流,那么从单字节边缘分布上看:
text
R XOR K
确实会趋近于均匀。
于是出现了一个有意思的结果:
text
SIREN 输出
↓
存在明显偏置
↓
float32 → bytes
↓
密钥流仍然有明显偏置
↓
+ read2 随机化
↓
最终密文变得接近均匀
这也说明:
最终密文"看起来随机",并不能反推出内部密钥流生成器安全。
因为可能存在其他机制把内部结构"遮住"了。
七、卡方检验其实没有通过
这也是重新审查论文时必须修正的一处。
实验得到:
text
χ² = 329.43
255 个自由度下,显著性水平:
text
α = 0.01
对应的临界值大约为:
text
310.46
329.43 高于临界值。
对应上尾 p-value 大约:
text
p ≈ 0.00115
因此在 α=0.01 的条件下:
text
p < 0.01
应该拒绝"完全均匀分布"的原假设。
所以更加严谨的表述应该是:
最终密文的字节分布虽然非常接近理论均匀分布,但从严格的卡方假设检验来看,在 0.01 显著性水平下仍然观察到了统计偏离。
这和"通过均匀性测试"不是一回事。
这也是做密码学实验时非常重要的一点:
不要因为数字看起来漂亮,就主动把统计结果解释成通过。
八、用机器学习来"抓密文",结果怎么样?
另一个实验是设计一个 MLP 区分器。
简单来说,就是给机器学习模型两种数据:
text
类别 1:方案生成的真实密文
类别 0:os.urandom 生成的随机字节
每个样本 1024 字节。
模型:
text
1024
↓
256
↓
64
↓
1
训练 30 个 epoch。
最终测试结果:
| 指标 | 结果 |
|---|---|
| Accuracy | 0.5115 |
| AUC | 0.4780 |
| 随机基线 | 0.5000 |
从数字来看,结果非常接近随机猜测。
也就是说:
在当前数据规模、训练方式和这个 MLP 架构下,没有观察到明显的可区分信号。
这是一个积极结果。
但不能得出:
"方案已经达到计算不可区分性。"
这两句话差别非常大。
九、为什么一个 MLP 测试不能证明 IND-CPA?
现代密码学里的计算不可区分性,是一个非常严格的概念。
简单来说,它关注的不是:
text
某个模型能不能区分
而是:
text
任何有效攻击算法
是否都无法获得不可忽略的优势。
而这里实际测试的是:
text
一个特定 MLP
+
一个特定训练集
+
一个特定测试集
+
一种特定训练配置
因此最多只能说明:
当前测试条件下,这个 MLP 没有发现明显的区分特征。
这仍然是有价值的实验结果,但证据强度和密码学安全证明完全不同。
未来甚至可以尝试:
text
MLP
CNN
Transformer
传统统计分类器
人工构造统计特征
然后观察不同区分器能否找到规律。
这样才会逐渐形成更有意义的经验性安全画像。
十、另一个实验:密码改一个数字,会发生什么?
这里做的是口令敏感性测试。
例如:
text
原密码:
1234567890...
修改:
1234567891...
只改变一个十进制字符,然后分别产生 4096 字节密钥流。
计算两份密钥流之间的 Hamming Distance。
结果:
text
平均 HD = 0.4870
另一次相同协议的密钥敏感性实验:
text
平均 HD = 0.4854
理论上,如果两个完全独立的随机比特串比较,期望值是:
text
HD = 0.5
所以:
text
0.4870
0.4854
确实非常接近 0.5。
这说明当前网络和后续状态变换对口令扰动具有较强的敏感性。
但这里也需要纠正一个术语。
原始实验曾把这个结果直接称为:
SAC(严格雪崩准则)
其实并不准确。
因为实验改变的是:
text
一个十进制字符
而不是:
text
一个二进制 bit
严格 SAC 实验通常需要逐 bit 修改输入,然后观察每一个输出 bit 的翻转概率。
因此更准确的名字应该是:
单字符口令扰动下的密钥流 Hamming 距离测试。
这是一个更小、更具体、但也更可信的结论。
十一、50 位数字是不是就意味着 166 bit 安全?
不能这么简单理解。
如果:
text
50 位数字
是由密码学安全随机数生成器均匀产生的,那么:
1050≈2166.10
确实具有约 166 bit 的理论最大熵。
但是如果这是人自己设置的:
text
111111111111111...
123456789012345...
888888888888888...
生日、电话号码、身份证号码......
实际熵可能远远低于 166 bit。
所以:
密钥空间 ≠ 用户实际密码熵。
这是口令密码系统里非常经典的问题。
PBKDF2 能够做什么?
它可以增加每次密码猜测的计算成本。
但:
text
低熵密码
+
PBKDF2
不会突然变成:
text
166 bit 随机密钥
这一点在设计口令加密系统时尤其重要。
十二、PBKDF2 的角色其实没有想象中那么大
当前实现使用:
text
PBKDF2-HMAC-SHA256
600,000 iterations
来生成 HMAC 密钥。
这对认证密钥派生是有意义的。
但是一个容易被忽略的问题是:
当前的密钥流生成并没有使用这个 PBKDF2 派生出来的密钥作为主要输入。
也就是说:
text
PBKDF2
↓
HMAC key
而另一边:
text
原始口令
↓
神经网络
↓
密钥流
因此不能简单地说:
"600,000 次 PBKDF2 让密钥流穷举也变得非常慢。"
当前实现并不是这样工作的。
这也是为什么原先根据"0.3 秒一次 PBKDF2"直接推算几十亿次 GPU 猜测能力的做法并不严谨。
真正需要回答的问题是:
text
攻击者测试一个候选口令
↓
到底必须执行哪些步骤?
↓
每一步需要多少计算?
↓
GPU 能并行多少?
只有 benchmark 之后,才能比较可信地估计离线攻击成本。
十三、这个实验最大的价值,可能恰恰是发现了方案的问题
如果只看最初的实验结果,很容易得出一个非常漂亮的结论:
text
HD ≈ 0.49
MLP ≈ 0.5
字节分布 ≈ 均匀
166 bit 密钥空间
然后宣布:
神经网络加密成功。
但真正把论文重新审查一遍后,得到的结论反而更加有意思。
这个方案确实表现出一些值得研究的性质:
1. 输入敏感
修改一个十进制字符后,密钥流 Hamming 距离接近 0.5。
2. 简单机器学习区分器没有找到明显差异
MLP 测试:
text
Accuracy = 0.5115
AUC = 0.4780
接近随机基线。
3. 最终密文的字节分布接近均匀
例如:
text
0x00 = 0.3950%
与理论值:
text
0.3906%
非常接近。
但是与此同时,又发现:
4. 神经网络密钥流本身存在严重偏置
text
0x00 = 8.3218%
这意味着当前的:
text
float32 → uint8 → XOR
提取方式存在明显问题。
5. NIST 类统计测试并不等于密码学安全证明
统计测试能发现某些问题,但不能证明:
text
抗差分攻击
抗线性攻击
抗选择明文攻击
抗密钥恢复攻击
抗状态恢复攻击
6. 一个 MLP 也不能证明 IND-CPA
它只能回答:
这个 MLP 在这个测试集上有没有学到明显的区分特征?
十四、下一步真正应该怎么改?
如果这个项目继续做下去,我认为最值得优先解决的不是继续堆更多神经网络层,而是先把密码学基础补齐。
第一件事,是把普通随机接口替换成真正的 CSPRNG,用于生成 IV、nonce 和其他随机参数。
第二件事,是重新设计密钥派生:
text
password
↓
KDF
↓
加密密钥
认证密钥
而不是让不同功能直接共享原始口令。
第三件事,是解决:
text
float32
↓
raw bytes
导致的偏置问题。
如果还希望保留神经网络,可以考虑:
text
SIREN state
↓
规范化编码
↓
SHA-256 / SHA-3 等标准密码学提取
↓
key stream
至少这样不会直接把 IEEE 754 的内部结构误当成随机字节。
第四件事,是重新做真正意义上的 bit-level SAC/BIC 实验。
第五件事,是进行真正的密码分析:
text
差分分析
线性分析
相关分析
已知明文攻击
选择明文攻击
状态恢复攻击
nonce 重用分析
第六件事,是把它和成熟方案正面比较:
text
AES-GCM
ChaCha20-Poly1305
SIREN + 自定义 ARX
比较:
text
吞吐量
CPU 占用
GPU 占用
延迟
密文统计特征
随机数要求
实现复杂度
攻击面
这样研究价值会比单纯证明"输出看起来像随机数"高得多。
十五、最后的结论
这项实验目前最合理的结论,并不是:
"我们设计了一个可以替代 AES 的神经网络加密算法。"
而是:
我们构造了一种结合 SIREN 神经网络和 ARX 类运算的实验性文件加密方案,并通过统计测试、机器学习区分器和口令扰动实验,对其若干可观测性质进行了分析。实验显示,该方案在特定测试条件下具有较强的输入敏感性,MLP 区分器未观察到明显的区分优势;与此同时,实验也发现了神经网络浮点输出直接转换为字节所导致的显著密钥流偏置,以及随机数来源、密钥派生和形式化安全分析方面的不足。
换句话说:
现在它更像一个有研究价值的实验性密码构造,而不是已经证明安全的密码系统。
这其实并不是一个坏结论。
对于一个研究型项目来说,能够明确回答:
text
哪里表现良好
哪里存在问题
哪些实验支持了哪些结论
哪些结论目前不能说
下一步应该怎么验证
往往比简单地得到一个"安全"更加重要。
密码学最怕的不是算法复杂。
最怕的是我们把"看起来随机"误认为"已经安全"。
而这次实验最大的收获,恰恰是把这两件事区分开了。