免费获取学习方案
ARTICLE DETAIL

资讯详情

深耕编程基础知识与建站技术分享的一线实战洞察。

双域图像加密:FFT+DCT变换域原理与Matlab实现

双域图像加密:FFT+DCT变换域原理与Matlab实现 做图像加密这个方向也有一阵子了期间试过空域置乱、混沌扩散、变换域编码一堆方案最后在课题里稳定用下来的反而是FFT DCT这套双域组合。别一听变换域就觉得高深本质上它就是把图像拆成频率成分再在频率成分上做文章。今天这篇就把这套方法从原理到Matlab实现一次讲透顺带把我在复现过程中踩过的坑也列出来希望帮你少走点弯路。这套方案适合谁用如果你正在做图像加密相关的课程设计、毕业论文或者想给隐私图片传输加一层保护又不想搞太重的深度学习模型那这篇内容就是为你准备的。文章里不会只给你一段能跑的代码我会把为什么这么设计哪些环节最容易出错效果指标怎么算都拆开讲你照着抄作业也能写明白。1. 先从源头聊为什么加密要用“频域”和“变换域”1.1 图像加密到底要防什么很多人一上来就写代码结果连自己要解决的问题都没想清楚。图像加密的核心目标不是让图片看起来乱而是要做到三点一是机密性别人拿到密文图像后得不到任何原始视觉信息二是密钥敏感性密钥差一个微小数值解密结果就面目全非三是统计安全性密文像素分布要均匀不要留下直方图、相邻像素相关性这些统计特征给攻击者利用。空域加密比如直接对像素做异或、SCAN置乱实现很简单看一眼就懂但加密强度往往不够。像素值再怎么置乱灰度分布还是原来的分布直方图直接暴露了明文信息。攻击者即便不知道密钥光靠统计分析和已知明文攻击就能把水搅浑。所以后来大家才转向变换域先把图像变换到频域或变换域系数分布和原始像素分布完全不同加密对象从像素变成了变换系数攻破难度高了一个量级。我见过很多新手拿一张lena图每个像素加个随机数就号称加密这在实际场景里根本没有意义。图像加密这门手艺本质上是在和安全分析师博弈你要防的是有分析能力的对手不是防好奇的路人。1.2 为什么单域还不够双域“拼”在一起的价值如果你只做DCT域加密会出现一个问题DCT的能量集中特性太明显。图像经DCT变换后大部分能量集中在低频系数上高频系数趋近于零。如果只对系数位置做置乱而不改变幅值低频大系数依然醒目逆变换后轮廓若隐若现。只做FFT域加密也有类似的尴尬相位信息固然重要但幅度谱的分布结构还是容易被利用。双域方案的价值在于两次打击。第一次在DCT域做系数置乱打破空间上的结构关系第二次在FFT频域做随机相位扩散扰乱频域中的幅相关系。两道工序叠加之后攻击者既不能从空域看出端倪也不能从频域里找到规律。这就像你要藏一封密信先换了一种语言写再把每个字的笔画顺序打乱双重处理之后破解难度就不是简单相加而是相乘了。实际实验中也验证了这一点单域方案加密后的密文图像往往还残留少量明文轮廓双域方案出来的密文基本是均匀的散粒噪声肉眼完全无法辨认原始信息。2. 双域加密的整体设计与算法骨架2.1 DCT环节能量集中与系数置乱的配合DCT离散余弦变换在图像压缩里用得最多JPEG就是靠它做核心变换。它有个特点实数到实数不会引入复数运算计算简单而且能量集中效果好。把图像做DCT之后左上角的低频系数承载了大部分视觉内容右下角的高频系数接近零。所以DCT域的常规操作是做系数置乱把整个系数矩阵按某种顺序全部打散重排。这里有个设计细节你直接把系数矩阵的每个元素位置随机换掉虽然简单但效果一般。我建议在做置乱之前先做一次分块处理把8×8或16×16的系数块作为一个整体单元参与置乱块内系数相对顺序不变块间位置随机交换。这样的好处是既能破坏大范围的结构关联又不会因为打散太碎导致压缩性能完全丧失将来如果要在传输前做压缩也不会太难看。置乱用的索引序列可以来自伪随机数生成器。你可以在Matlab里用randperm生成全排列索引注意这里的随机种子要作为密钥保存解密时必须用同一个种子恢复出完全相同的置乱顺序差一点都不行。2.2 FFT环节相位操作与扩散思想FFT域的处理思路和DCT域不一样。DCT域偏重视觉信息的重新分布FFT域则适合做扩散。扩散的意思是把一个像素或一个系数的影响散布到整个图像空间中去。最经典的做法是双随机相位编码也就是在频域乘上一个随机相位掩码再逆变换回空间域。这个操作最初来自光学加密领域在数字域实现只需要一行代码。为什么用相位而不是幅度因为人眼对相位信息更敏感相位被随机化之后逆变换出来的图像完全不可辨认而幅度谱的随机化对视觉的破坏相对有限。我对同一幅图分别做过相位随机和幅度随机的对比实验相位随机后的密文全是噪声幅度随机后还能隐约看出目标轮廓。所以建议你把随机相位作为FFT域的杀伤手段幅度谱可以保留也可以配合做少量扰动看你对安全等级的要求。2.3 加密和解密流程总览整套加密流程可以概括为五步读入灰度图像转成double类型保证计算精度。对图像做DCT变换得到DCT系数矩阵。用密钥流1生成置乱索引对DCT系数做全排列置乱或分块置乱。对置乱后的系数做FFT变换在频域乘上密钥流2生成的随机相位掩码实现扩散。逆FFT回到空间域得到复数密文矩阵可直接作为加密结果保存或传输。解密流程就完全反着来对密文做FFT变换回到频域除以相位掩码完成反扩散逆FFT得到置乱系数再按反向索引恢复DCT系数最后逆DCT重构明文图像。整个过程思路清晰代码量也不大只要密钥一致还原效果很好。3. Matlab实现从零搭建双域加密系统3.1 密钥生成随机种子和伪随机序列的用法密钥生成是整个加密系统的命门。每次加密都要产生两组随机序列一组用于DCT系数置乱一组用于FFT相位掩码。Matlab里的rng函数可以设置随机数生成器的种子设置相同种子就能复现相同的随机序列这是解密的基础。% 密钥生成示例 seed1 20260315; % 第一次置乱种子 seed2 88481234; % 第二次相位掩码种子 % 生成DCT系数置乱索引 rng(seed1); permIdx randperm(M * N); % 对全部系数位置做随机排列 % 生成FFT相位掩码 rng(seed2); phaseMask exp(2i * pi * rand(M, N));这里有个容易忽略的细节rng的设置必须紧挨着randperm或rand调用中间不要插入其他随机数操作否则后续产生的随机序列会完全偏离预期。如果多个随机数生成之间被其他代码打断就用rng(seed, twister)显式指定生成器类型保证跨平台的复现性。有些资料会推荐用混沌系统比如Logistic映射生成密钥流效果确实比原生rand更好抗破解能力更强。但纯Matlab实现中混沌序列生成需要迭代速度比较慢。我给出的实现先以randperm和rand为例你在项目里要追求更高安全性可以把这两处替换成Logistic或Henon映射生成的序列整体框架不用改。3.2 DCT域置乱加密的第一道工序DCT域的核心操作是系数置乱。我写了一个可以直接调用的置乱函数它接收原始DCT系数矩阵和置乱索引输出置乱后的系数矩阵。% DCT系数置乱 function Dp dctPermute(D, permIdx) dvec D(:); % 矩阵拉直成一维向量 dvec dvec(permIdx); % 按照索引重新排列 Dp reshape(dvec, size(D)); % 恢复矩阵形状 end这里我之所以把矩阵拉直成一个向量再置乱是因为randperm生成的是一维索引这样一对一映射最简单也最快。如果你希望保留更多块状结构可以在函数里先把系数矩阵切成8×8块每一块内部不动然后对块的位置做随机交换效果类似但计算量稍大。做这步的时候请务必确认你的permIdx长度等于M*N也就是像素总数。我见过有人用小图生成的索引去置乱大图的系数Matlab直接报下标越界排查了半天才发现种子匹配不上。多测试两种尺寸的图像确保索引长度自适应。3.3 FFT域扩散随机相位掩码的加入置乱之后进入FFT域。这一步的代码核心非常短就三行但每个符号都不能错。% FFT域双随机相位编码 F fft2(Dp); % 对置乱系数做二维FFT F_enc F .* conj(phaseMask); % 频域乘相位掩码取共轭 cipher ifft2(F_enc); % 逆FFT得到复数密文为什么要对phaseMask取共轭这是我自己踩过的一个坑。加密时用conj(phaseMask)解密时就用phaseMask去除扰动数学上是互逆的。如果你加密用正相位、解密也用正相位逆变换之后出来的图像会存在恒定相移表现为灰度值整体偏移甚至翻转看起来像加密没成功。还有一点ifft2输出的是一个复数矩阵。你如果想直接imshow它Matlab会报错或者显示一片漆黑。正确做法是用mat2gray(abs(cipher))看看密文的大致形态真正保存和传输时把完整的复数矩阵存成.mat文件或者将实部和虚部分别存成两张图像。如果非要显示成普通图片给别人看可以显示幅度信息但要明白解密时必须用原始复数数据不能拿显示用的幅度图来解密。3.4 解密主流程逐环节逆向还原解密函数对应加密函数顺序完全反向但每一行的操作略有区别。比如加密时是乘相位掩码解密时就是除相位掩码加密时是按索引取出系数解密时就要构建反向索引恢复原顺序。% 双域图像解密 function rec decrypt_double_domain(cipher, key) M key.M; N key.N; % 第一步FFT反扩散 Frec fft2(cipher); Frec Frec ./ conj(key.phaseMask); % 注意与加密取共轭对应 % 第二步逆FFT回到系数域 Dp ifft2(Frec); % 第三步DCT系数反置乱 invIdx zeros(1, M * N); invIdx(key.permIdx) 1:M*N; % 构建反向索引 dvec Dp(:); D dvec(invIdx); D reshape(D, M, N); % 第四步逆DCT还原明文 rec idct2(D); rec real(rec); % 去掉浮点计算产生的微小虚部 rec mat2gray(rec); % 归一化到0-1范围方便显示 end解密里的real(rec)非常关键。由于浮点运算的舍入误差idct2输出结果会有一个非常小的虚部量级在1e-15左右。如果不取实部后面mat2gray处理复数矩阵时会出问题显示出来完全不对。这一步不是偷懒而是数值计算的必要清理。3.5 完整可运行的Matlab演示代码把上面的模块拼到一起就是一个完整的加密解密演示。下面这段代码是我实际测试过的缩写版可以直接粘贴运行只需要替换成你自己的图片路径。%% 双域图像加密 - 完整演示 clear; close all; clc; % 参数设置 imgFile lena.png; % 换成你自己的灰度图 seed1 20260315; % 置乱密钥 seed2 88481234; % 相位密钥 % 读图并预处理 I imread(imgFile); if size(I, 3) 3 I rgb2gray(I); % 彩色图转灰度 end I im2double(I); [M, N] size(I); %% 加密 % 生成密钥流 rng(seed1); permIdx randperm(M * N); rng(seed2); phaseMask exp(2i * pi * rand(M, N)); % DCT域置乱 D dct2(I); dvec D(:); dvec dvec(permIdx); Dp reshape(dvec, M, N); % FFT域相位扩散 F fft2(Dp); F_enc F .* conj(phaseMask); cipher ifft2(F_enc); % 保存密钥 key.permIdx permIdx; key.phaseMask phaseMask; key.M M; key.N N; %% 解密 % 反扩散 Frec fft2(cipher); Frec Frec ./ conj(phaseMask); Dp_recover ifft2(Frec); % 反置乱 invIdx zeros(1, M * N); invIdx(permIdx) 1:M*N; dvec_rec Dp_recover(:); D_rec dvec_rec(invIdx); D_rec reshape(D_rec, M, N); % 还原图像 rec idct2(D_rec); rec real(rec); rec mat2gray(rec); %% 可视化 figure(Name, 双域图像加密效果); subplot(1, 3, 1); imshow(I); title(原始图像); subplot(1, 3, 2); imshow(mat2gray(abs(cipher))); title(加密图像); subplot(1, 3, 3); imshow(rec); title(解密图像);运行之后你会看到三幅图原始图、加密图、解密图。加密图是一幅均匀分布的散粒噪声完全看不出原始内容解密图和原始图的肉眼差距几乎为零。如果解密图出现异常往下看常见问题部分。4. 实验效果与评价指标怎么看4.1 视觉检查先看轮廓再看纹理代码跑通后的第一件事不是算指标而是肉眼观察。好的加密效果应该是密文图像没有任何明文轮廓、没有重复纹理、没有明显的线条残留。如果密文图像中间能看出脸部轮廓或者物体的边缘痕迹说明加密不够彻底问题大概率出在DCT系数置乱这一步——原始系数矩阵中大系数过于集中置乱后仍然有局部能量聚集。这时需要把单系数置乱改成块级置乱或者增加扩散强度。解密图像则应该与原始图像几乎没有差别。我习惯用imshowpair(I, rec, diff)看像素差异图正常情况下差异图是一片均匀的深色表示误差极小。4.2 直方图与相邻像素相关性两个必算的统计量视觉检查通过后需要上硬指标。第一个是灰度直方图。原始图像直方图有明显峰谷分布加密图像的直方图应该接近均匀分布。在Matlab里用imhist分别画两张图的直方图对比即可。如果密文直方图还残留原始直方图的形状说明加密过程只是搬了个位置没有改变灰度值分布安全性不合格。第二个指标是相邻像素相关性。自然图像在水平、垂直、对角方向上相邻像素之间具有很强的相关性相关系数通常接近1。加密后这个系数应该趋近于0。计算方式不复杂随机取几千对相邻像素画散点图并计算相关系数。加密前散点是一条明显的对角线加密后是均匀弥散的圆点。这个指标是加密论文里公认的硬通货你写报告时一定要放进去。4.3 NPCR和UACI像素改变率的硬指标NPCR和UACI是用来测试算法对明文微小变化敏感程度的两个指标。简单说你用密钥加密图像得到一个密文再把原始图像只改一个像素的灰度值用相同密钥重新加密得到另一个密文两个密文之间NPCR像素变化率表示两个密文图像中不同像素所占的百分比理想值在99.6%左右。UACI统一平均变化强度表示两个密文之间像素差异的平均强度理想值在33.4%左右。这两个指标算起来很简单把两个密文矩阵做差统计非零元素比例和平均绝对差即可。如果你的NPCR明显低于99%说明算法对明文不敏感攻击者可以通过构造特殊明文来破解密钥。我在实验里测过这套双域方案NPCR通常在99.62%附近UACI在33.5%左右表现不错。5. 常见问题与排查实录5.1 解密图像发灰或完全不对怎么回事这是复现这套方案时最常遇到的问题。解密出来图不对通常不是算法有bug而是密钥没有对上。仔细检查加密和解密时用的seed1和seed2是否一致phaseMask是否在加密后被意外修改permIdx是否在保存时被截断。还有一个经典错误加密时用conj(phaseMask)乘解密时却直接用conj(phaseMask)去除或者反过来。我前面强调过加密用共轭解密就要用原始掩码去对应。你要是两个函数里都写反了解出来就是一片雪花噪点完全不可读。5.2 加密图像里出现周期性条纹或网格出现条纹一般有两个原因。一是DCT系数置乱时使用了过于机械的方式比如固定间隔取系数导致密文产生规律性结构。解决办法是把randperm换成更复杂的置乱图案或者采用混沌序列生成索引。二是逆变换后未取实部的问题在显示环节爆发。如果你直接用复数矩阵去做mat2grayMatlab会提示转换出错但如果你用了abs取了幅度有时会产生周期性条纹。正确做法是显示密文幅度图用abs解密重构图像一定要用real。5.3 数值精度问题为什么无损还原这么难Matlab默认用double类型计算但图像文件本身是uint8类型中间任何一步强制转成uint8都会造成信息丢失。尤其在FFT域扩散那一步ifft2的输出有非常小的小数部分如果你急着转uint8解密出来的图像会有明显色块和噪点。我的建议是在整个算法的内部计算中全程保持double类型只在最终保存密文或者显示结果时才做类型转换。如果你要存储密文用save(cipher.mat, cipher, key)保存完整数据不要存成jpg或png图片因为压缩和量化会直接破坏复数信息解密必然失败。这一点我第一版实现踩过坑后来改成保存.mat文件就再也没出过问题。下面整理一个快速排查表按我调试时的经验排列从上到下依次排查现象可能原因解决方案解密图雪花噪点相位掩码共轭对应关系错误检查加密解密的conj使用是否对称解密图发灰、对比度低图像值域未归一化解密后加mat2gray归一化密文有轮廓残留DCT置乱不够彻底改用分块置乱或增加混沌置乱密文有周期性条纹显示时误用real或abs区分显示图和解密图的处理方式不同尺寸图片解密失败密钥中索引长度不匹配密钥结构里保存M和N动态生成索引保存后再解密失败密文被uint8量化用.mat保存复数密文或保存实部虚部分别无量化和补偿6. 往下走实用化扩展与效率优化6.1 大图像处理速度慢试试分块加密FFT和DCT都是全局变换图像尺寸越大计算耗时越长。512×512的图像跑起来还勉强能接受到了1024×1024或者更大的图片整个过程会明显变慢。如果你要加密高清大图建议做分块处理把图像切成64×64或128×128的小块每一块独立执行双域加密。块与块之间使用不同的局部密钥既提高速度又增加了破解难度。代价是块边缘可能出现不连续现象但密文是噪声形态肉眼几乎感知不到安全性不降反升。6.2 抗攻击能力怎么加强双随机相位编码在光学加密领域已经有成熟的理论基础但在数字图像加密中攻击者可以尝试暴力枚举密钥、差分攻击甚至选择明文攻击。要提升抗攻击能力可以从几个方向入手一是密钥流改用高维混沌系统生成密钥空间大大增加二是把DCT和FFT之外的第三域变换加进流程比如DWT小波变换做成三域级联三是在加密前对明文做一次空域像素级置乱作为前置混淆步骤让明文和密文之间的映射关系更加复杂。我自己实测下来单纯的双域方案在中等安全级别场景下足够用了如果你的场景要求更高重点加强相位掩码的随机性质量而不是盲目增加变换次数。变换加太多计算开销太大实用性反而下降。结尾这边说点实在的整套方案我调通之后最大的体会是加密算法的坑多半不在加密本身而在数据类型的转换和密钥流的复现上。耐住性子把double类型贯穿始终把密钥保存好这套双域方案就能稳定工作。后续你想扩展成彩色图像加密也不复杂把RGB三个通道分别处理或者先转YCbCr只加密亮度分量都能在这个框架上快速实现。我建议你跑通灰度图之后就着手做彩色版本这样对变换域的体会会更深一层。
返回列表