光学加密这个方向早些年听起来挺“硬核”要懂傅里叶光学、要搭光路、要对准相位掩码动不动就跟信息论安全绑在一起。但这两年深度学习进场之后整个局面被搅得很有意思——原本被视为高安全性的双随机相位加密居然被一个普通的卷积网络从密文里扒出了明文轮廓反过来也有人用生成网络去构造更抗破解的加密方案。这个“深度学习随机相位加密光学图像加密方法”项目就是把这两条线合到一套代码里既复现了传统随机相位加密的光学成像模拟也实现了基于深度学习的攻击与防御分析。它适合三类人想搞懂光学加密原理的初学者做信息隐藏或多媒体安全方向的研究生以及单纯想看看深度学习如何“拷打”传统加密方案的技术爱好者。我当初拿到这个项目时第一反应是标题有点绕。但实际跑通代码后发现它的主线非常清晰用深度学习去分析、评估、甚至增强一种经典的光学图像加密技术——随机相位加密。这篇文章我会把这套东西拆开从原理、代码、训练到踩坑全部过一遍保证你看完能自己复现。1. 项目思路为什么把深度学习和光学加密放在一起1.1 光学加密的基础双随机相位编码到底在做什么要搞清楚这个项目得先明白它研究的对象是什么。随机相位加密最经典的实现是双随机相位编码Double Random Phase EncodingDRPE。这套方案在九十年代被提出基本思想非常优雅在光学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_compressTrue): 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, padding1), nn.BatchNorm2d(out_ch), nn.ReLU(inplaceTrue), nn.Conv2d(out_ch, out_ch, 3, padding1), nn.BatchNorm2d(out_ch), nn.ReLU(inplaceTrue), ) def forward(self, x): return self.conv(x) class CipherAttackNet(nn.Module): def __init__(self, in_ch1, base64): 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, stride2) self.dec3 UNetBlock(base * 8, base * 4) self.up2 nn.ConvTranspose2d(base * 4, base * 2, 2, stride2) self.dec2 UNetBlock(base * 4, base * 2) self.up1 nn.ConvTranspose2d(base * 2, base, 2, stride2) 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], dim1)) d2 self.dec2(torch.cat([self.up2(d3), e2], dim1)) d1 self.dec1(torch.cat([self.up1(d2), e1], dim1)) 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 训练策略与超参数我在复现时用的参数是批大小32AdamW优化器初始学习率1e-3Cosine退火到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_channels1), transforms.Resize((256, 256)), transforms.ToTensor(), ]) dataset datasets.MNIST(root./data, trainTrue, downloadTrue, transformtransform) 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训练密钥-已知明文训练集密钥范围A31.20.93训练密钥-新明文新明文密钥范围A29.50.90新密钥-新明文新明文密钥范围B23.80.75从表里能看出模型在“同分布密钥”上泛化尚可但换到完全没见过的密钥分布时指标掉得很明显。这说明深度学习的破解能力存在边界这个边界恰恰可以帮助设计更安全的加密方案。4.4 不同密钥条件下的泛化测试如果你想复现一篇完整论文级的结果我建议把实验拆成三组单密钥训练、多密钥混合训练、跨分布密钥测试。单密钥训练最容易恢复指标也最好看但安全意义最弱多密钥混合训练贴近现实攻击场景跨分布密钥测试则是检验模型是否“作弊”——如果模型在新分布密钥上依然能恢复出轮廓说明密文本身就存在跨密钥的结构泄露这比破解特定密钥更值得警惕。我跑下来有个很有意思的观察当成像尺寸较小时比如64×64缩略图跨密钥泛化性能反而更高。原因可能是小尺寸密文的频域混叠更严重明文低频信息更容易“穿透”相位调制泄露出来。这个发现提醒我们光学加密的安全性评估不能只看视觉噪声强度要定量算一算信息泄露到底分布在哪个频段。5. 常见问题与避坑实录5.1 密文幅值尺度不稳定训练发散这是复现时最容易踩的第一个坑。直接拿FFT输出当网络输入数值范围可能在0到几百之间喂给卷积网络后权重更新幅度巨大Loss曲线像心电图。解决办法有两个一是做log1p压缩再归一化二是对输入做batch级别的标准化。我更推荐前者因为对数压缩保留了相对强度关系标准化会把这种关系抹平。5.2 模型学到了模糊轮廓但细节全无如果你只看到恢复出物体的大致形状但纹理细节完全糊掉通常不是网络结构的问题而是损失函数和输入信息的组合不够。先用L1MSE混合损失试如果还不行把输入从单通道幅度改成实部虚部双通道让网络有机会利用相位信息。一个容易被忽略的事验证模型输出时别忘了把网络输出反归一化回去再算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或扩散模型或者在防御端用对抗训练生成更抗破解的相位掩码。源码里留下的实验模板已经够你展开至少三四个有趣的课题了。