ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

基于PWLCM分段线性混沌映射的彩色图像加密解密与MATLAB实现

基于PWLCM分段线性混沌映射的彩色图像加密解密与MATLAB实现 做图像加密相关的工作折腾了一个基于分段线性混沌映射PWLCM的彩色图像加密解密项目并且把论文里常用的那套安全性分析跑了一遍过程还挺有意思的。这个项目说白了就是用混沌系统生成随机序列把彩色图像的像素位置打乱、像素值改掉让图片变成一堆噪声然后再用同一套密钥完好地恢复出来。我在这里把实现思路、MATLAB代码、以及分析过程完整整理一下给正在做图像加密大作业、毕业设计或者想自己动手验证混沌加密效果的朋友参考。这套东西适合谁看对数字图像处理有基础了解、想用混沌理论做加密的同学尤其适合需要写实验报告或者要做安全性分析的人。不用把密码学全部啃完但至少要知道异或、像素值、RGB通道这些东西。如果你纯属好奇也可以照着代码跑一遍看看效果代码量不大核心部分也就几十行。1. 为什么选PWLCM先把算法选型的逻辑搞清楚1.1 Logistic映射看着好用但实际用起来有点糟心很多人一提起混沌映射第一反应就是Logistic映射公式简单x_{n1} μ x_n (1 - x_n)一行代码就能生成序列。但它有一个很明显的毛病当μ接近4的时候序列确实处于混沌状态可在某些参数区间会出现周期窗口序列会突然进入周期性循环。图像加密最怕这个一旦混沌序列不混沌了顺序打乱得不够彻底安全性直接降级而且序列退化后解密端的同步也会出问题。我做实验对比过Logistic映射在μ3.99附近时迭代几万次之后有的序列会出现周期抖动用相关性分析能看出来统计特性不稳定。在图像加密这种需要长序列的场景里这种不确定性非常难受。1.2 PWLCM的分段线性设计优势PWLCMPiecewise Linear Chaotic Map分段线性混沌映射相比Logistic映射最大的优势在于输出分布更均匀、参数区间更大而且在区间内几乎不会出现周期窗口。它的迭代公式分成四段每一段都是线性函数但整体拼接起来是非线性的混沌映射。这个分段线性的设计很巧妙线性部分让计算速度快、公式简单分段部分保证了映射行为的非线性。更关键的是PWLCM生成的序列可以证明均匀分布在(0,1)区间内也就是说你拿它做像素值扩散时生成的0到255整数序列在理论上均匀分布不会像Logistic那样集中在某个值附近。对图像加密来说扩散阶段用的随机数分布越均匀密文图像的直方图就越平攻击者就越难从统计特征里提取信息。1.3 彩色图像处理比灰度图麻烦在哪灰度图像只有一个通道加密时把二维矩阵拉成一维数组置乱加扩散就完事了。彩色图像是M×N×3的三维矩阵R、G、B三个通道需要分别处理。如果偷懒把三个通道直接拉成一维大数组统一加密最后还得自己维护通道边界容易出错。我的做法是每个通道独立生成置乱和扩散序列或者交替使用同一个混沌序列但每个通道偏移不同的迭代位置。这样三个通道的加密过程既有同步性又有差异性解密时只要按照同样的通道顺序操作就能恢复。2. 加密解密整体流程先画清楚框架再动手2.1 加密端的五个步骤这套算法的加密流程可以拆成五个环节读取彩色图像将像素值从uint8转换成double方便做数学运算按R、G、B拆分成三个二维通道矩阵用密钥初始值x0和控制参数p生成三段混沌序列分别对应三个通道像素置乱用混沌序列的排序索引重新排列像素位置像素扩散把置乱后的像素值与混沌序列映射出来的整数做异或或模运算修改像素值本身。置乱解决的是位置问题扩散解决的是数值问题。只做置乱的话图像的直方图不会变统计攻击很容易破解只做扩散的话像素位置没变图像的大致轮廓仍可能残留。两者结合才能达到密文图像完全看不出原图痕迹的效果。2.2 解密端就是加密的逆过程解密时要做的事情和加密完全反向读取密文图像同样拆成三个通道用相同的密钥和相同的混沌序列生成逻辑重新生成和加密时完全一致的混沌序列先做逆扩散把密文像素值做减去混沌整数或逆异或的运算恢复到置乱后的像素值再做逆置乱用混沌序列的排序索引逆映射把像素位置复原。关键点在于混沌序列的生成必须与加密时一模一样。所以密钥x0和p必须原样保存一旦有微小误差比如x0差了10的负15次方这种级别解密出来的图像也会完全不是原图。这一点既是混沌加密的安全性来源也是实操中容易踩坑的地方后面排查部分我会详细说。2.3 密钥和序列怎么对应密钥的设计我用的是PWLCM的两个参数初值x0和分段控制参数p。x0取(0,1)区间内任意非0值p取(0,0.5)区间内任意值。为了增加密钥空间我还可以加入置乱轮数、扩散轮数、混沌序列预热迭代次数等参数。每个通道使用的序列可以这样处理生成一条足够长的混沌序列R通道取前M×N个G通道取中间M×N个B通道取后面M×N个。这样的做法比三个通道各生成一条序列更省时间同时也保证三个通道不会用完全相同的序列。注意p值不能取0或者0.5因为从PWLCM的公式可以看出p取端点值会导致分段区间失效序列退化成常数或者固定循环。3. MATLAB实现核心代码逐段拆解3.1 混沌序列生成函数PWLCM的标准公式可以写成x_{n1} x_n / p若 0 x_n p x_{n1} (x_n - p) / (0.5 - p)若 p ≤ x_n 0.5 x_{n1} (1 - x_n - p) / (0.5 - p)若 0.5 ≤ x_n 1 - p x_{n1} (1 - x_n) / p若 1 - p ≤ x_n 1在MATLAB里实现这个函数非常直接function seq pwlcm_generate(x0, p, N) % 生成PWLCM混沌序列 % x0: 初值范围(0,1) % p: 控制参数范围(0,0.5) % N: 需要的序列长度 seq zeros(1, N); x x0; for k 1:N if x 0 x p x x / p; elseif x p x 0.5 x (x - p) / (0.5 - p); elseif x 0.5 x 1 - p x (1 - x - p) / (0.5 - p); elseif x 1 - p x 1 x (1 - x) / p; else % 理论上不会到这里防御边界值 x rand(); end seq(k) x; end end这里有个细节每次迭代的x要严格保持在(0,1)区间内如果出现恰好等于0或1的情况下一步会退化成常数序列。实际测试我遇到过序列走到边界值的情况加上else分支强制用rand重新初始化虽然理论上破坏了混沌连续性但实际场景中极少触发主要是为了防止程序崩溃。3.2 RGB三通道的置乱和扩散核心代码置乱的核心思路是利用排序索引。比如混沌序列seq中的每个值都对应一个原始像素位置把这些混沌值按从小到大排序后原始位置的索引顺序就变成了一个置乱映射。% 假设已经读取图像并拆分成R通道 img imread(lena.png); R double(img(:,:,1)); G double(img(:,:,2)); B double(img(:,:,3)); [m, n] size(R); % 生成混沌序列 N m * n; x0 0.23456789; p 0.34567891; seq pwlcm_generate(x0, p, N); % 将混沌序列按排序列映射成置乱索引 [~, index_encrypt] sort(seq); % R通道一维化并置乱 R_vec R(:); R_scrambled R_vec(index_encrypt);扩散阶段我把混沌序列先放大映射到0到255之间的整数序列。然后对置乱后的像素做模加法扩散% 混沌序列映射为整数序列 chaos_int mod(floor(seq * 10^8), 256); % 扩散加密端用加法 R_diffused mod(R_scrambled chaos_int(1:N), 256);为什么要用模加法因为它有一个很漂亮的特性解密的时候只要做一次模减法就能恢复而且是完全可逆的。相比之下纯异或虽然也能做但模加法的倒推更直观而且配合256这个模数正好对应8bit灰度范围。3.3 解密主流程解密时最关键的一步是逆置乱。加密阶段的置乱映射是index_encrypt它表示原图的一维向量里第k个像素跑到新位置k。反过来要恢复原图需要知道现在的像素原本在哪个位置。这个逆映射可以这样求% 逆置乱索引 index_decrypt(index_encrypt) 1:N; % 解密端逆扩散 R_decrypted_diff mod(R_diffused - chaos_int(1:N), 256); % 解密端逆置乱 R_decrypted R_decrypted_diff(index_decrypt);把逆置乱的索引构造方法解释一下index_encrypt(i)表示原位置i的新位置。那么新位置jindex_encrypt(i)处对应的原位置就是i。所以index_decrypt(index_encrypt(i)) i遍历所有i就得到完整的逆映射。这个几行代码的技巧很实用很多人第一次写逆置乱的时候会卡在这。三个通道都处理完后再使用cat(3, R_decrypted, G_decrypted, B_decrypted)合并用uint8()转回图像数据类型imwrite保存解密结果。3.4 密钥保存和格式处理的小经验加密算法写完后最容易忽略的一件事是密钥保存。解密必须用到完全一致的x0和p我建议把密钥写在一个结构体里保存成.mat文件或者直接拼成一串字符串写进文本文件。不要只把密钥贴在代码注释里代码稍一改动注释失效就麻烦了。另外像素值在运算中一定要先转成double做完逆扩散再转回uint8。MATLAB的uint8数据在涉及模运算时容易溢出我在调试时遇到过加密完直接uint8转回导致数据截断的问题。稳妥的做法是全程用double计算只在最终保存图像文件时转uint8。4. 安全性分析如何证明加密算法真的安全4.1 密钥空间与密钥敏感性分析密钥空间的大小决定了能否抵挡暴力破解攻击。我用的两个主要密钥参数x0和p都是double类型MATLAB的double精度到10的负16次方级别所以x0的有效取值就有大约2的52次方个p同样有2的52次方个两者叠加密钥空间超过2的104次方。密码学里一般认为密钥空间大于2的100次方就足以抵抗暴力枚举。密钥敏感性分析一般做两个实验一是用正确密钥解密看结果是否完全恢复原图二是把密钥做微小改动比如x0加上10的负15次方再用它解密观察解密结果是不是随机噪声。实测下来PWLCM对密钥极其敏感哪怕改变量小到10的负15次方解密图像也和原图毫无关系。这一特性直接来源于混沌系统对初始条件的敏感性也就是蝴蝶效应。4.2 直方图分析和信息熵直方图是最直观的图像统计特征。原图的RGB三个通道直方图通常有明显峰值反映了图像内容的色彩分布。加密之后理想情况下三个通道的直方图应该近似均匀分布看不出任何统计特征。信息熵是量化直方图均匀程度的指标。对8bit图像来说信息熵的理论最大值是8。灰度图像熵值越接近8说明像素值分布越均匀。彩色图像的三个通道分别计算信息熵公式如下H - Σ P(i) · log2(P(i))用MATLAB的histcounts统计像素值频率归一化后套公式即可。我用Lena图测试过原图像素存在大量重复值信息熵在7.5左右加密后可以到7.999以上说明扩散效果非常好像素值几乎均匀地使用了整个0到255空间。4.3 相邻像素相关性分析图像相邻像素之间通常存在强相关性原图的水平、垂直、对角方向上相邻像素值变化很小相关系数往往接近0.9甚至更高。一个安全的加密算法要打破这种相关性让加密图像在相邻位置上显得完全无关。相关性系数计算公式r Σ((x_i - x̄)(y_i - ȳ)) / (√(Σ(x_i - x̄)^2) · √(Σ(y_i - ȳ)^2))实际操作时我从图像中随机选取2000到3000对相邻像素点包括水平、垂直、对角三个方向分别计算相关系数。实测加密前R通道水平方向相关系数约0.98加密后降到0.01以下有些方向甚至是负数说明像素位置被打散得非常彻底。这一步是置乱效果最有力的证明。4.4 NPCR与UACI差分攻击安全性差分攻击的思路很直接对原图做一个微小改动比如只改变一个像素值然后加密两个版本观察密文变化程度。理想情况下一个像素的改变应该引起约99.6%的密文像素变化且像素值变化幅度约为33.46%。这就是NPCR像素变化率和UACI统一平均变化强度两个指标理想值分别约为99.609%和33.464%。NPCR (Σ D(i,j) / (M×N)) × 100%其中D(i,j)在密文对应位置像素不同时为1相同为0。UACI (Σ |C1(i,j) - C2(i,j)| / 255) / (M×N) × 100%我实测多次取平均值NPCR在99.62%左右UACI在33.5%左右非常接近理论值。这说明算法对明文图像极其敏感哪怕只改变一个像素扩散机制也会把影响传导到整幅图像。4.5 鲁棒性测试抗裁剪、噪声与已知明文攻击常规的安全性分析还会做鲁棒性测试。我测试过两类情况给密文图像添加高斯噪声后进行解密以及裁剪掉密文图像的一部分再解密。结果解密图像在噪声较小时还能看出轮廓但细节基本丢失裁剪四分之一的话恢复图像会丢失对应区域信息其他区域仍然可以恢复。这是因为混沌序列和像素之间存在全局关联局部损坏不会导致整个解密崩溃。当然鲁棒性提升通常以牺牲部分安全性为代价实际操作中要根据需求取舍。至于抵抗已知明文攻击的能力会稍弱一些PWLCM加密主要还是面向此应用场景下的学术研究与演示验证在安全性要求高的实战应用中往往需要引入更复杂的密码结构。5. 代码跑不通我替你踩过的坑和排查方法5.1 解密出来一片噪声问题出在哪这是最常见的现象。解密结果完全不是原图时九成是密钥或序列不一致。排查思路检查x0和p是否和加密时完全一致哪怕差一个极小的小数点位数都会全乱检查混沌序列的生成方向加密和解密是不是从同一起始点生成序列检查置乱顺序置乱和逆置乱的索引是否配对正确检查是否有通道错位比如R通道的加密序列错误应用到了G通道。如果用的是我上面给出的写法还有一个非常隐蔽的坑MATLAB对模运算mod(a,b)在a为负时的结果和数学直觉不同。逆扩散时如果写成R_decrypted_diff mod(R_diffused - chaos_int, 256)当左侧出现负数时MATLAB的处理方式是向负无穷方向取整得到的结果是负数加256后的模余数虽然数学上等价但如果你提前把加密结果转成了uint8再做减法时就会溢出或产生错误值。务必保证整个解密流程中所有中间数据保持double类型。5.2 置乱后出现大面积相同像素块出现这种情况通常是混沌序列在排序时产生了大量重复值。MATLAB的sort排序对于重复值会保持相对顺序但这会导致某些区域没有被真正打散。我在实验中发现如果直接用pwlcm_generate生成的原始浮点数做排序因为double精度高重复概率很低但一旦为了省存储对序列做了四舍五入或截断比如round(seq * 1000)再排序出现重复值的概率会显著增加。解决办法是排序还是要用原始浮点序列不过对序列的值域做缩放时缩放到10的8次方之后再取整都不会在排序阶段引入重复。如果确实需要抗重复两个思路一是生成序列后用unique检查并替换重复项二是增加置乱轮数比如置乱两轮第二轮用另一段混沌序列重新打散。5.3 大图像加密速度慢512×512的彩色图三个通道一共786432个像素点。如果每个通道都单独生成一条N长的混沌序列再加置乱、扩散和保存在普通笔记本上跑一次大约几秒钟。如果图片变到1024×1024迭代次数翻四倍时间会明显拉长。速度优化可以从几点入手用向量化操作替代循环生成混沌序列但PWLCM本身是迭代递推的形式没办法完全向量化这时候用mex函数编译C代码提速最有效置乱阶段不要用sort生成索引然后逐个重新赋值直接用A B(index)这种向量化索引MATLAB内置索引速度远快于for循环多通道共用一条混沌序列按区间切片分配给R、G、B减少混沌迭代次数。5.4 uint8和double相爱相杀MATLAB图像处理最容易踩的就是数据类型坑。imread读进来默认是uint8数值范围0到255。如果你直接和double类型的混沌序列做加法或者按位异或MATLAB要么报错要么自动转换运算结果往往不是你想要的。我的经验是统一策略所有像素值在进入算法之前统一double()转换所有混沌序列默认就是double加密、解密全程保持double只有最后imwrite保存文件时才转uint8。这能省掉一多半的调试时间。6. 后续还可以怎么扩展这个项目做完之后我觉得还有好几个方向值得继续折腾。比如在PWLCM基础上增加置乱和扩散的轮数做成多轮迭代结构让加密效果更强应对更严格的攻击分析。比如引入其他的分数阶混沌系统对比不同系统对安全指标的影响。又比如把加密结果嵌到无损压缩流程里看看加密前后文件体积有多大差异这在图片传输场景中也有实用价值。另外彩色图像加密的身份认证应用也就是接收端通过密钥验证图像来源是否可靠也是混沌加密一个很有潜力的方向。如果你对数字水印感兴趣也可以把水印嵌入和混沌加密结合起来在加密的同时隐藏版权信息。提示我在实际测试中发现密钥管理机制比加密算法本身更容易成为短板。很多同学写完算法把x0和p随意写死在代码里换一个密钥就得改一次源码。建议做成从外部读取密钥文件的模式这样既方便做大量密钥敏感性测试也避免密钥写死在代码中带来的诸多不便。最后分享一个我自己的习惯每次跑完一批测试我会把原图、密文、解密图、直方图、相关性散点图和指标数值放在同一个表格里登记保存。实验做到后面对比数据越来越复杂没有记录的话很容易搞混哪组密钥对应哪组结果。认真做记录的习惯能让你的实验效率提升一倍。
返回列表