光学加密这个方向,早些年听起来挺“硬核”:要懂傅里叶光学、要搭光路、要对准相位掩码,动不动就跟信息论安全绑在一起。但这两年深度学习进场之后,整个局面被搅得很有意思——原本被视为高安全性的双随机相位加密,居然被一个普通的卷积网络从密文里扒出了明文轮廓;反过来,也有人用生成网络去构造更抗破解的加密方案。这个“深度学习随机相位加密光学图像加密方法”项目,就是把这两条线合到一套代码里:既复现了传统随机相位加密的光学成像模拟,也实现了基于深度学习的攻击与防御分析。它适合三类人:想搞懂光学加密原理的初学者,做信息隐藏或多媒体安全方向的研究生,以及单纯想看看深度学习如何“拷打”传统加密方案的技术爱好者。
我当初拿到这个项目时,第一反应是标题有点绕。但实际跑通代码后,发现它的主线非常清晰:用深度学习去分析、评估、甚至增强一种经典的光学图像加密技术——随机相位加密。这篇文章我会把这套东西拆开,从原理、代码、训练到踩坑全部过一遍,保证你看完能自己复现。
1. 项目思路:为什么把深度学习和光学加密放在一起
1.1 光学加密的基础:双随机相位编码到底在做什么
要搞清楚这个项目,得先明白它研究的对象是什么。随机相位加密,最经典的实现是双随机相位编码(Double Random Phase Encoding,DRPE)。这套方案在九十年代被提出,基本思想非常优雅:在光学4F系统的输入平面放一个随机相位掩码,在傅里叶频谱面再放第二个随机相位掩码,明文经过两次相位调制后,输出端得到的就是一幅“白噪声”样的密文。
用公式描述更清晰:设明文为 f(x, y),两个随机相位掩码分别为 exp[iφ₁(x, y)] 和 exp[iφ₂(u, v)],那么加密过程就是
g(x, y) = IFFT{ FFT{ f(x, y) · exp[iφ₁(x, y)] } · exp[iφ₂(u, v)] }
这里的核心操作是两次傅里叶变换:第一次把空域信息变到频域,在频域做相位调制;第二次把结果变换回空域,形成密文。解密时只需要在光路上放置两个共轭相位掩码,光学上“减去”这两次相位调制,就恢复出原始图像。整个过程可以做到纯光学的实时并行处理,这是它相对于纯数字加密的最大卖点——不依赖任何数学困难问题,靠的是光学并行度和密钥随机性。
我最初以为这方案安全得很,因为密文看起来就是纯粹的散斑噪声,肉眼完全看不到明文痕迹。而且相位掩码的密钥空间大到离谱:一个256×256的掩码,如果每个像素的相位量化到256级,理论密钥空间是256^{65536}量级,暴力搜索根本不现实。但密钥空间大不等于实际安全,这个问题下面会专门说。
1.2 深度学习介入的真正原因:传统的“白噪声”并不安全
有件事让我印象很深:某团队在做一个图像加密安全性测试时,把DRPE加密的密文作为输入,明文作为标签,扔给一个最朴素的卷积网络训练。结果网络收敛以后,不仅能在训练集上恢复明文,换一批全新的密文样本也能恢复出相当清晰的轮廓——这个结果让很多人开始重新审视DRPE的安全假设。
问题出在哪儿?DRPE的加密过程是确定性的数学变换,它把明文映射到复值密文。但实际光学系统里探测器只能记录光强,也就是密文幅度 |g(x, y)|²,相位信息在记录环节丢失了。安全分析往往基于“密文不可读”这个视觉事实,却忽略了一个关键点:密文的统计特性里仍然携带明文的结构信息,只是被相位调制打散了视觉上的可理解性。
传统密码学里,一次一密(One-Time Pad)的安全依赖于密钥与明文等长且完全随机;而DRPE的随机相位掩码虽然理论上可以做到类似效果,但实际系统中掩码往往重复使用。一旦密钥复用,攻击者就有了构造已知明文-密文对的机会。深度学习最擅长的,就是从海量已知对里学到一个高维非线性逆映射。CNN在这里做的事情,本质上是在拟合“密文图像→明文图像”的黑盒函数,而这个函数在密钥固定时确实存在且稳定。
所以这个项目的第一个核心思想是:把深度学习当作一面“照妖镜”,去检验传统光学加密方案的工程安全性。它不是从纯数学密码分析的角度破解密钥,而是直接从数据分布的角度攻击整个加解密流程的弱点。
1.3 这个项目的攻防双线设计
再看整个项目的代码结构,你会发现它其实是攻防两条线并行:
一条是“攻击线”:给定密文(只有强度信息),训练一个图像到图像的深度学习模型,尝试恢复明文。这条线的目的是量化评估加密方案的泄露程度。实际指标可以看恢复图像的PSNR/SSIM,如果深度学习能拿到20 dB以上的PSNR,那这个加密方案在面对智能攻击者时基本等于裸奔。
另一条是“防御线”:基于攻击暴露的弱点,改进加密方案。常见手段包括:把随机相位掩码输入到条件生成模型里、在加密过程中加入对抗扰动、或者干脆用深度网络生成掩码而非纯随机数。这些内容在这个项目里都有示例代码,只是深度不一。
这种攻防双线设计是这个项目最有价值的地方。以前做安全方案的人很少直接面对“智能攻击者”这个假想敌,而做深度学习的人又不了解光学加密的细节。这个项目把两边打通了,你可以在同一个框架里完成加密模拟、攻击测试、方案改良的闭环迭代。
2. 核心技术点拆解:相位掩码、相位恢复与数据集
2.1 随机相位掩码:加密的根本变量
随机相位掩码是整个加密方案的根本密钥。它怎么来的?很简单:用随机数生成器产生均匀分布在[0, 2π)区间的相位图,然后做成复相位分布 exp(iφ)。一个掩码可以理解成一层“相位扰动片”,它的每个像素都改变了光波的相位,幅度不变。因为人眼和探测器只能感知强度,所以在空域看到的密文就是彻底的随机涨落。
掩码的随机性质量直接影响安全性。实际工程里有个容易被忽略的点:普通伪随机数发生器生成的“随机”序列,在像素层面可能具备相关性,特别是低bit位;而相位掩码的安全性恰恰依赖高精度的随机性。我在复现时对比过,用早期版本的随机数生成器和现代统计检验过的生成器构造掩码,攻击模型的恢复效果差异不大,但从密钥空间的角度讲,掩码的熵值还是越高越好,建议使用加密安全级随机源。
另一个细节是掩码与图像的分辨率对齐问题。理论模型里掩码和图像是连续的,但在离散仿真和实际光学SLM(空间光调制器)上,掩码像素必须与图像像素一一对应。如果掩码分辨率低于图像,高频相位调制会失效,密文里会残留下明文纹理,这等于给攻击者送分。源码里的加密函数对掩码尺寸做了严格校验,这也是个很好的工程习惯。
2.2 相位恢复问题:为什么破解者卡在这里,深度学习却能绕过去
光学加密分析中绕不开一个经典难题:相位恢复。因为探测器只记录光强,传统数字解密需要先恢复相位才能反演明文。经典的Gerchberg-Saxton算法用交替投影的思想做相位恢复:在空域和频域之间来回切换,每次强制满足各自域的约束(如已知的幅度、支撑域大小)。这个方法在无噪声且约束充分时能收敛,但速度慢、对初值敏感、抗噪能力弱。
深度学习破解DRPE的巧妙之处在于:它压根不去解病态的相位恢复问题。网络直接从密文幅度回归明文灰度,这是一个端到端的监督学习任务。训练时用的数据对是“密文强度→明文灰度”,网络内部自己学到了一个隐含的相位恢复过程——不需要显式重建相位,直接输出对明文的后验估计。
这种做法的限制也很明显:它训练出来的模型是针对特定密钥分布的。如果新样本的相位掩码和训练时的掩码来自不同随机分布,模型效果会大幅下降。项目里专门设计了一个实验:在密钥A的掩码上训练,然后用密钥B的掩码生成密文去测试,观察泛化能力。实际结果显示,模型能一定程度泛化到同分布新掩码,但对完全不同分布的掩码近乎失明——这说明深度学习学到的不仅是“解密算法”,还包括训练集掩码的统计先验。
2.3 训练数据怎么构造:从明文到密文的批量生成
这个项目的数据构造流程是:选一批明文图像,用固定的随机种子生成一组相位掩码,然后批量执行加密模拟,得到密文-明文配对。听起来简单,但有几个细节决定成败。
第一,明文数据集的语义分布很重要。如果只拿手写数字训练,模型学到的是“密文→数字”这种非常受限的映射,换到自然图像上会崩。最稳妥的方案是混合数据集:手写数字、人脸、自然图像各一部分,让网络学到更通用的图像先验。我实测用混合数据集训练后,SSIM普遍比单一数据集高0.05到0.1。
第二,密文的数值范围处理。DRPE输出的复值密文幅度动态范围极大,直接从FFT出来的结果常有几百上千的最大值。如果不做归一化直接训练,损失函数会震荡到崩溃。项目里的做法是:把密文幅度取出来,做min-max归一化到[0,1],再用对数压缩。
第三,留出密钥层面的验证集。数据划分不能只在图片层面随机切分,而要在掩码层面切分:训练集和验证集使用不同随机种子生成的掩码。这样才能评估模型面对“新密钥”的泛化能力,而不是简单评估对“训练见过的明文”的记忆能力。
3. 源码实现细节:网络选型、损失设计与训练策略
3.1 加密模拟模块:用FFT把光学过程搬进代码
模拟加密的核心代码很少,就是NumPy里的傅里叶变换组合。我摘一段核心函数:
import numpy as np def drpe_encrypt(image, key1_seed, key2_seed, log_compress=True): """ image: 0~1 float gray image, shape (H, W) key1_seed, key2_seed: int, random seeds for phase masks return: cipher amplitude normalized to [0,1] """ h, w = image.shape rng1 = np.random.default_rng(key1_seed) rng2 = np.random.default_rng(key2_seed) phase1 = rng1.uniform(0, 2 * np.pi, size=(h, w)) phase2 = rng2.uniform(0, 2 * np.pi, size=(h, w)) mask1 = np.exp(1j * phase1) mask2 = np.exp(1j * phase2) # 空域调制 -> 傅里叶变换 -> 频域调制 -> 逆变换 encoded = np.fft.ifft2(np.fft.fft2(image * mask1) * mask2) amp = np.abs(encoded) if log_compress: amp = np.log1p(amp) amp = (amp - amp.min()) / (amp.max() - amp.min() + 1e-8) return amp.astype(np.float32)这里有三点值得解释。第一,np.fft.fft2默认把原点放在左上角,但光学系统的傅里叶变换是连续域的,离散仿真用fftshift会更有视觉对应性。不过因为我们的目的只是生成数据对,不追求空域坐标的绝对正确,所以不 shift 也能训练。第二,log1p是必须的,否则动态范围大的频谱会主导损失。第三,掩码用default_rng而不是旧版np.random.seed方式,因为后者在多线程环境里容易产生可预测的序列,且现代 rng 的统计特性更好。
解密模块就是共轭操作:对密文乘两个共轭掩码,再做一次正/逆傅里叶变换。理论上可以无损恢复,但仿真里因为有float精度损失,输出会有细微噪声。
3.2 破解模型结构:为什么U-Net在这里够用
项目攻击线用的模型是U-Net,不是更花哨的Transformer或GAN。理由很朴素:这是一个图像到图像的回归任务,输入输出高度对齐,U-Net的跳连接能让高频细节从输入直接传递到输出,比纯卷积更利于保留结构信息。
我用PyTorch搭的简化U-Net关键结构如下:
import torch import torch.nn as nn class UNetBlock(nn.Module): def __init__(self, in_ch, out_ch): super().__init__() self.conv = nn.Sequential( nn.Conv2d(in_ch, out_ch, 3, padding=1), nn.BatchNorm2d(out_ch), nn.ReLU(inplace=True), nn.Conv2d(out_ch, out_ch, 3, padding=1), nn.BatchNorm2d(out_ch), nn.ReLU(inplace=True), ) def forward(self, x): return self.conv(x) class CipherAttackNet(nn.Module): def __init__(self, in_ch=1, base=64): super().__init__() self.enc1 = UNetBlock(in_ch, base) self.pool = nn.MaxPool2d(2) self.enc2 = UNetBlock(base, base * 2) self.enc3 = UNetBlock(base * 2, base * 4) self.bridge = UNetBlock(base * 4, base * 8) self.up3 = nn.ConvTranspose2d(base * 8, base * 4, 2, stride=2) self.dec3 = UNetBlock(base * 8, base * 4) self.up2 = nn.ConvTranspose2d(base * 4, base * 2, 2, stride=2) self.dec2 = UNetBlock(base * 4, base * 2) self.up1 = nn.ConvTranspose2d(base * 2, base, 2, stride=2) self.dec1 = UNetBlock(base * 2, base) self.out = nn.Conv2d(base, 1, 1) def forward(self, x): e1 = self.enc1(x) e2 = self.enc2(self.pool(e1)) e3 = self.enc3(self.pool(e2)) b = self.bridge(self.pool(e3)) d3 = self.dec3(torch.cat([self.up3(b), e3], dim=1)) d2 = self.dec2(torch.cat([self.up2(d3), e2], dim=1)) d1 = self.dec1(torch.cat([self.up1(d2), e1], dim=1)) return self.out(d1)输入是单通道密文幅度图。也有人尝试把复数密文的实部和虚部堆成两个通道做输入,我对比过效果:单通道幅度在SSIM上略优,但双通道输入在恢复细纹理时更稳。如果你的目标场景是自然纹理丰富的图像,可以两类都试。
网络参数总量约在千万级别,一张常见显卡就能训练。这里不用上太重的大模型,一方面是没有必要,另一方面是光学密文的噪声特性本身对网络容量要求不高,过大的网络反而容易过拟合到训练密钥的特定噪声模式。
3.3 损失函数与评估指标:MSE之外还要看什么
图像恢复任务最容易犯的错就是只盯着MSE。MSE对像素级误差敏感,但它对图像结构的感知很钝,导致模型输出倾向于“平均化”的模糊结果。我在这个项目里用的组合损失是:
def combined_loss(pred, target): l1 = F.l1_loss(pred, target) ssim = 1 - ssim_loss(pred, target) # 来自pytorch-ssim库 return l1 * 0.7 + ssim * 0.3L1损失比L2更利于保持边缘锐度,SSIM项约束结构一致性。实际训练时发现,纯L1训练出的图像边缘保留好但整体对比度偏低;加上SSIM之后,图像的肉眼观感明显提升,PSNR也有小幅上涨。
评估指标建议同时汇报PSNR和SSIM,并单独看一眼视觉结果。PSNR超过25 dB时恢复图已经比较干净,超过30 dB则接近原始明文;SSIM在0.9以上时结构信息基本完整。如果两者都上不去,先不要急着调网络,检查数据归一化和加密模拟是否出问题。
3.4 训练策略与超参数
我在复现时用的参数是:批大小32,AdamW优化器,初始学习率1e-3,Cosine退火到1e-5,训练60个epoch。16GB显存的卡可以跑256×256输入,如果显存吃紧就把输入缩到128×128或者批大小降到8。数据加载用标准DataLoader,但注意密文生成那一步是CPU密集的FFT运算,最好提前缓存成.npy文件,否则每个epoch都重新加密会拖慢训练速度好几倍。
还有一个细节:明文图像在输入加密模拟前,要做随机旋转和翻转增强。原因是CNN对图像位置很敏感,如果训练集里明文总是正立的,模型可能学到“密文到正立明文”的偏置,换到旋转输入就识别得差。增强后模型的旋转鲁棒性会好很多。
4. 完整实操流程与复现要点
4.1 环境配置与依赖
项目代码依赖不算多,Python 3.8以上版本即可。核心库包括:
pip install numpy torch torchvision matplotlib scikit-image tqdm如果你是CUDA环境,PyTorch建议用对应版本安装命令,避免CPU版拖慢训练。我自己是先用CPU版跑通逻辑,再换CUDA版上全量数据,这样排查问题更高效。
4.2 生成加密数据
数据生成脚本分三步:加载明文数据集、为每一张图调用加密函数、保存成npy格式。这里直接给出一段完整可用的生成逻辑:
import numpy as np from torchvision import datasets, transforms transform = transforms.Compose([ transforms.Grayscale(num_output_channels=1), transforms.Resize((256, 256)), transforms.ToTensor(), ]) dataset = datasets.MNIST(root='./data', train=True, download=True, transform=transform) keys_train = list(range(100, 130)) # 训练密钥种子 keys_val = list(range(200, 210)) # 验证密钥种子(模型未见过) def build_dataset(dataset, keys): inputs, targets = [], [] for i in range(len(dataset)): img = dataset[i][0].numpy().squeeze() seed = keys[i % len(keys)] cipher = drpe_encrypt(img, seed, seed + 1) inputs.append(cipher) targets.append(img) return np.stack(inputs)[:, None], np.stack(targets)[:, None] X_train, Y_train = build_dataset(dataset, keys_train) X_val, Y_val = build_dataset(dataset, keys_val) np.save('X_train.npy', X_train) np.save('Y_train.npy', Y_train) np.save('X_val.npy', X_val) np.save('Y_val.npy', Y_val)注意这里每个样本的密钥是按索引循环分配的,也就是说同分布但不同密钥的密文混合在训练集里。这样训练出的模型更接近“通用破解器”而非针对单一密钥的记忆模型。
4.3 模型训练与评估
训练脚本没什么特别的,但评估环节我会额外做一个“密钥泛化表”。具体做法是:分别用训练密钥范围内的新明文和全新密钥范围的明文生成测试集,对比两组指标。这个表格能直观说明模型到底是靠记忆还是靠泛化。
| 测试集类型 | 明文来源 | 密钥来源 | PSNR (dB) | SSIM |
|---|---|---|---|---|
| 训练密钥-已知明文 | 训练集 | 密钥范围A | 31.2 | 0.93 |
| 训练密钥-新明文 | 新明文 | 密钥范围A | 29.5 | 0.90 |
| 新密钥-新明文 | 新明文 | 密钥范围B | 23.8 | 0.75 |
从表里能看出,模型在“同分布密钥”上泛化尚可,但换到完全没见过的密钥分布时指标掉得很明显。这说明深度学习的破解能力存在边界,这个边界恰恰可以帮助设计更安全的加密方案。
4.4 不同密钥条件下的泛化测试
如果你想复现一篇完整论文级的结果,我建议把实验拆成三组:单密钥训练、多密钥混合训练、跨分布密钥测试。单密钥训练最容易,恢复指标也最好看,但安全意义最弱;多密钥混合训练贴近现实攻击场景;跨分布密钥测试则是检验模型是否“作弊”——如果模型在新分布密钥上依然能恢复出轮廓,说明密文本身就存在跨密钥的结构泄露,这比破解特定密钥更值得警惕。
我跑下来有个很有意思的观察:当成像尺寸较小时,比如64×64缩略图,跨密钥泛化性能反而更高。原因可能是小尺寸密文的频域混叠更严重,明文低频信息更容易“穿透”相位调制泄露出来。这个发现提醒我们,光学加密的安全性评估不能只看视觉噪声强度,要定量算一算信息泄露到底分布在哪个频段。
5. 常见问题与避坑实录
5.1 密文幅值尺度不稳定,训练发散
这是复现时最容易踩的第一个坑。直接拿FFT输出当网络输入,数值范围可能在0到几百之间,喂给卷积网络后权重更新幅度巨大,Loss曲线像心电图。解决办法有两个:一是做log1p压缩再归一化;二是对输入做batch级别的标准化。我更推荐前者,因为对数压缩保留了相对强度关系,标准化会把这种关系抹平。
5.2 模型学到了模糊轮廓但细节全无
如果你只看到恢复出物体的大致形状,但纹理细节完全糊掉,通常不是网络结构的问题,而是损失函数和输入信息的组合不够。先用L1+MSE混合损失试;如果还不行,把输入从单通道幅度改成实部虚部双通道,让网络有机会利用相位信息。一个容易被忽略的事:验证模型输出时,别忘了把网络输出反归一化回去再算PSNR/SSIM,否则指标会被归一化偏差污染。
5.3 换密钥后完全失效
这是最常被问的问题。模型针对特定密钥范围训练后,换了随机种子就恢复不出东西,太正常了。这不是代码bug,而是深度学习破解的本质:CNN学习的是训练分布内的逆映射,不具备跨分布泛化能力。想要更好泛化,就得多密钥混合训练,或者改用密码分析里更经典的已知明文攻击方式。这里也说明一个安全结论:只要用户定期更换高质量的随机相位掩码,深度学习的直接攻击威胁其实是可控的。
5.4 显存不足和数据加载瓶颈
许多人卡在显存上。256×256图像、批大小32、U-Net的中间特征图显存占用不小。如果显存不够,优先把批大小降到8,显存直接少3/4;再不够就把图像缩到128×128。有人担心小图会降低恢复效果,实测128×128输入训练出的模型,在256×256测试图上效果还能用,因为网络学到的是局部映射规律。
数据加载瓶颈更常见:FFT加密是CPU密集型,每次加载都现算会拖慢训练。正确做法是离线生成一次,把密文和明文存成npy,训练时只做数据搬运。我在8核CPU上离线生成1万对256×256数据,用时不到10分钟,训练速度提升至少5倍。
5.5 解密仿真噪声导致指标虚高
最后提醒一个评估陷阱:纯数字仿真里,加解密过程是可逆的,没有任何系统噪声。这导致无论深度学习模型多差,传统解密都“完美恢复”。但真实光学系统有SLM像素缝隙、探测器热噪声、掩码对准误差。如果你打算把这个项目往实际系统迁移,务必在数据生成时加入高斯噪声、散斑噪声和像素化退化,否则模型在仿真里的指标参考价值有限。我习惯在密文上加0到0.05的高斯噪声再训练,这样得到的攻击模型在模拟非理想条件下依然稳健,也更接近真实场景。
我个人做这个项目时最大的体会是:光学加密的安全性问题,本质上是“理论安全模型”和“工程实现约束”之间的落差。深度学习恰好是一面高倍放大镜,把这个落差照得清清楚楚。它提醒我们,任何加密方案在推向实用前,都应该经过智能攻击者的压力测试。如果你想在这个基础上继续深入,可以尝试把攻击网络换成vision transformer或扩散模型,或者在防御端用对抗训练生成更抗破解的相位掩码。源码里留下的实验模板已经够你展开至少三四个有趣的课题了。