news 2026/9/29 8:35:52

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于拉丁矩形置乱与混沌映射的图像加密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 到 n,L(i, j) 恰好是 basePerm(i) 开始的一组循环移位,所以每一行都是完整排列。对固定列 j,i 变化时 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(n+1) = 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 映射在 mu=4 附近对初值极其敏感,密钥稍微差 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 主函数架构

把上面的模块串起来,加密主流程大概是这样的:

  1. 读入图像,如果是彩色图则转灰度,或者对 R、G、B 三个通道分别处理。
  2. 将图像补零成 n×n 方形矩阵,转成 uint8 或 double 范围 0 到 255。
  3. 用 key.x0、key.mu 生成拉丁方,做两步置乱。
  4. 用 key.y0、key.mu2 生成混沌序列,做链式 XOR 扩散。
  5. 可选地重复步骤 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 那么“娇气”。

第四,也是我自己的习惯:每一次改完参数,先加密再解密,确认能无损还原,再去做指标分析。如果还原都失败,后边所有指标都白跑。别觉得自己代码没问题就跳过这步,我有好几次都是因为补零区域忘了裁剪,白白浪费了大半天。

最后说一点个人体会:图像加密这类项目的核心难点不在于代码本身,而在于你对“置乱+扩散”这两件事的理解深度。拿拉丁矩形做置乱,拿混沌序列做扩散,组合起来并不复杂,但每一步的逆过程都必须严丝合缝。只要你把逆置乱和逆扩散的顺序理清楚,这个项目就已经成功了一大半。后面再想优化,无非是从“能跑”到“跑得稳、跑得快、指标更好看”的过程。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/29 8:34:45

Claude 金融 Skills 开源:用 TaoToken 统一 Key 跑通投研 Agent 配置

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/29 8:32:41

大麦抢票脚本完整指南:如何用 3 个接口请求完成抢票

大麦抢票脚本完整指南:如何用 3 个接口请求完成抢票 【免费下载链接】Automatic_ticket_purchase 大麦网抢票脚本 项目地址: https://gitcode.com/GitHub_Trending/au/Automatic_ticket_purchase Automatic_ticket_purchase 是一个大麦网抢票脚本&#xff0c…

作者头像 李华
网站建设 2026/9/29 8:31:00

C++函数模板实现数组排序输出:类型抽象与泛型编程入门

1. 题目背后的真实意图:为什么数组排序要扯上函数模板不知道你有没有这种感觉:明明就是一道“把数组排个序再输出”的练习题,偏偏题目后面要加个括号,写上“函数模板”四个字。很多同学第一反应是“哦,就是用一个模板来…

作者头像 李华