ARTICLE DETAIL

资讯详情

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

基于拉丁矩形置乱与混沌映射的图像加密Matlab实现

基于拉丁矩形置乱与混沌映射的图像加密Matlab实现 图像加密这个方向我陆陆续续做了小半年。以前总觉得加密是搞通信安全那帮人的事直到自己动手用 Matlab 实现了一版“基于拉丁矩形置乱 混沌映射”的图像加密算法才体会到里面的坑比想象中多得多。这篇文章不是论文复述就是把我做这个项目时踩过的坑、想明白的原理和能直接跑的代码完整记录下来。算法流程、Matlab 实现、参数调试、常见问题我都放在下面适合正在做图像加密课设、毕业设计或者刚接触混沌加密这块的读者直接参考。1. 做这个项目前先把技术路线想清楚1.1 置乱和扩散到底在干什么很多初学者拿到“图像加密”题目第一反应是“把像素打乱不就行了”。确实置乱就是打乱像素位置但只做这一步根本不够。举一个最直观的例子一张大白天的风景照像素值普遍集中在高亮度区间哪怕你把所有像素的位置全部调换直方图还是老样子。攻击者拿到密文图不需要知道具体位置变化只看统计特征就能猜出原始图像大概是什么内容。所以现代图像加密基本都走“置乱 扩散”两条路置乱Permutation改变像素位置打破图像在空间上的相关性。相邻像素原本很像打乱后就不再像了。扩散Diffusion改变像素值让任意一个明文的微小变化扩散到整张密文。这是对抗统计分析和已知明文攻击的关键。一句话总结置乱负责把图像“搅乱”扩散负责把图像“化开”。两者都能单独用但合在一起才是图像加密的常见形态。1.2 拉丁矩形在这条路线里的真正价值那为什么偏偏用“拉丁矩形”做置乱而不是随便生成一个随机置换表拉丁矩形是从拉丁方推广出来的概念。严格一点说一个 r×n 的拉丁矩形行数 r 不超过列数 n每一行由 1 到 n 的符号构成一个排列每一列中同一个符号不能重复出现。如果 r 等于 n它就是大家更熟悉的拉丁方也就是数独里那种“每行每列都不重复”的结构。这种结构对置乱有一个天然的好处确定性下的均匀性。你用它给像素位置做映射可以保证每个位置被访问且只被访问一次不存在碰撞也不需要额外判重。反过来说如果你只是用随机数生成器硬凑一组置换遇到特殊情况还得检查是不是有重复性能和使用体验都会差一些。我自己在实现时采用的是“混沌序列生成基础置换 拉丁矩阵展开”的方式。混沌负责提供不可预测的随机性拉丁矩形负责把这些随机性组织成可逆、无碰撞的位置映射。用生活化的说法混沌是“随机种子”拉丁矩形是“稳定骨架”。2. 拉丁矩形置乱到底怎么玩2.1 拉丁矩阵的构造原理构造拉丁方的方式有很多我用的是一种最容易理解和代码化实现的方法先由一个混沌序列得到一个长度为 n 的基础排列再对这个排列做循环移位。假设基础排列是 basePerm那么第 i 行的值可以这样构造L(i, j) mod(basePerm(i) j - 2, n) 1因为 basePerm 本身包含 1 到 n 的所有数字对每一个固定行 i随着 j 遍历 1 到 nL(i, j) 恰好是 basePerm(i) 开始的一组循环移位所以每一行都是完整排列。对固定列 ji 变化时 basePerm(i) 本身在遍历 1 到 n所以每一列也都是完整排列。这就是拉丁方。如果原图是长方形我习惯先把短边补零到方形做完加密后再把填充区域裁掉。这样既能复用标准的拉丁方构造逻辑又不容易写错。2.2 置乱代码与逆变换我用的置乱分两步先用拉丁方做“列内行置换”再用同一个拉丁方做“行内列置换”。两步都是可逆的合起来仍然是一一映射。先看混沌序列生成函数我常用 Logistic 映射function seq chaos_logistic(x0, mu, len) seq zeros(1, len); x x0; for i 1:len x mu * x * (1 - x); seq(i) x; end end然后由混沌序列生成基础排列function permIdx chaos_perm(x0, mu, n) seq chaos_logistic(x0, mu, n); [~, permIdx] sort(seq); end基础排列拿到手之后生成拉丁方并做置乱的核心函数可以写成这样function P latin_permute(A, key) n size(A, 1); [~, basePerm] sort(chaos_logistic(key.x0, key.mu, n)); L zeros(n, n); for i 1:n L(i, :) mod(basePerm(i) (0:n-1), n) 1; end % 第一步逐列做行置换 P A; for j 1:n P(:, j) A(L(:, j), j); end % 第二步逐行做列置换 Q P; for i 1:n Q(i, :) P(i, L(i, :)); end P Q; end解密的时候要做相反操作。关键点有两个行内列置换的逆对每一行找到 L(i,:) 的逆排列再把 Q 的该行按逆排列取回。列内行置换的逆对每一列找到 L(:,j) 的逆排列再把中间结果按逆排列取回。逆置乱的参考代码function A latin_inv_permute(P, key) n size(P, 1); [~, basePerm] sort(chaos_logistic(key.x0, key.mu, n)); L zeros(n, n); for i 1:n L(i, :) mod(basePerm(i) (0:n-1), n) 1; end % 逆第二步行内列置换的逆 T P; for i 1:n [~, invRow] sort(L(i, :)); T(i, :) P(i, invRow); end % 逆第一步列内行置换的逆 A T; for j 1:n [~, invCol] sort(L(:, j)); A(:, j) T(invCol, j); end end这里用 sort 求逆排列的思路是L(i,:) 本身是 1 到 n 的一个排列排序后返回的索引 invRow(k)正好告诉你在第 k 个值原来来自哪个位置。3. 混沌序列与扩散加密3.1 混沌映射的参数选择混沌映射里最常被拿来入门的就是 Logistic 映射x(n1) mu * x(n) * (1 - x(n))这个映射看起来简单但要真正“混沌”起来mu 和 x0 的选择有讲究mu 通常在 3.57 到 4 之间才进入混沌区间。我一般用 3.999 或 3.9999越接近 4序列的遍历性越好但也越容易出现数值稳定性问题。x0 不能取 0、0.25、0.5、0.75 这类特殊点否则迭代会落到不动点或周期轨道。比如 x0 取 0.5一步之后就变成 mu/4后面就完全废了。Matlab 默认 double 精度足够用但注意迭代次数大了之后Logistic 映射在 mu4 附近对初值极其敏感密钥稍微差 1e-12 都会导致完全不同的序列这其实是好事。扩散阶段我用的也是混沌序列但不会直接用原始浮点值而是先映射到 0 到 255 的整数再参与 XOR。这样处理有两个原因一是图像像素值本身是整数浮点参与位运算还得先做量化二是量化后可以自然地对 uint8 图像做异或代码写起来干净。3.2 扩散阶段的实现扩散的思路比较简单生成一个与图像尺寸相同的混沌矩阵把图像和它做按位异或。为了防止“同一位置每次只 XOR 同一个随机值容易被反推”可以再加一个链式扩散让前一个像素的加密结果影响后一个像素function C xor_diffuse(P, key) [m, n] size(P); seq chaos_logistic(key.y0, key.mu2, m * n); S uint8(floor(mod(seq, 1) * 255)); S reshape(S, m, n); P uint8(P); C zeros(m, n, uint8); % 第一个像素单独处理 c bitxor(P(1, 1), S(1, 1)); C(1, 1) c; % 链式扩散 for i 1:m for j 1:n if i 1 j 1 continue; end if j 1 prev C(i-1, n); else prev C(i, j-1); end C(i, j) bitxor(P(i, j), S(i, j)); C(i, j) bitxor(C(i, j), prev); end end end解密的时候反过来处理即可。加密时用 prev 参与异或解密时就把 prev 再异或回去。需要注意链式扩散让解密顺序变得敏感这也是它安全性更高的原因。4. 完整算法流程与可运行 Matlab 代码4.1 主函数架构把上面的模块串起来加密主流程大概是这样的读入图像如果是彩色图则转灰度或者对 R、G、B 三个通道分别处理。将图像补零成 n×n 方形矩阵转成 uint8 或 double 范围 0 到 255。用 key.x0、key.mu 生成拉丁方做两步置乱。用 key.y0、key.mu2 生成混沌序列做链式 XOR 扩散。可选地重复步骤 3 和 4 多轮一般 2 轮左右效果就很明显。主函数参考function enc latin_chaos_encrypt(img, key) if size(img, 3) 3 img rgb2gray(img); end img im2double(img); [M, N] size(img); n max(M, N); A zeros(n, n); A(1:M, 1:N) img; % 统一到 0-255 的 uint8方便 XOR A uint8(round(A * 255)); % 置乱 P latin_permute(A, key); % 扩散 enc xor_diffuse(P, key); end对应的解密主函数function dec latin_chaos_decrypt(enc, key, M, N) % 逆扩散 P inv_xor_diffuse(enc, key); % 逆置乱 A latin_inv_permute(P, key); % 裁掉补零区域恢复原尺寸 A double(A(1:M, 1:N)) / 255; dec uint8(round(A * 255)); end注意解密时必须传回原始图像的尺寸 M、N否则没有办法判断哪里是补零区域。我建议把尺寸信息直接拼到密钥结构体里省得后续调用出错。4.2 小图和彩色图的处理建议如果只是做课设演示直接转灰度图是最省事的。但如果需要加密彩色图我的建议是不要合并通道而是对 R、G、B 三个通道分别做同样的流程最后用 cat(3, Renc, Genc, Benc) 拼回去。对小尺寸测试图比如 64×64 或 128×128可以直接在脚本里跑速度很快。对大图比如 1024×1024两层 for 循环的置乱扩散会比较吃力。如果你需要在项目里处理大图可以把置乱改为向量化写法或者先分块再做处理。分块的额外好处是能并行但要小心块与块之间不能有依赖。5. 调试中的坑、评价指标与实用建议5.1 常见问题速查表做这个项目时我遇到的最典型的问题集中在解密失败和效果不理想这两类。整理成了一张表方便你对照排查现象可能原因解决办法解密图像完全乱码扩散方向反了解密时把 XOR 的链式方向调转从末尾像素往前处理解密图像有明显黑边补零区域没有被裁掉解密后必须按原图 M、N 裁剪图像只有局部被打乱只做了行置换或列置换确认置乱阶段确实执行了两个方向的置换密钥稍微变了解密结果变化不大扩散没有链式传播增加 XOR 与前一个密文像素的关联Logistic 序列退化x0 取到了特殊值避开 0、0.25、0.5、0.75或更换 Sine 混沌映射彩色图加密后颜色通道错乱三通道没有分别处理R、G、B 各自加密后再拼接5.2 加密效果怎么验证光看加密后的图“完全看不出原图”是不够的至少要跑几个客观指标否则答辩时很容易被问住。我常用的几个指标如下直方图加密后的灰度直方图应接近均匀分布。如果直方图还有明显峰谷说明扩散不充分。相邻像素相关性从原图和密文图中各取 1000 对水平、垂直、对角相邻像素计算相关系数。原图通常接近 0.9 以上密文应该低于 0.1。信息熵8 位灰度图的理想熵是 8。实测加密后如果低于 7.9说明不够随机。NPCR 和 UACI这两项用于衡量“明文微小变化对密文的影响”通常要求 NPCR 大于 99%UACI 在 33% 附近。这些指标不用全部做完但至少把直方图和相关系数跑一下花不了几分钟却能让你的结果可信很多。5.3 几个我自己比较在意的实操细节第一所有密钥信息最好用一个 struct 集中管理。x0、mu、y0、mu2、轮数、原始尺寸都放在 key 里代码可读性会好很多。千万不要在函数里硬编码这些参数不然改一次密钥等于全项目大扫雷。第二补零后的尺寸选 max(M, N) 而不是下一批 2 的幂。这样虽然做不了 FFT 那类加速但对小尺寸图像来说内存浪费最小解密的尺寸恢复也最简单。第三如果你想把算法拿去跑更正式的对比实验建议把 Logistic 映射换成 Sine 映射或者两种映射级联。Sine 映射在 mu 接近 1 的时候也有不错的混沌特性而且对初值的退化没有 Logi stic 那么“娇气”。第四也是我自己的习惯每一次改完参数先加密再解密确认能无损还原再去做指标分析。如果还原都失败后边所有指标都白跑。别觉得自己代码没问题就跳过这步我有好几次都是因为补零区域忘了裁剪白白浪费了大半天。最后说一点个人体会图像加密这类项目的核心难点不在于代码本身而在于你对“置乱扩散”这两件事的理解深度。拿拉丁矩形做置乱拿混沌序列做扩散组合起来并不复杂但每一步的逆过程都必须严丝合缝。只要你把逆置乱和逆扩散的顺序理清楚这个项目就已经成功了一大半。后面再想优化无非是从“能跑”到“跑得稳、跑得快、指标更好看”的过程。
返回列表