尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

正弦余弦混沌图像加密:MATLAB实现原理与像素置乱异或全解析

正弦余弦混沌图像加密:MATLAB实现原理与像素置乱异或全解析 做数字图像处理实验图像加密几乎是必做的题目之一。很多同学看到“基于正弦余弦混沌映射生成随机序列对RGB三通道分别执行行移位-列移位-XOR”这种描述第一反应是找别人的MATLAB代码直接跑一遍跑通了就算交差。我建议你先别急着复制粘贴这篇文章我会把思路、公式、完整代码和排错经验一起讲清楚跑完你能自己做改动而不是只会换一张图。先说它解决什么问题普通图像相邻像素相关性强、数据冗余高直接用文本加密的思路效果并不好。而混沌序列具备“初值敏感、遍历性好”的特点用来生成随机序列做像素置乱和异或扩散能在视觉上把图片彻底打乱成雪花噪声解密时又能精确还原。适合数字图像处理课程作业、本科毕设入门以及想系统了解混沌图像加密原理的MATLAB初学者。下面直接进入正题。1. 项目整体设计与核心思路先弄清为什么加密流程要这样设计1.1 图像加密为什么要用混沌映射传统加密算法不是不能用而是用起来别扭。比如AES、DES面向的是字节流而图像本质上是一个像素矩阵数据量动辄几百KB甚至几MB冗余度高相邻像素的数值高度相关。先把图像读成字节流再用AES一圈圈扩散耗时长而且整块数据里大量的重复模式很容易被统计攻击利用。混沌映射不一样它本身是一个确定性系统给一组初始值能生成一段看起来完全随机的序列初始值差一点点后面生成的序列会差到十万八千里。这个“初值敏感性”正是加密最需要的东西密钥稍微一变密文就完全不同。这里把混沌序列当作“密钥流”来用思路和流密码很像序列本身不是密钥但序列完全由密钥决定。合法接收方只要知道初始值、参数和迭代规则就能重新生成一模一样的序列完成解密攻击者拿不到这些信息就只能面对一张雪花图。这也是为什么“生成随机序列”这一步是整个加密算法的地基后续的行移位、列移位、异或都要依赖它。1.2 正弦余弦混沌映射生成序列的原理本文采用的正弦余弦耦合映射可以写成下面这组迭代式x(n1) a * sin(pi * x(n)) b * cos(pi * y(n))y(n1) a * cos(pi * y(n)) b * sin(pi * x(n))其中 a、b 是控制参数x、y 是状态变量。每次迭代时两个状态彼此耦合x 的新值同时受旧 x 和旧 y 影响y 也一样。由于 sin、cos 本身是周期函数加上 a、b 的调制系统能在一定参数范围内进入混沌状态输出序列会表现出伪随机性。光有这两个公式还不够实际生成序列时要注意三点。第一预迭代直接从初始值开始取数前面一小段通常会受暂态过程影响不够“混沌”一般先迭代 k 次再正式取值本文用 k100。第二归一化sin、cos 的输出有正有负为了方便后续计算位移量和整数密钥用 mod(abs(x),1) 把序列映射到 [0,1) 区间。第三密钥依赖x0、y0、a、b、k 这五个量全部可以作为密钥的一部分解密时必须完全一致差 1e-10 都还原不出来。1.3 加密流程总览行移位-列移位-XOR三维度操作整个加密流程概括起来就一句话先把图像按 RGB 三通道拆开对每一个通道依次做行移位、列移位、XOR再把三个通道合并回去。用逆操作做解密。加密顺序具体如下imread 读入彩色图像分别取出 R、G、B 三个通道矩阵根据密钥参数生成足够长度的混沌序列用混沌序列对每个通道的每行做循环移位打乱行内像素顺序再用混沌序列对每个通道的每列做循环移位打乱列方向顺序生成 0~255 的整数密钥流用 bitxor 对每个像素的灰度值做异或把三个通道合并为彩色密文图像。解密顺序完全相反先对密文做一次异或再逆列移位最后逆行移位。这里有一个很容易踩的坑操作顺序一旦反了就不可能正常还原。后面第五章我会专门讲怎么排查这类问题。1.4 为什么RGB三通道要分别加密而不是合成一体彩色图像有三个通道各自描述红、绿、蓝的亮度。如果只把图像转成灰度做加密最后得到的密文是灰度图一来丢失了彩色信息本身的意义二来加密前后都能直接看到亮度结构安全性弱。三通道分别加密的好处主要在两点。第一通道内相关性被彻底打散。相邻像素之间的数值相关主要体现在每个通道内部的行、列结构上行移位和列移位正是针对这种空间相关性的。对三个通道分别执行后原来“天空是一整块蓝色”这种局部平滑结构会被打碎。第二三个通道使用的是不同序列切片或不同初始值偏移通道之间的颜色比例也被扰乱加密图很难从颜色统计上找到规律。同时三通道分别处理并不会增加多少实现难度循环三遍即可这也是算法设计时很讨巧的地方。2. 加密算法详解与关键参数每一步的原理和选参依据2.1 行移位用序列值决定每行循环移动多少格行移位的本质是像素位置置换。假设某一行有 w 个像素我们需要一个范围在 0 到 w-1 的整数表示这一行整体向右循环移动几格。最右边被挤出去的像素会从左边补回来这就是“循环移位”不会丢信息。以这一行为例如果移动量是 3那么原位置 1、2、3 的像素会被移到倒数第3、2、1个位置原位置靠后的像素依次补到前面。整行像素都改变了位置但颜色值本身一个没动。混沌序列在这里扮演的角色是“位移量表”。序列每个值都落在 [0,1) 区间对行移位来说位移量 floor(seq * w)。比如这一行第一位的序列值是 0.37图像宽度是 256那这一行就循环右移 floor(0.37*256) 94 个像素。因为序列具有伪随机性所以每一行移动的距离都不同行与行之间不会形成统一的偏移规律视觉上横方向结构就乱了。这里要特别注意位移量的计算不能越界。理论上 w 是列数floor 后的最大值为 w-1循环移位移动 w 相当于没动问题不大但为了绝对安全最好再加一次 mod 操作。代码里我会写成 shift mod(floor(seqRow(i) * w), w)。2.2 列移位把二维空间横向纵向都打乱行移位只打乱了行内顺序列方向上的垂直结构依然保留。比如图像里有一垂直边缘行移位之后边缘会断成一段段错开的线段但整列之间仍然存在某种规律。列移位就是再做一次垂直方向的循环移位对每一列根据混沌序列值决定整列向上或向下循环移动多少格位移量 mod(floor(seqCol(j) * h), h)h 是图像高度。行移位和列移位合起来相当于把一张图片看成一张拼图先横向剪开每一行并错动再纵向剪开每一列并错动。经过两步之后原图中任意一个像素的坐标都发生了改变位置信息完全被打乱。至于为什么先按行再按列而不是反过来其实两种顺序都可以只要加密和解密保持一致就行。实际操作中建议固定“先行列后”这个顺序代码结构也清晰。2.3 XOR异或像素值层面的扩散位置置换只改变了像素的位置像素值大小并没有变化。也就是说光做行移位和列移位密文图像的直方图会和原图一模一样空间结构虽然乱了但统计信息还在攻击者可以根据直方图猜出原图的大致特征。XOR这一步就是为了改变像素值本身做所谓的“扩散”。异或的性质非常好用对一个数连续异或同一个密钥两次结果还是原来的数。也就是 A xor K xor K A。加密时对每个像素做 bitxor(A, K)解密时再用同一个 K 做一次 bitxor 就能还原。因为加密和解密是同一个操作代码实现也变得非常简单。密钥流怎么来混沌序列拿到手后通过 uint8(floor(seqKey * 256)) 把 [0,1) 的小数映射成 0~255 整数也就是一个字节。这个字节再和像素灰度值做按位异或。举个例子像素值为 200密钥字节为 85200 转成二进制是 1100100085 是 01010101逐位异或得到 10011101也就是 157。下一个像素又用另一个密钥字节所以整张图的像素值都会被大幅改掉直方图趋近均匀分布。2.4 密钥与参数设定推荐的一组可复现配置混沌系统的自由度就是密钥空间。本文演示代码用到两个初始状态 x0、y0两个控制参数 a、b还有一个预迭代次数 k。它们全部可以由使用者自行指定。我的建议是初始值不要取有理数零点或整数边界尽量取带小数位的数控制参数选 a0.8、b0.2 这类能维持混沌震荡的组合预迭代次数 k 设在 50~200 之间太小消除不了暂态太大影响速度。推荐配置如下表参数推荐值说明x00.123456789初始状态密钥的一部分y00.987654321初始状态密钥的一部分a0.8正弦项权重b0.2余弦项权重k100预迭代次数序列长度h w h*w行移位用 h 个列移位用 w 个异或用 h*w 个需要说明不同论文里的“正弦余弦混沌映射”公式可能不一样参数含义也不同。本文给的是工程演示版重点在于把流程跑通。如果作业要求必须采用某篇论文的具体混沌方程替换掉 sinCosChaosMap 函数内部的公式就行其他环节不需要动。3. MATLAB代码实现与实操过程可直接复现的完整脚本3.1 环境准备与图像读取先明确环境要求本演示只需要 MATLAB 基础环境不需要额外的图像处理工具箱imread、circshift、bitxor、imshow 这些函数都是内置的。我用 MATLAB R2020a 实测过R2016b 之后应该都能跑。图像读取一行即可img imread(lena.png);但要注意三点。第一imread 读进来的数据默认是 uint8灰度值范围 0~255后续 bitxor 希望输入也是 uint8不要随手转成 double 再异或double 转回 uint8 时会有截断误差。第二有些版本的 imread 会把单通道灰度图复制成三层 RGB 通道最好用 size(img) 检查 H、W、C 三个维度。第三加密完成后 imshow 直接显示 uint8 数组就可以不要除以 255uint8 范围 imshow 自己懂。3.2 混沌序列生成函数正弦余弦耦合映射的MATLAB实现下面是核心的序列生成函数。输入参数依次是序列长度 n、初始值 x0、y0、控制参数 a、b、预迭代次数 k。输出一个长度为 n 的一维行向量所有值落在 [0,1) 区间。function seq sinCosChaosMap(n, x0, y0, a, b, k) seq zeros(1, n); x x0; y y0; for i 1:n for j 1:k xt a * sin(pi * x) b * cos(pi * y); yt a * cos(pi * y) b * sin(pi * x); x xt; y yt; end seq(i) mod(abs(x), 1); end end这里有两个细节值得展开。第一个是迭代顺序先算临时变量 xt、yt再分别赋给 x、y这样保证新 x 和新 y 都用的是同一轮旧值而不是新 x 提前影响新 y。第二个是取数位置每生成一个正式序列值之前先内部循环 k 次相当于每步都做 k 轮预迭代。这样做的好处是弱化初值在短暂过渡期的规律性让序列更“乱”。如果只想加快速度可以把 k 调低到 20但安全性会略降。3.3 行移位、列移位与XOR函数三个可复用的子模块先写行移位。输入一个单通道矩阵 img 和一个长度等于行数的序列 seqRow输出行移位后的矩阵。function imgShift rowShift(img, seqRow) [h, w] size(img); imgShift zeros(h, w, uint8); for i 1:h shift mod(floor(seqRow(i) * w), w); imgShift(i, :) circshift(img(i, :), [0, shift], 2); end end列移位同理但换一个维度function imgShift colShift(img, seqCol) [h, w] size(img); imgShift zeros(h, w, uint8); for j 1:w shift mod(floor(seqCol(j) * h), h); imgShift(:, j) circshift(img(:, j), [shift, 0], 1); end endXOR加密函数如下。seqKey 长度必须等于 h*w把它 reshape 成与图像同尺寸的矩阵就能一次完成整图异或不需要双重循环function imgXor xorEncode(img, seqKey) [h, w] size(img); key uint8(floor(seqKey * 255)); key reshape(key, h, w); imgXor bitxor(img, key); end注意我在这里用 floor(seqKey * 255) 而不是 256是为了保证 key 最大是 255不会溢出 uint8。如果你想用 0~255 完整区间可以先 floor(seqKey*256) 再 mod 256二者效果差不多。3.4 完整主流程加密、解密、对比图一条龙有了上面的子函数主脚本就很清晰了。注意每个通道生成独立序列避免三通道使用完全相同序列带来的通道间可预测性。clear; clc; img imread(lena.png); [h, w, ~] size(img); x0 0.123456789; y0 0.987654321; a 0.8; b 0.2; k 100; encImg zeros(h, w, 3, uint8); decImg zeros(h, w, 3, uint8); for ch 1:3 channel img(:, :, ch); n h w h * w; seq sinCosChaosMap(n, x0 ch * 0.01, y0 - ch * 0.01, a, b, k); seqRow seq(1:h); seqCol seq(h1:hw); seqKey seq(hw1:end); % 加密行移位 - 列移位 - XOR e rowShift(channel, seqRow); e colShift(e, seqCol); e xorEncode(e, seqKey); encImg(:, :, ch) e; % 解密XOR逆运算 - 逆列移位 - 逆行移位 d xorEncode(e, seqKey); d invColShift(d, seqCol, h); d invRowShift(d, seqRow, w); decImg(:, :, ch) d; end figure(Name, 混沌图像加密解密演示); subplot(1,3,1); imshow(img); title(原图); subplot(1,3,2); imshow(encImg); title(加密图); subplot(1,3,3); imshow(decImg); title(解密图);这里用到了两个逆移位函数定义如下。不需要真实“减移位量”直接让 circshift 反向移动即可function imgShift invRowShift(imgShift, seqRow, w) h size(imgShift, 1); for i 1:h shift mod(floor(seqRow(i) * w), w); imgShift(i, :) circshift(imgShift(i, :), [0, -shift], 2); end end function imgShift invColShift(imgShift, seqCol, h) w size(imgShift, 2); for j 1:w shift mod(floor(seqCol(j) * h), h); imgShift(:, j) circshift(imgShift(:, j), [-shift, 0], 1); end end整个流程跑完后第三个子图显示的解密图应当与原图完全一致。因为整套操作全是可逆的移位是循环的异或是自逆的逆操作按原顺序倒过来执行就行。4. 加密效果分析与安全性讨论为什么这串操作能扛住常见攻击4.1 加密前后的视觉对比跑完主脚本你会看到加密图几乎是一张带彩色噪点的“雪花图”完全看不出原图内容。这是加密成功的直观标志。三个通道经过不同的行移位、列移位和异或后RGB 三种分量的值都被打乱合并后的颜色不再具有原图的自然色彩空间结构。如果加密图仍能隐约看出物体轮廓大概率是某个环节没生效。最常见的原因是位移量全为 0比如参数写成了整数导致序列收敛到固定点w 取整后全为 0或者 Xor 密钥全是 0。后面第五章会细讲。4.2 直方图分析为什么加密后直方图会趋近均匀直方图统计每个灰度值出现的频率。自然图像的直方图通常是集中分布在某个或某几个灰度带上例如暗场景图像集中在低灰度区这本身就是可利用的统计特征。行移位、列移位不改变像素值所以单靠置换每通道的直方图不变XOR 扩散后每个像素值都被不同的密钥字节改写分布被摊平。用 MATLAB 验证很简单加密前后分别对每个通道调用 imhist 函数对比。你会发现加密图的灰度分布接近一条平线很难再看出原图的色调偏好。这就是扩散操作的价值让统计攻击失去抓手。4.3 安全性评价指标相邻像素相关性、信息熵课程作业或论文里除了贴一张加密对比图评审老师通常会要求给出量化指标。最常用的有两个。第一个是相邻像素相关系数。自然图像相邻像素的灰度值高度相关相关系数接近 1好的加密算法应该让这个值趋近 0。计算方法是在原图和密文中各随机取 3000 对水平相邻像素分别计算灰度值序列的 Pearson 相关系数。加密前的相关系数通常在 0.9 以上加密后如果能降到 0.1 以下就算合格。第二个是信息熵。信息熵反映灰度分布的随机程度8 位图像的理想值是 8。加密前熵值通常在 6~7 之间加密后应该接近 7.999。这两个指标配合直方图基本就是图像加密论文里的“老三样”。MATLAB 里都可以用几行代码算如果大家需要我后续可以再单独写一篇指标计算脚本。4.4 密钥空间与抗攻击能力为什么猜不中这个方案的密钥包括 x0、y0、a、b 四个浮点参数加上预迭代次数 k。每一个浮点参数取到小数点后 10 位理论密钥空间已经达到 10^40 量级远超暴力搜索能到达的范围更何况每次取不同 k 得到的序列也不一样。退一步说即使攻击者知道算法结构只要不知道这几个参数的具体值还是无法生成相同的混沌序列。还有一个常被忽略的点三个通道分别使用了 x0 ch0.01、y0 - ch0.01 这样的变体相当于每个通道的密钥流都不同。这避免了“三通道同密钥”可能导致的通道间差分攻击也进一步加大了解密难度。5. 常见问题与排错记录我自己踩过的坑5.1 常见报错速查表这一段放一个速查表方便直接对照。现象/报错可能原因解决办法Subscript indices must either be real positive integers混沌序列长度不够或者取整后出现 0检查 n h w h*w位移量加 modIndex exceeds the number of array elementsseqKey 或 seqRow 长度不足确认序列切分方式前 h 个给行再 w 个给列最后 h*w 个给XOR解密图与原图完全不同加密解密顺序不一致或移位方向反了逆操作为 XOR - 逆列移位 - 逆行移位移位取相反数加密图仍是雪花但能看出轮廓位移量全为0或XOR密钥全为0检查混沌参数是否选了收敛值试 a0.8,b0.2Double与uint8类型不一致报错解密时对double矩阵做bitxor确保所有处理对象都是uint8运行速度很慢三重循环加每步100次预迭代先用小尺寸图片测试程序结构不变可把k降到20imshow显示全黑uint8数据除255变成小数被截断不要对uint8图像除以255直接imshow5.2 解密结果不正确的排查思路如果你跑出来的解密图不对先别怀疑加密算法本身按下面的顺序排查。第一步确认密文图是加密函数返回的 uint8 数组而不是显示过程中被转成了 double。第二步确认解密操作顺序是“异或、逆列移位、逆行移位”不是“逆列移位、逆行移位、异或”。第三步确认逆移位使用的序列和加密时完全一样初始值差一点点都会导致完全不同的混沌序列。第四步在循环里临时输出几个像素验证比如 d(1,1) 是否等于 img(1,1)如果不等于追踪到移位还是异或出问题。我之前调试时最常犯的错就是在逆移位函数里把 shift 写成 h - shift结果越改越乱。其实 circshift 的第三个参数传负值就实现了反向移动不用自己算补位移。5.3 性能优化经验混沌映射的迭代是串行的这一步没办法完全向量化所以速度瓶颈主要在 sinCosChaosMap 的嵌套循环。想提速可以把预迭代次数 k 适当调低或者对序列生成函数做 MEX 编译但在课程作业阶段完全没必要。图像尺寸如果很大建议先用 128x128 的小图调试通过再换大图。行移位、列移位、XOR 三个函数其实都有矩阵化写法比如 XOR 那一步已经用了整图一次 bitxor而移位因为每行移动量不同循环是最直观的。还有一个细节如果图像不是标准矩形比如 PNG 有透明通道imread 读进来可能是四通道函数里 size(img) 会拿到第三维是 4通道循环 ch1:3 会漏掉 Alpha 通道。这个问题做作业时不常见但换成真实图片时要注意可以先转成 RGBimg img(:,:,1:3)。最后分享一个实践中的小经验。图像加密这类项目真正难的不是某个函数写不出来而是“可逆性”这个整体约束。加密阶段每做一步都要问自己一句解密时这一步怎么还原想清楚了代码结构就会非常清晰。建议你把加密和解密各封装成一个接口函数输入输出都是 uint8 三通道矩阵这样以后想加更多技术点比如像素块置乱、动态反馈扩散都不用改动主体框架直接在这个项目基础上扩展就行。
返回列表