十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

超混沌系统+斐波那契Q矩阵图像加密:Matlab实现与安全分析

超混沌系统+斐波那契Q矩阵图像加密:Matlab实现与安全分析 图像加密这个方向这几年在论文和毕设项目里出现频率非常高。传统做法要么直接拿AES这类通用分组密码硬套图像要么用单混沌系统加置乱扩散的简单框架。前者处理图像这种高冗余数据效率偏低后者在已知明文攻击面前往往撑不住。所以我现在写图像加密相关的实现默认框架基本都是超混沌系统做密钥流再配合某种可逆矩阵做像素变换。今天拆解的方案就是典型的“超混沌系统 斐波那契Q矩阵”组合配合Matlab完整仿真代码。先给结论这套做法最大的价值不是单点用了多冷门的数学工具而是“置乱 扩散”两条链路都站得住而且Matlab代码跑起来非常直观特别适合做算法验证、毕设和期刊论文的起点。适合三类人看一是刚接触图像加密、想找一个能完整跑通全流程的同学二是想拿现成算法做安全性分析对比的研究人员三是想了解混沌加密如何在工程中落地的图像处理从业者。全文不废话直接拆原理、贴代码、给踩坑记录。1. 为什么图像加密偏爱“超混沌系统 斐波那契Q矩阵”这对组合1.1 图像数据与普通文本加密的本质差异很多人第一次做图像加密时会惯性思维地想到AES、DES或者RSA。这些经典算法当然能加密图像把图像当作字节流处理也不是不行。但问题是图像数据和文本数据有本质区别。文本数据本身冗余度低一个字节变了意思就差很远图像数据相邻像素之间存在极强的相关性用AES加密后虽然统计学上看起来安全但加密过程中每个像素独立处理没有充分破坏图像空间结构。更麻烦的是图像数据量大一次加密一幅高清图要处理几百万个像素纯分组密码在这种场景下速度很难看。图像加密真正需要解决的核心问题有三个像素位置置乱、像素值扩散、密钥流随机性。位置置乱解决的是“人眼可辨识”问题让像素位置打乱之后看不出原始轮廓像素值扩散解决的是“统计暴露”问题让相邻像素的灰度值不再有关系密钥流随机性解决的是“抗破解”问题如果密钥流的统计特征太明显攻击者可以通过差分分析、线性分析找回原始信息。混沌系统在这三个方面天然匹配这也是为什么过去二十多年混沌图像加密的论文铺天盖地。1.2 超混沌系统比普通混沌系统强在哪传统论文里最常见的混沌映射是Logistic映射公式简单、实现容易一维单变量迭代。但这类低维混沌系统存在明显的短板参数区间和混沌区间重叠部分并不大某些参数组合下会出现周期窗口生成的序列周期性明显而且单变量序列的随机性来源单一在已知明文攻击面前容易被还原出迭代轨迹。超混沌系统就是为解决这个问题出现的。它至少有两个正的Lyapunov指数意味着系统在多个方向上都存在指数分离的轨迹迭代序列的不可预测性大幅提升。通俗点说普通混沌系统是一个“比较乱的随机源”超混沌系统是“两三个独立随机源叠加在一起”的随机源。用在图像加密里最直接的好处是一次迭代可以同时产生多组混沌序列分别用于置乱和扩散密钥流之间的关联性更低抗破解能力更强。比较常用的超混沌系统包括超混沌Lorenz系统、超混沌Chen系统、超混沌Rossler系统。它们都是四维常微分方程组状态变量多动力学行为丰富。我下面代码里用的是超混沌Lorenz系统表达式和参数会给出具体细节。1.3 斐波那契Q矩阵在这个方案里的真正角色斐波那契Q矩阵并不是一个新鲜概念它最早是作为一个纯数学工具出现的。Q矩阵形式是 [1 1; 1 0]它的n次幂可以写成斐波那契数列的组合Q^n [F(n1) F(n); F(n) F(n-1)]。把这个矩阵用在图像加密里原因是多方面的。第一Q矩阵是整数矩阵操作简单计算开销极小。图像像素值是整数用整数矩阵做变换不存在浮点误差累积问题。第二Q矩阵可逆而且逆矩阵形式简单在模256的整数环里也能顺利求逆解密过程有严格数学保证。第三Q矩阵的幂次n可以作为密钥的一部分不同n对应不同变换矩阵密钥空间被进一步扩大。第四它是一个2×2矩阵天然适合对图像分块操作每个2×2像素块乘以Q矩阵后块内四个像素值被线性混合到一起这个动作本质上就是像素值扩散。不过Q矩阵的局限也很明显。它本质上是线性变换单独使用的话抗攻击能力不足。攻击者如果拿到足够多的明密文对可以反推出等价变换矩阵。这也是为什么必须把Q矩阵和超混沌系统放在一起用超混沌负责提供随机索引和动态参数Q矩阵负责做可逆的像素混合二者互补才能构建完整的置乱-扩散链路。2. 算法整体设计与原理拆解2.1 完整加解密流程整套算法的核心框架可以归纳为四个阶段。第一阶段是密钥生成给定超混沌系统的初值和控制参数迭代生成足够长的混沌序列。第二阶段是位置置乱用混沌序列构造随机索引把图像的全部像素位置打乱。第三阶段是分块变换把置乱后的图像切成2×2的小块每个像素块与斐波那契Q矩阵相乘完成块内像素值混合。第四阶段是全局扩散用另外两组混沌序列构造密钥流对像素值做正向和反向的逐点异或或模加操作这一步会让任意一个像素的微小变化传递到整个图像。解密过程就是加密过程的严格逆序。先用同一组混沌密钥流去掉全局扩散再对每个2×2块乘以Q矩阵的逆矩阵最后按逆索引恢复像素原始位置。整个加解密流程是对称的只要密钥一致解密出来的图像就能和原图完全一致。2.2 超混沌序列的生成与预处理超混沌Lorenz系统的微分方程组写法如下dx/dt a(y - x) wdy/dt cx - y - xzdz/dt xy - bzdw/dt -yz rw当参数a35、b3、c12、r取0.5左右时系统处于超混沌状态四个状态变量都有复杂的振荡行为。数值求解采用四阶Runge-Kutta法步长一般取0.001到0.005。这里有一个特别容易被忽略的细节混沌系统迭代初期存在暂态过程初值产生的序列在开始的几百步内并没有进入稳定的混沌状态。如果直接使用这段序列随机性很弱甚至可能出现明显的相关特征。我习惯的做法是预先迭代丢弃前500到1000个点只保留暂态结束后的序列。超混沌系统生成的是浮点数序列而图像像素是0到255的整数。直接用浮点数去和像素值做模加显然不行必须做量化。量化方式很多最常见的是取序列小数部分再乘一个大数然后取模quant_val mod(floor((x - floor(x)) * 10^14), 256)。我后面代码里就是这么写的。这个处理有讲究直接取整会有大量信息丢失乘10^14再取模可以保留浮点数的高位随机性量化后的序列统计分布更均匀。2.3 置乱与扩散的设计逻辑置乱阶段的常见实现有两种。一种是直接用混沌序列的排序索引打乱像素位置对序列排序后记录每个元素排序后的位置索引用这个索引重新排列图像像素。另一种是逐像素循环移位每个像素移动的步长由混沌序列决定。排序索引法的优点是一次性完成全局置乱像素位置变化范围大缺点是排序本身有O(nlogn)的开销。循环移位法速度快但如果不进行多轮置乱范围有限容易残留局部结构。我下面的实现采用排序索引法并且在MATLAB里直接调用sort函数效率并不差。扩散阶段的设计目标是让像素值的变化像多米诺骨牌一样传播。我采用的方案是双向扩散正向对整幅图像从左到右做模加把当前像素的密文值依赖前一个像素的密文值反向从右到左再做一遍把依赖关系反向传播。再加上超混沌系统生成独立的密钥流做异或整体效果就是任意一个明文像素改动经过两轮扩散之后密文中接近一半像素的灰度值都会改变。差分攻击的NPCR和UACI指标主要靠这个阶段拉起来。斐波那契Q矩阵嵌入在置乱与扩散之间做的事情是块内像素混合。置乱只改了像素位置没有改像素值扩散虽然改了像素值但只对全局序列做模加。Q矩阵是额外的代数变换它让相邻四个像素的灰度值互相线性混合这等于在像素值域上又做了一层置乱。三层变换叠加之后图像空间结构的破坏程度非常彻底。2.4 解密流程的对称性解密不是把加密函数的输入输出倒过来就行。Q矩阵变换涉及模运算必须先搞清楚逆变换的数学形式。加密时用的变换是C Q^n * B mod 256其中B是2×2像素块C是加密后的像素块。解密时需要用B inv(Q^n) * C mod 256。这里有个关键问题Q^n的行列式是(-1)^n在模256环里可能是1也可能是255正负号本身无关紧要因为1和255的逆元都是它们自身。所以在模256意义下Q^n的逆矩阵可以用伴随矩阵直接算出来不需要用普通矩阵求逆公式。解密过程的顺序必须严格倒置。如果有任何一步顺序错了结果是整幅图像完全错乱。我在实际调试过程中经常遇到解密图像一半正常一半花屏的情况最后检查基本都是顺序问题比如说先做了逆Q变换再做逆扩散结果完全反了。3. Matlab代码实现与关键环节复现3.1 环境准备与密钥设计Matlab版本用R2021b之后的基本都没问题代码不依赖额外工具箱只用基础函数和图像处理工具箱的imread、imhist、rgb2gray。完整文件结构建议这样组织encrypt_image.m是主加密函数decrypt_image.m是主解密函数gen_hyper_lorenz.m生成超混沌序列fibQ_powmod.m计算斐波那契Q矩阵的幂次取模fibQ_invmod.m计算Q矩阵的模逆矩阵metrics.m统一计算信息熵、相关性和差分攻击指标。密钥设计是整个算法的安全基石。超混沌系统四个初值x0、y0、z0、w0可以当作四组密钥控制参数r也可以作为一组密钥斐波那契Q矩阵的幂次范围也可以折叠进密钥设计里。理论上密钥空间至少是10^56量级换算成二进制远超过2^128。实际使用中不需要所有密钥都设得特别复杂四到六个有效参数足够应对常规安全需求。3.2 超混沌序列生成函数这个函数是整个算法的“随机源工厂”。代码里直接对四维状态向量做Runge-Kutta积分子函数定义超混沌Lorenz方程的右端项。function seq gen_hyper_lorenz(x0, y0, z0, w0, total, dt, discard) a 35; b 3; c 12; r 0.5; n total discard; seq zeros(n, 4); state [x0; y0; z0; w0]; seq(1, :) state; for i 2:n k1 lorenz4d(state, a, b, c, r); k2 lorenz4d(state 0.5 * dt * k1, a, b, c, r); k3 lorenz4d(state 0.5 * dt * k2, a, b, c, r); k4 lorenz4d(state dt * k3, a, b, c, r); state state (dt / 6) * (k1 2 * k2 2 * k3 k4); seq(i, :) state; end seq(1:discard, :) []; end function ds lorenz4d(s, a, b, c, r) x s(1); y s(2); z s(3); w s(4); ds [a * (y - x) w; c * x - y - x * z; x * y - b * z; -y * z r * w]; end调用方式seq gen_hyper_lorenz(0.3524, 0.2031, 0.8423, 0.1237, M * N 1000, 0.002, 500);这里total取M乘N加1000因为后面置乱和扩散需要的序列长度都接近像素总数。丢弃前500个点用于避开暂态。步长0.002实际跑下来比较稳既保证精度又不会太慢。3.3 斐波那契Q矩阵及逆矩阵计算Q矩阵的幂次不能直接算再取模因为斐波那契数增长极快n稍微大一点矩阵元素就爆炸了。正确做法是快速幂取模每步乘法都做mod 256运算。function Qn fibQ_powmod(n, m) Q [1 1; 1 0]; Qn eye(2); base Q; while n 0 if mod(n, 2) 1 Qn mod(Qn * base, m); end base mod(base * base, m); n floor(n / 2); end end逆矩阵计算利用Q^n行列式为(-1)^n这一特性用伴随矩阵求模逆。function Qinv fibQ_invmod(Qn, m) detQ mod(det(Qn), m); adj [Qn(2,2), -Qn(1,2); -Qn(2,1), Qn(1,1)]; Qinv mod(mod(detQ, m) * adj, m); end需要注意在模256环里detQ可能是1也可能是255这两个数都是可逆的逆元也是自身。所以上面代码里的mod(detQ,m) * adj本质上就等于伴随矩阵除以行列式的模意义版本。实测解密时直接用这个逆矩阵乘密文块再取模256能完全恢复原始像素块。3.4 主流程加密函数核心代码加密主流程我简化成几个关键片段。第一步读取图像并转为灰度第二步生成超混沌序列第三步排序索引做像素置乱第四步分块Q矩阵变换第五步双向扩散。img imread(cameraman.tif); if size(img, 3) 3 img rgb2gray(img); end [M, N] size(img); totalPixels M * N; seq gen_hyper_lorenz(0.3524, 0.2031, 0.8423, 0.1237, totalPixels 1000, 0.002, 500); X seq(1:totalPixels, 1); Y seq(1:totalPixels, 2); Z seq(1:totalPixels, 3); W seq(1:totalPixels, 4); [~, idx] sort(X Y); flat img(:); scrambledImg reshape(flat(idx), M, N); QnArray mod(floor((Z - floor(Z)) * 1e14), 256); cipherImg zeros(M, N); for row 1:2:M-1 for col 1:2:N-1 block scrambledImg(row:row1, col:col1); n mod(floor(Z((rowcol)/2) * 100), 8) 1; Qk fibQ_powmod(n, 256); cipherImg(row:row1, col:col1) mod(Qk * double(block), 256); end end stream mod(floor((W - floor(W)) * 1e14), 256); cf cipherImg(:); cf(1) mod(cf(1) stream(1), 256); for i 2:totalPixels cf(i) mod(cf(i) cf(i-1) stream(i), 256); end for i totalPixels-1:-1:1 cf(i) mod(cf(i) cf(i1) stream(i), 256); end cipherImg reshape(cf, M, N); imwrite(uint8(cipherImg), cipher.png);运行这段代码cameraman.tif加密后的图像肉眼看就是纯噪声直方图分布非常平坦说明扩散和置乱发挥了作用。3.5 主流程解密函数与一致性验证解密按逆顺序实现。先把密文图像做反向扩散消除再做Q矩阵逆变换最后按逆索引恢复原始像素位置。cf cipherImg(:); for i 1:totalPixels-1 cf(i1) mod(cf(i1) - cf(i) - stream(i1), 256); end for i totalPixels-1:-1:1 cf(i) mod(cf(i) - cf(i1) - stream(i), 256); end imgQInv reshape(cf, M, N); recoveredImg zeros(M, N); for row 1:2:M-1 for col 1:2:N-1 block imgQInv(row:row1, col:col1); n mod(floor(Z((rowcol)/2) * 100), 8) 1; Qk fibQ_powmod(n, 256); Qinv fibQ_invmod(Qk, 256); recoveredImg(row:row1, col:col1) mod(Qinv * block, 256); end end recoveredFlat zeros(totalPixels, 1); recoveredFlat(idx) recoveredImg(:); recoveredImg reshape(recoveredFlat, M, N);验证一致性的最简单方法是计算解密图像与原图的均方误差理想情况下应该是0。还可以直接用isequal(uint8(round(recoveredImg)), img)判断。这里有一个特别值得说的细节Q矩阵变换后cipherImg是double类型但值在0到255之间。解密时如果直接对double做mod有时候会因为浮点精度问题出现254.9999这种结果round之后才能恢复整数。所以正式代码里记得在显示或保存前做uint8(round(...))。这个坑我踩过很多次加密流程没问题但解密图像总是有几个像素差1原因就在浮点误差上。4. 实验效果与安全指标分析4.1 直观效果与直方图分析加密算法效果最直观的验证就是看直方图。原始灰度图像一般具有明显的灰度分布特征比如cameraman.tif背景区域大灰度值集中在120到200之间直方图有明显的峰和谷。加密后的密文图像直方图应该像白噪声一样均匀分布所有灰度值出现频率接近均匀。Matlab里几行代码就能看subplot(2,2,1); imshow(img); title(Plain Image); subplot(2,2,2); imhist(img); title(Plain Histogram); subplot(2,2,3); imshow(uint8(cipherImg)); title(Cipher Image); subplot(2,2,4); imhist(uint8(cipherImg)); title(Cipher Histogram);直方图均匀说明加密算法没有保留原始图像的统计特征。如果直方图还有明显峰谷说明扩散不彻底攻击者可以通过统计攻击恢复部分信息。4.2 相邻像素相关性计算自然图像的另一个显著特征是相邻像素高度相关无论是水平方向还是垂直方向相邻像素的灰度值差异很小。好的图像加密算法必须把这种相关性破坏到接近零。我习惯用两种方式验证一是计算相关系数二是画相邻像素散点图。相关系数计算函数function r pixel_corr(img, direction) [rows, cols] size(img); img double(img); if direction h A img(:, 1:cols-1); B img(:, 2:cols); elseif direction v A img(1:rows-1, :); B img(2:rows, :); else A img(1:rows-1, 1:cols-1); B img(2:rows, 2:cols); end A A(:); B B(:); r sum((A - mean(A)) .* (B - mean(B))) / ... sqrt(sum((A - mean(A)).^2) * sum((B - mean(B)).^2)); end原始图像的水平相关系数一般在0.95以上垂直和对角方向也在0.9左右。加密后相关系数应该低于0.01越接近0说明位置置乱越彻底。如果相关系数还在0.1量级说明置乱策略没有充分打乱像素空间结构。4.3 信息熵与差分攻击指标信息熵衡量灰度值的随机程度。对于256级灰度图像信息熵的理论最大值是8。加密后密文图像的信息熵越接近8说明像素值分布越均匀。function h image_entropy(img) counts imhist(img); p counts / sum(counts); p(p 0) []; h -sum(p .* log2(p)); end我实测跑出来的加密图像信息熵通常在7.997到7.999之间说明扩散阶段的效果已经很接近理论极限。差分攻击指标NPCR和UACI用来衡量算法对明文微小变化的敏感程度。测试方法是随机挑一个像素把它的灰度值翻转低比特位比如从128改成129然后分别加密原图和新图计算两幅密文图像之间的差异。function [nPCR, uACI] diff_metrics(C1, C2) diffMap C1 ~ C2; nPCR sum(diffMap(:)) / numel(C1) * 100; uACI sum(abs(double(C1(:)) - double(C2(:))) / 255) / numel(C1) * 100; end理想值方面NPCR应该大于99.6%UACI在33.4%左右。这两个值没有达到标准说明扩散阶段对明文的敏感性不足攻击者可以通过差分分析构造攻击。这个指标是审稿人非常喜欢看的一个点。4.4 密钥敏感性测试密钥敏感性测试的目的是验证“密钥差一点点解密结果完全不同”。测试方法是用密钥A加密图像得到密文C然后用密钥A加上一个极小的扰动比如x0增加1e-15去解密C得到的解密图像应该与原始图像完全不同。实际操作时我会分别计算扰动前后解密图像与原始图像的像素差异或者直接看NPCR值。如果微小密钥扰动就能让解密图像的NPCR接近100%说明算法对密钥高度敏感能有效抵御密钥搜索攻击。同时这也意味着用户在应用时必须精确保存密钥哪怕差一个小数点后十几位解密都会彻底失败。5. 踩坑记录与常见问题排查5.1 混沌序列退化导致加密强度不够我刚开始实现的时候混沌初值随便选了一个比较整的数比如x00.5、y00.5结果生成的混沌序列在几百步之内表现出明显的周期性加密出来的密文图像肉眼能看出规则纹理。原因就是初值落在某些特定轨迹上系统没有进入理想混沌状态。处理办法有两个。第一初值尽量选择带有较多小数位的“普通”数字比如0.3524124这种避免0.1、0.2、0.5这样的整十整五。第二迭代初期一定要丢弃我一般丢弃500步稳妥一点可以丢1000步。丢弃之后序列的随机性会明显改善。如果你在实验中发现加密结果有规律条纹优先检查这两点。5.2 解密出来的图像花屏或部分不对解密失败的原因通常有三个。第一个是Q矩阵逆矩阵计算错误尤其是忘记在模256环里求逆直接用了普通矩阵求逆结果必然不对。第二个是加解密顺序颠倒可能只是某个环节顺序错了导致整幅图像错乱。第三个是数据类型转换问题解密过程中用了uint8过早截断中间值取模运算结果不正确产生累积误差。排查思路是从最简单的验证开始先不加密只跑一遍Q矩阵变换再逆变换看能否恢复原图。再单独测试置乱和逆置乱。分模块验证通过后再组合起来测完整加解密。这样能快速定位问题出在哪个环节。5.3 大尺寸图像加密太慢如果直接用嵌套for循环对每个像素块做Q矩阵变换512乘512图像的块数量是两万五千多块循环本身开销很大。再叠加MATLAB里for循环效率偏低跑到后面会非常慢。优化思路有几个。一是把超混沌序列生成改成向量化方式尽量少用循环。二是Q矩阵的幂次只可能有有限的几种取值如果n范围限定在1到8那就只需要预先算8个Q矩阵和8个逆矩阵循环里直接查表不用每次都快速幂。三是如果内存允许将图像分块操作改成矩阵运算或者用blockproc函数并行处理。实际项目中我通常会把n范围缩小到1到8这是图像加密论文里比较常见的做法既保证密钥空间又兼顾运行速度。5.4 斐波那契Q矩阵阶数爆炸斐波那契数增长极快F(50)已经超过10^10F(100)是10^20量级。如果直接把n取得特别大再乘矩阵结果溢出是必然的。有些人会问既然快速幂取模可以解决是不是n可以取任意大理论上可以但实际没必要。n太大会导致Q矩阵的模逆矩阵计算变得复杂而密钥空间已经由超混沌系统的初值和控制参数保证了Q矩阵的幂次只需要提供一定程度的变换多样性即可。我一般把n控制在1到32之间动态选择时再折叠到1到8。比如用混沌序列的某个区间映射到1到8这样既避免了阶数爆炸问题又能让不同像素块使用不同的变换矩阵。这个细节在论文里可以写成一个亮点实际上也是提高安全性的有效手段。5.5 实验指标和论文对不上怎么办很多同学跑完加密算法之后发现信息熵到了7.99但相关性和NPCR总差一点。这种情况多半不是算法本身的问题而是扩散轮数不够或者置乱索引有问题。解决办法是增加一轮全局扩散或者在置乱之后再做一次分块置乱。我实际测试下来双轮扩散的明文敏感性比单轮扩散提升非常明显NPCR从98%左右直接跳到99.6%以上。另外测试图像选择也有影响。Lena图和cameraman.tif的空间结构差异很大有些算法在纹理复杂的图像上表现好在平滑图像上表现差。做实验对比时不要只用一张图至少要覆盖平滑图像比如MRI脑部图、纹理图像比如指纹图和自然图像三类实验结果才有说服力。加密指标本身也不是越高越好。NPCR和UACI要落在合理区间离理想值太远说明扩散不彻底但完全等于理想值反而可能说明算法过度拟合了这次测试。只要各项指标都处于论文公开的可靠范围内算法就是可用的。做图像加密项目的体会是数学原理再漂亮最终都要落到代码可复现和指标可验证上。超混沌系统加斐波那契Q矩阵这套组合理论深度够代码实现也不算复杂非常适合作为入门到进阶的过渡项目。我建议你拿到代码后不要直接跑完就放一边而是自己尝试改动Q矩阵幂次的映射方式或者换一组超混沌系统参数观察安全性指标的变化。这个过程比单纯跑通代码要有价值得多。看完这篇文章之后如果你也打算用这个思路做实验先花半小时把加密和解密主流程跑通再把安全分析函数加上后续扩展开销就会小很多。
返回列表