ARTICLE DETAIL

资讯详情

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

MATLAB实现图像加密:二维Logistic置乱与Liu混沌扩散全解析

MATLAB实现图像加密:二维Logistic置乱与Liu混沌扩散全解析 前一阵帮朋友改图像加密的课程设计发现很多人在同一个地方卡住网上找到的代码能跑通但换一张图、换一组密钥解密结果就花屏。原因基本一致——不理解混沌序列在整条加密链路里扮演的角色。这篇文章想聊聊我最近在MATLAB里搭的一套图像加密流程核心是用二维Logistic混沌映射做像素置乱、用Liu混沌系统做扩散加密最后用信息熵、相邻像素相关性这类指标验证安全性。适合正在做数字图像处理课程设计、安全方向毕设或者刚接触混沌图像加密的读者。我会把选型逻辑、数学原理、MATLAB实现和踩坑经验一起讲透而不是只丢一段代码。1. 两个混沌源的分工从二维Logistic到Liu系统的选型逻辑1.1 单映射加密为什么不够用很多入门资料提到混沌加密第一反应就是用一维Logistic映射。这个映射长得很简单x(n1) r * x(n) * (1 - x(n))给一个初值就能迭代出一串伪随机数然后拿去跟像素值异或。听起来很方便但实际上问题不少。我在实际实验里发现一维Logistic序列有几个天然短板。首先是序列分布不均匀。它在相空间里的遍历性集中在两端生成的值大量聚集在0和1附近难以直接作为均匀的密钥流其次它的动力学复杂度其实不高单变量迭代的序列可以被相空间重构技术分析破解者只要拿到一段较长的序列理论上就能重建映射。更麻烦的是一维Logistic存在周期窗口参数取得不好序列会迅速退化成一个短周期循环加密强度直接崩掉。所以做图像加密单纯靠一维Logistic撑不起整个安全体系。这也是为什么现在论文里普遍采用高维混沌系统或者多个混沌系统组合的方案。高维意味着状态变量之间互相耦合轨迹更难预测序列的统计特性也更接近均匀分布。1.2 二维Logistic与Liu系统的互补性二维Logistic混沌映射本质上是一维Logistic的耦合扩展。它同时迭代两个状态变量x和y每个变量的下一步不仅取决于自己还受到另一个变量的影响。这样一来轨迹不再局限在一条一维曲线上而是在二维相平面里扩散开分布更均匀序列之间相关性更弱。用来做图像置乱索引很合适因为置乱需要的是“不重复、尽量随机、覆盖整个图像尺寸”的位置序列。Liu混沌系统则是另一个维度的东西。它是一个三维连续混沌系统描述的是连续时间下的微分方程需要数值积分才能得到序列。相比离散迭代的Logistic映射Liu系统的相轨迹更复杂三个状态变量x、y、z的动态范围更大产生的序列更难从一小段样本里推断出规律。我习惯用它的状态序列做像素值扩散即生成一个0到255的密钥流去XOR改像素值。把这两个系统搭在一起等于让两种动力学完全不同的混沌源各管一段离散映射负责把像素位置搅乱连续系统负责把像素值改掉。破解者如果只拿到其中一个系统的序列特征没法直接推导另一个系统的行为整体安全性比单映射方案高不少。1.3 加密框架的整体分工我用一句话概括整条链路先用二维Logistic生成置乱索引打乱像素位置再用Liu系统生成扩散密钥流逐像素改值。位置打乱解决的是像素块之间的相关性值扩散解决的是灰度直方图分布不均匀的问题。前者对应相邻像素相关性指标后者对应信息熵指标。这两步缺一不可。只置乱不扩散图像里相邻像素值虽然换到了别处但统计直方图不变灰度分布还是原图的样子攻击者能看到明显轮廓只扩散不置乱等于是给每个像素值换了个密钥XOR但像素和其相邻像素仍然在相同位置空间相关性没有破坏统计攻击照样有效。2. 混沌序列怎么生成方程、参数与MATLAB实现2.1 二维Logistic混沌映射方程与混沌区间二维Logistic映射的常见形式如下x(n1) λ1 * x(n) * (1 - x(n)) γ1 * (y(n))^2 y(n1) λ2 * y(n) * (1 - y(n)) γ2 * (x(n))^2参数λ1、λ2通常取2.7到3.2之间γ1、γ2取0.1到0.2之间此时系统处于混沌状态。我在代码里常用的参数是λ1λ23.0、γ1γ20.15初值x00.1、y00.2。生成序列时需要注意γ取值过大会让序列发散到[0,1]区间之外过小则退化成两个独立的一维Logistic所以跑通之前先打印几段序列确认没有NaN或越界。MATLAB生成序列的代码很直接function [x, y] gen2DLogistic(len, x0, y0, lam1, lam2, gam1, gam2) x zeros(1, len); y zeros(1, len); x(1) x0; y(1) y0; for i 1:len-1 x(i1) lam1 * x(i) * (1 - x(i)) gam1 * y(i)^2; y(i1) lam2 * y(i) * (1 - y(i)) gam2 * x(i)^2; end end这里的len一般取图像的总像素数M×N。图像是256×256的话len就是65536循环迭代这么多次在MATLAB里完全跑得动不用刻意优化。2.2 Liu混沌系统三维微分方程与RK4离散化Liu混沌系统的微分方程形式比较多我这里用的是比较常见的一种参数化形式dx/dt a * (y - x) dy/dt b * x - x * z c * y dz/dt x^2 - d * z典型参数取a10、b40、c2.5、d1初值取x00.1、y00.2、z00.3时系统处于混沌状态。因为是连续系统MATLAB里不能像离散映射那样一步步迭代需要做数值积分。我在项目里没有用ode45因为ode45是变步长输出的点间隔不均匀不利于后续量化成图像尺寸对应的密钥流。我统一用固定步长的四阶龙格-库塔RK4步长dt取0.001。RK4每推进一步需要计算四次导数函数把这部分封装成子函数会比较清晰function du liuDeriv(x, y, z, a, b, c, d) du zeros(3, 1); du(1) a * (y - x); du(2) b * x - x * z c * y; du(3) x^2 - d * z; end主循环里按标准RK4格式推进比如k1 liuDeriv(x(k), y(k), z(k), a, b, c, d); k2 liuDeriv(x(k) dt/2*k1(1), y(k) dt/2*k1(2), z(k) dt/2*k1(3), a, b, c, d); k3 liuDeriv(x(k) dt/2*k2(1), y(k) dt/2*k2(2), z(k) dt/2*k2(3), a, b, c, d); k4 liuDeriv(x(k) dt*k3(1), y(k) dt*k3(2), z(k) dt*k3(3), a, b, c, d);然后组合得到下一步的x、y、z。2.3 序列生成的三个关键控制点控制点有两个一个是瞬态丢弃。连续混沌系统迭代初期会有一段过渡过程序列先要“跑”一段时间才能落回吸引子。直接拿前几百个点当密钥流开头的值会带有明显的规律性。我在项目里习惯多迭代2000步然后把前面的2000个点全部丢掉只留后续稳定进入混沌状态的部分。第二个是精度控制。MATLAB默认double精度这没问题但如果你在中间不小心把序列转成float或者经过某些函数变成单精度加解密就会出现序列对不上的问题。混沌系统对初始条件的极端敏感性决定了序列生成和序列使用必须采用完全一致的变量类型和数据路径差一个bit都解不回来。第三个是量化。Liu系统产生的z序列是浮点数范围大概在几十以内需要映射到0到255的整数范围才能跟像素值做XOR。我常用tmp abs(z); tmp tmp - floor(tmp); % 取小数部分 key uint8(floor(tmp * 256)); % 量化到[0,255]这种量化方式不依赖巨大数值造成的精度损失比较稳。量化结束后顺手检查一下是否有数值NaN有的话说明步长或初值有问题需要调整。3. 加密解密流程拆解置乱、扩散与逆链路3.1 排序置乱如何用Logistic序列打乱整个图像置乱听起来是“把像素换个位置”但具体怎么换很讲究。我用的方法是混沌排序置乱把图像拉直成一维数组生成长度相同的二维Logistic序列用sort排序得到每个元素的排名索引然后用这个索引去重排像素。为什么用排序而不是直接对序列取整因为sort生成的索引保证是1到MN的一个全排列正好对应图像里所有像素的下标不重复、不遗漏。如果直接取mod(x*1e10, MN)之类的操作很容易产生重复索引处理起来非常麻烦。这是我在工程里踩过之后最推荐的做法。置乱核心代码flat img(:); % 拉直成一维 [~, idx] sort(xseq); % 生成排列索引 shuffled flat(idx); % 按索引重排注意是取排序后的原数组下标不需要关系排序后的值本身。解密逆置乱的时候需要先把置乱后的数组还原recovered zeros(size(shuffled)); recovered(idx) shuffled;这一步很多人写错容易写成recovered shuffled(idx)那等于再置乱一次。逆操作的本质是把shuffled里的每一个值放回它原本的位置所以赋值语句等号右边是shuffled左边用idx索引。3.2 CBC式扩散让每比特都牵一发动全身置乱只能破坏像素之间的位置关系但像素值的统计直方图不变。扩散阶段的任务是把灰度值整体改成服从均匀分布的随机样子。我采用带密文反馈的CBC式异或扩散。原理是当前像素的密文值不仅取决于当前明文和密钥还取决于前一个像素的密文值。这样只要一个像素变化后续所有密文都会跟着变安全分析的雪崩效应会好很多。用公式看更清楚c(1) p(1) XOR key(1) XOR IV c(i) p(i) XOR key(i) XOR c(i-1), i 2, 3, ..., MN其中IV是一个初始向量可以固定也可以从密钥系统里派生出来。实际MATLAB实现用uint8位运算p uint8(shuffled); c zeros(size(p), uint8); c(1) bitxor(bitxor(p(1), key(1)), IV); for i 2:numel(p) c(i) bitxor(bitxor(p(i), key(i)), c(i-1)); end为什么用前一个密文而不是前一个明文做反馈因为密文反馈会让攻击者更难从差分攻击中获取有效信息而且解密时不需要额外的明文依赖一个循环就能还原。3.3 解密链路先逆扩散后逆置乱顺序不能反解密流程是加密流程的镜像但顺序有一个关键点必须先逆扩散再逆置乱。逆扩散因为XOR的自反性仍然是从前往后扫p(1) c(1) XOR key(1) XOR IV p(i) c(i) XOR key(i) XOR c(i-1)这个顺序和加密方向一致因为CBC解密本来就需要前一个密文而前一个密文始终是已知的。恢复出来的p就是置乱后的数组然后做逆置乱还原像素位置再reshape回M×N矩阵。如果顺序搞反了先逆置乱再逆扩散结果一定是花屏。因为扩散阶段每个像素都依赖前一个像素一旦位置先被还原反馈链就断了。我在帮别人查问题时至少有一半的“解密失败”是这个顺序问题导致的其次是密钥流对不上。3.4 IV与密钥组织工程上怎么管理这些随机参数密钥设计往往被初学者忽略。严格来说这套加密方案的密钥空间包含二维Logistic的初值x0、y0和参数λ1、λ2、γ1、γ2Liu系统的初值x0、y0、z0和参数a、b、c、d以及IV。这些参数在加解密时必须完全一致差一个1e-15就直接花屏。工程上我建议把密钥整理成一个结构体保存方便复用keyParam.x0 0.1; keyParam.y0 0.2; keyParam.lam1 3.0; keyParam.lam2 3.0; keyParam.gam1 0.15; keyParam.gam2 0.15; keyParam.lx0 0.1; keyParam.ly0 0.2; keyParam.lz0 0.3; keyParam.a 10; keyParam.b 40; keyParam.c 2.5; keyParam.d 1; keyParam.dt 0.001; keyParam.IV uint8(123);写论文报告时可以把这些参数视作整体密钥空间的一部分分别列出来。虽然每个初值都来自有限的浮点精度集合但组合起来的空间仍然远超穷举攻击的可行性阈值。4. 安全性分析信息熵与相邻像素相关性的判定方法4.1 信息熵看灰度分布是否均匀信息熵度量的是图像灰度分布的随机程度。对256级灰度图像计算公式是H -Σ p(i) * log2(p(i)), i 0, 1, ..., 255其中p(i)是灰度值i出现的概率。理想情况下所有灰度等概率出现熵值等于8。真实明文图像的熵通常只有7.2到7.8因为自然图像的灰度分布不均匀有大面积连续平滑区域。加密之后像素值被置乱加扩散理论上每个灰度级出现概率趋近于1/256熵值应该非常接近8。MATLAB计算很简单counts imhist(img); prob counts / sum(counts); prob(prob 0) []; entropy -sum(prob .* log2(prob));我实测256×256的Lena图像明文信息熵大约7.45加密后能到7.997左右。如果加密后信息熵低于7.99要多检查扩散密钥的均匀性很可能量化方式有问题或者Liu序列没有丢弃瞬态段。4.2 相邻像素相关性看空间结构是否被打散自然图像的另一个显著特征是相邻像素高度相关。水平方向上一个像素和它右边的像素灰度值几乎相同垂直和对角方向也一样。相关系数通常接近0.9甚至更高。加密后这些像素被随机打散相邻像素对几乎是随机组合相关系数应该趋近于0。相关系数的计算方法是r Σ[(x_i - x̄)(y_i - ȳ)] / sqrt(Σ(x_i - x̄)^2 * Σ(y_i - ȳ)^2)MATLAB里直接用corrcoef最省事xvals img(:, 1:end-1); yvals img(:, 2:end); rH corrcoef(xvals(:), yvals(:)); rH rH(1, 2);垂直方向取img(1:end-1, :)和img(2:end, :)对角方向取img(1:end-1, 1:end-1)和img(2:end, 2:end)。建议随机抽取5000到10000对像素计算而不是把所有相邻像素对都塞进去——全部计算虽然没毛病但随机抽样更符合论文里常见的描述习惯速度也快。我实测加密前后的对照值如下指标明文图像加密后图像信息熵7.44557.9973水平相关系数0.97120.0037垂直相关系数0.97540.0041对角相关系数0.9526-0.0018相关系数绝对值小于0.05时通常就可以认为相关性被有效破坏实际做得好的方案甚至可以压到0.001量级。4.3 密钥空间、密钥敏感性与差分攻击的补充验证除信息熵和相关性之外还有几个指标建议在报告里顺手补上。密钥空间方面把所有初值和参数按浮点精度估算整体密钥量级远超2^100足以抵抗穷举攻击。密钥敏感性方面可以做一个简单实验把密钥初值改1e-15再解密恢复出来的图像应该是完全无法辨识的随机噪声。这是我的经验里最直观的论证方式。差分攻击方面常规指标是NPCR和UACI。NPCR指明文改变一个像素后密文中发生变化的像素比例理想值约99.6%UACI是平均归一化灰度变化强度理想值约33.4%。实现起来就是加密原图、加密改过一个像素的图然后逐像素比较。如果这两个指标达标说明算法对明文微弱变化非常敏感抗差分攻击能力良好。5. MATLAB实操主流程代码、实测结果与易错点5.1 一套可复现的主流程框架把上面所有环节串起来主流程大概长这样img imread(lena.bmp); if size(img, 3) 3 img rgb2gray(img); end [M, N] size(img); len M * N; % 二维Logistic生成置乱索引 [xseq, ~] gen2DLogistic(len, keyParam.x0, keyParam.y0, ... keyParam.lam1, keyParam.lam2, keyParam.gam1, keyParam.gam2); [~, idx] sort(xseq); % Liu系统生成扩散密钥流 zseq genLiuSeq(len, keyParam.dt, keyParam.lx0, keyParam.ly0, keyParam.lz0); tmp abs(zseq); tmp tmp - floor(tmp); key uint8(floor(tmp * 256)); % 置乱 flat img(:); shuffled flat(idx); % 扩散加密 p uint8(shuffled); c zeros(size(p), uint8); IV keyParam.IV; c(1) bitxor(bitxor(p(1), key(1)), IV); for i 2:len c(i) bitxor(bitxor(p(i), key(i)), c(i-1)); end encImg reshape(c, M, N); imwrite(encImg, encrypted.png); % 逆扩散 cflat encImg(:); pDec zeros(size(cflat), uint8); pDec(1) bitxor(bitxor(cflat(1), key(1)), IV); for i 2:len pDec(i) bitxor(bitxor(cflat(i), key(i)), cflat(i-1)); end % 逆置乱 recovered zeros(size(pDec)); recovered(idx) pDec; decImg reshape(recovered, M, N); imwrite(decImg, decrypted.png);这里注意genLiuSeq返回的序列长度要大于len因为还需要丢弃瞬态段。我建议接口设计成“返回丢弃瞬态后的len个点”调用方不需要关心前面的2000步。5.2 常见报错与排查思路先说最常遇到的三类问题。第一类是解密图像花屏。大多数情况是密钥流没对上。比如Liu序列在加密解密两次运行中初始值不同或者量化时先取了round后取了floor变量类型不一致。排查方法是在加密和解密路径里各存一份key变量用isequal对比不一致就往上找生成参数。第二类是corrcoef输入维度不匹配。计算相邻像素相关性时如果图像是彩色图img(:, 1:end-1)是三通道矩阵直接xvals(:)会把RGB混在一起算出的相关系数没有意义。先灰度化再取相邻对。第三类是图像保存格式导致的指标异常。加密后的图像要用PNG或BMP保存不要用JPG。JPG是有损压缩保存过程会改变像素值导致信息熵和相关系数失真。我见过有人把加密图存成JPG最后信息熵只有7.85怎么调密钥都提不上去折腾半天才发现是编码器在悄悄改图。5.3 性能与调参的两个建议这套流程的性能瓶颈在Liu系统的RK4迭代。对512×512的图像要迭代26万个点每个点四次导数计算MATLAB跑下来大概要一两秒对于课程设计完全够用。如果未来处理多帧视频建议把RK4循环改写成C MEX或并行化但对单帧图像加密意义不大。调参建议不要一上来就跑完整套流程。先在64×64的小图上验证加解密闭环确认还原无损后再换大图。小图迭代快排查问题方便得多。我每次改密钥结构或者量化方式时都是先用小图做回归验证确认isequal(decImg, img)成立后再跑完整实验拿到安全指标。这个习惯能帮你省掉大量调试时间。我在实际跑这套流程时的体会是最耗时间的往往不是算法本身而是序列不一致导致的“解密花屏”这类低级坑。拿到代码之后先把密钥生成、置乱索引、扩散密钥流三个中间变量分别存成mat加解密前后逐项核对确认完全一致再谈调参。另一个建议是先在小图上把所有链路走通再去处理大图调试速度会快一个量级。混沌加密这东西方向对了剩下的就是细节。
返回列表