Chen系统与DNA编码的混沌图像加密MATLAB实现解析
简介一套面向图像加密学习与研究者的MATLAB完整实现源码包围绕Chen混沌系统和DNA分块编解码技术完整演示图像加密与解密流程。资源包含主加密、解密、DNA运算、分块处理等7个m脚本同时提供lena标准图和壁纸等测试图像、加密前后效果图压缩包共16个文件、约4.61MB代码注释清晰目录按加密流程分开组织便于直接运行与二次开发。目前已有433人学习使用。通过阅读源码可掌握Chen系统混沌序列生成、DNA序列映射与替换操作、前后向加解密链路设计等关键环节并结合测试图像观察加密效果和置乱程度适合信息安全、图像处理方向的本科生和工程师作为课程设计、毕业设计或论文复现的参考实现。包内还附有README说明和.gitattributes配置文件帮助快速理解项目结构整体体量适中适合快速研读与复现。1. 为什么图像加密源码里要同时出现Chen系统和DNA第一次打开 DNA_chen混沌_混沌图像加密 这个MATLAB源码包发现它并不是把图像像素随机打乱就收工里面同时有 chen_output.m 生成混沌轨迹、DNA_bian.m 把像素编成碱基、DNA_yunsuan.m 做DNA异或最后才由 main_jiami.m 和 main_jiemi.m 完成闭环。这个组合在混沌图像加密相关实现里很常见但真正跑通的人不多因为经常卡在DNA编码规则不一致、混沌序列长度与图像不匹配、补边后解密对不上这三个地方。这篇就按源码里的文件顺序拆开讲适合正在做基于Chen系统的图像加密实现、又想改到自己图上验证效果的读者。2. Chen混沌序列生成从微分方程到MATLAB矩阵2.1 为什么选Chen系统而不是一维Logistic映射很多混沌图像加密课设会先用Logistic映射x(n1)μx(n)(1-x(n))因为代码短、参数少。但它的序列只有一个通道要生成整幅图像那么多随机数只能长循环迭代而且Logistic在部分参数区间会出现周期窗口密钥空间被压缩。Chen系统是三维常微分方程组一条轨迹同时输出 x、y、z 三个通道互不相似天然适合给图像的行、列、像素值分别生成扰动序列。Chen系统的标准形式为dx/dt a(y - x)dy/dt (c - a)x - xz c·ydz/dt xy - b·z当参数取 a35、b3、c28 时系统处于混沌状态。轨迹在三维空间形成双涡卷吸引子x 分量和 y 分量之间的相关性很低这对图像加密很重要——加密后的像素分布不应该残留明文的空间特征。参数常见取值对混沌轨迹的影响a35控制 x 与 y 之间的耦合强度b3耗散项影响 z 轴方向的收缩程度c28系统呈现混沌状态的关键参数dt0.0010.005积分步长越小轨迹越平滑计算量越大2.2 chen_output.m 的固定步长实现与调用方式源码中的 chen_output.m 一般不会用 ode45而是用固定步长欧拉法或四阶Runge-Kutta离散化。原因很直接图像加密需要在解密端用同一个初始值重新生成完全一样的混沌序列而 ode45 的自适应步长会受浮点舍入影响不同MATLAB版本或不同CPU上可能产生微小的轨迹偏移解密时就会出现局部花屏。固定步长虽然粗糙但可复现性好。function seq chen_output(x0, y0, z0, a, b, c, n) % 用固定步长欧拉法生成Chen混沌序列 % 输入 % x0,y0,z0系统初始值是整个加密算法的密钥一部分 % a,b,cChen系统参数通常用 35, 3, 28 % n需要生成的序列点数至少等于图像像素总数 % 输出 % seqn行3列矩阵三列分别对应 x, y, z 轨迹 dt 0.005; seq zeros(n, 3); x x0; y y0; z z0; for i 1:n xDot a * (y - x); yDot (c - a) * x - x * z c * y; zDot x * y - b * z; x x dt * xDot; y y dt * yDot; z z dt * zDot; seq(i, :) [x, y, z]; end end这段代码把连续微分方程转成迭代式核心是每一步用当前状态计算三个方向的变化率再乘步长 dt 后累加。因为 Chen 系统的轨迹对初始值极其敏感x00.1和x00.1000000001会在迭代几十步后产生完全不同的序列所以 x0、y0、z0 本身就是加密密钥的一部分。调用时先按图像尺寸生成足够长的混沌序列再转成 0255 的像素级密钥[M, N] size(img); n M * N; seq chen_output(0.1, 0.2, 0.3, 35, 3, 28, n); % 取 x 通道放大后取整并映射到 uint8 范围 key mod(floor(seq(:, 1) * 10^6), 256); key uint8(reshape(key, M, N));这里seq(:,1)是 x 通道轨迹乘 10^6 是为了把小数点后的微小差异放大mod(..., 256)保证结果落在 0255。注意不要用round(seq(:,1) * 255)这种方式因为混沌值乘 255 后可能集中在某些整数附近分布不均匀。用floor配合mod(..., 256)得到的密钥流直方图更接近均匀分布。混沌序列只取前 n 个点是因为图像加密通常要求密钥流与图像尺寸一致。如果图像是 512×512n 就是 262144。生成这段序列在普通PC上耗时约一两秒完全可接受。如果你发现解密后的图像有一半不对先检查 n 是否等于像素总数或者是不是把 seq 的第 1 列和第 2 列用反了。3. DNA编解码与分块操作在加密流水线中的落位3.1 像素值到碱基序列的映射规则DNA 序列由 A、T、C、G 四种碱基组成每个碱基可以用两位二进制表示。图像像素是 0255 的整数占 8 位所以一个像素可以映射成 4 个碱基。这套源码里的 DNA_bian.m 做的就是这件事把每个像素拆成 4 个碱基后续再用 DNA_yunsuan.m 对碱基序列做异或操作。常见的编码表如下二进制碱基互补碱基00AT01CG10GC11TA注意这只是一个映射规则。不同的 DNA 图像加密论文会定义 8 种不同的编码规则有些规则下 00 对应 A有些规则下 00 对应 C。关键不是选哪张表而是加密端和解密端必须使用同一张表否则 DNA_jie.m 解出来的像素值会完全错误。3.2 fenkuai.m分块补边与边界恢复源码里的 fenkuai.m 负责把图像分成若干小块。很多经典实现是分成 8×8 或 16×16 的块每个块单独做 DNA 编解码和混沌运算。分块的好处是能把局部像素绑定在一起后续可以配合块置乱提高抗裁剪攻击能力。function blocks fenkuai(img, blockSize) % 将灰度图像分成 blockSize x blockSize 的小块 % 如果图像尺寸不能整除用复制边缘像素补齐 % 输入 % img灰度图像矩阵uint8 % blockSize分块尺寸例如 8 % 输出 % blocks元胞数组每个元素是一个图像块 [h, w] size(img); padH mod(blockSize - mod(h, blockSize), blockSize); padW mod(blockSize - mod(w, blockSize), blockSize); % replicate 表示复制边缘像素比补0更平滑 imgPadded padarray(img, [padH, padW], replicate, post); blocks mat2cell(imgPadded, ... blockSize * ones(1, size(imgPadded, 1) / blockSize), ... blockSize * ones(1, size(imgPadded, 2) / blockSize)); end这里padarray用replicate而不是0填充是为了避免解密后图像四周出现一圈明显的黑边。解密端必须记录原始尺寸[h, w]在最终输出前用decImg(1:h, 1:w)裁掉补齐部分。很多人在换自己的图片时只处理了能整除的 Lena 图一遇到 300×200 这类尺寸就报错多半就是忽略了补边。3.3 DNA_bian.m 和 DNA_yunsuan.m单像素到碱基运算下面是一个可以对照源码理解的单点编码函数function d DNA_bian(pixel) % 将一个0~255像素值转为4个碱基字符串 % 编码表00-A, 01-C, 10-G, 11-T bitStr dec2bin(pixel, 8); map ACGT; d blanks(4); for i 1:4 idx bin2dec(bitStr(2*i-1 : 2*i)) 1; d(i) map(idx); end enddec2bin(pixel, 8)得到 8 位二进制字符串例如 25 变成00011001。reshape每两位一组通过bin2dec转成 03 的数字再在map中找到对应碱基。这个函数输出一个字符数组长度是 4。对应的 DNA 异或运算可以这样实现function out DNA_yunsuan(s1, s2) % 两个等长碱基字符串做DNA异或 % 编码规则沿用 DNA_bian.m 中的 ACGT 表 % 返回与输入等长的碱基字符串 map ACGT; n numel(s1); out blanks(n); for k 1:n idx1 find(map s1(k)) - 1; idx2 find(map s2(k)) - 1; out(k) map(bitxor(idx1, idx2) 1); end end这里把字符先映射为 03 的数字bitxor做按位异或再映射回碱基。这种 DNA 异或和普通二进制异或有一个相同的性质同一个碱基异或两次会还原。也就是说DNA_yunsuan(DNA_yunsuan(a, b), b) a这个性质让解密主程序不需要写独立的逆向运算函数。如果主程序需要处理整幅图可以对这个单点函数做双重循环也可以把它写成支持矩阵输入的版本。源码包里的 DNA_yunsuan.m 为了效率通常会用arrayfun或查表矩阵但只要理解了单点逻辑看矩阵版就不会发懵。4. main_jiami与main_jiemi把混沌序列和DNA运算串成完整加解密流程4.1 加密主程序的整体结构main_jiami.m 做的事情可以拆成三步读图并生成密钥流、对明文图像做 DNA 编码、与密钥流的 DNA 编码做 XOR 后解码保存。忽略分块细节后核心循环是这样的% main_jiami.m 核心循环分块逻辑省略 clear; close all; clc; img imread(lena.bmp); if size(img, 3) 3 img rgb2gray(img); end [M, N] size(img); % 用Chen系统生成密钥流 seq chen_output(0.1, 0.2, 0.3, 35, 3, 28, M * N); key mod(floor(seq(:, 1) * 10^6), 256); key uint8(reshape(key, M, N)); % 逐像素做DNA编码和DNA异或 encImg zeros(M, N, uint8); for i 1:M for j 1:N d1 DNA_bian(img(i, j)); % 明文像素 - 4个碱基 d2 DNA_bian(key(i, j)); % 密钥像素 - 4个碱基 d DNA_yunsuan(d1, d2); % DNA异或 encImg(i, j) DNA_jie(d); % 碱基还原为像素值 end end imwrite(uint8(encImg), 加密后的lena.bmp);这个双重循环在 512×512 图像上需要跑几秒主要开销在DNA_bian里的dec2bin和bin2dec。实际源码一般会把它向量化但用循环展示能让逻辑更清楚每个像素先被拆成 4 个碱基密钥像素也被拆成 4 个碱基然后对应位置做 DNA 异或最后把 4 个碱基拼回一个像素。参数说明x00.1, y00.2, z00.3是整个加密的密钥只要加解密用这组值就能还原图像。mod(..., 256)和reshape保证密钥矩阵与图像尺寸一致。DNA_jie是DNA_bian的逆过程接收 4 个碱基字符返回 0255 的数值。4.2 解密不能简单逆序DNA-XOR的自反性决定了解密和加密核心相同很多初学者拿到源码后会以为解密应该把加密步骤倒过来先 DNA_jie、再减法、再逆置乱。但在这套源码里核心运算 DNA 异或具有自反性所以解密主程序与加密主程序的差别非常小。阶段加密解密密钥流chen_output 生成相同长度序列转 uint8用相同初始值再次生成完全相同的密钥流DNA 编码明文像素编码为碱基密文像素编码为碱基DNA 运算与密钥碱基做 DNA 异或与相同密钥碱基再做 DNA 异或DNA 解码碱基还原为密文像素碱基还原为明文像素对应的 main_jiemi.m 核心循环% main_jiemi.m 核心循环 encImg imread(加密后的lena.bmp); [M, N] size(encImg); % 重新生成与加密时完全一样的密钥流 seq chen_output(0.1, 0.2, 0.3, 35, 3, 28, M * N); key mod(floor(seq(:, 1) * 10^6), 256); key uint8(reshape(key, M, N)); decImg zeros(M, N, uint8); for i 1:M for j 1:N d1 DNA_bian(encImg(i, j)); d2 DNA_bian(key(i, j)); d DNA_yunsuan(d1, d2); decImg(i, j) DNA_jie(d); end end % 如果加密时补过边这里截回原始大小 decImg decImg(1:M, 1:N); imwrite(uint8(decImg), 解密后的lena.bmp);注意如果加密时用的是fenkuai.m的补齐版本那么encImg的尺寸会大于原图解密循环结束后必须裁掉补齐部分。这里的示例为了简单直接按[M, N]循环但实际上 M 和 N 应该取size(encImg)最后用原始尺寸截断。判断是否补边的简单办法是看加密后的图片尺寸是否和size(img)一致。密钥流能否精确复现是解密成败的分水岭。Chen 系统初始值差 10^-10生成的 key 就完全不一样解密结果是一张雪花图。所以在 main_jiemi.m 里不要手动修改 x0 或参数也不要从文本文件读取密钥后做格式转换最好直接复用加密时的变量。5. 换图换参数时怎么验证和改进这个混沌图像加密源码5.1 从灰度lena换到自己的图片如果只是验证算法把imread(lena.bmp)换成自己的图片路径即可。但如果图片是彩色图需要先转灰度因为这套源码的 DNA 编解码函数是面向单通道 uint8 设计的。常见做法是直接rgb2gray但这样会丢失彩色信息。如果你想保留彩色可以对 R、G、B 三个通道分别调用相同的加密循环function encRGB encryptRGB(img, x0, y0, z0) if size(img, 3) ~ 3 error(需要彩色图); end encRGB zeros(size(img), uint8); for ch 1:3 encRGB(:,:,ch) encryptGray(img(:,:,ch), x0, y0, z0); end end这里encryptGray就是前面 main_jiami.m 里的单通道加密逻辑。三个通道用同一组 Chen 初始值没有问题但每个通道的像素位置相同密钥流相同可能会保留通道间的相关性。更好的是用 x、y、z 三列轨迹分别给 R、G、B 通道生成密钥。这样做不仅能提高安全性也让chen_output的三列输出都派上用场。如果你换的图片尺寸不是 8 的倍数使用fenkuai.m时务必在解密端记录原始尺寸。我一般会把原始尺寸直接保存到encImg的末尾像素里或者存成一个original_size.mat避免解密时还要人工猜测是否补过边。5.2 直方图、相邻像素相关性和一个实用小技巧验证加密效果最简单的方式是看直方图。加密前的 Lena 直方图有明显的山峰加密后的直方图应该接近均匀分布说明 DNA 异或把像素值扩散开了。figure; subplot(2,2,1); imshow(img); title(原图); subplot(2,2,2); imhist(img); title(原图直方图); subplot(2,2,3); imshow(uint8(encImg)); title(加密图); subplot(2,2,4); imhist(uint8(encImg)); title(加密图直方图);再算一下相邻像素相关性。自然图像水平方向相邻像素的相关系数通常接近 0.9加密后应该接近 0p1 double(encImg(:, 1:end-1)); p2 double(encImg(:, 2:end)); corrVal corrcoef(p1(:), p2(:)); disp(corrVal(1, 2));如果corrVal绝对值小于 0.05说明置乱效果基本合格。如果还在 0.3 以上说明 DNA 异或没有把相邻像素的关系打散需要在 DNA 异或前插入一次基于 Chen 序列的像素位置置乱比如同时用 y 列生成行索引、z 列生成列索引按以下方式交换[~, rowIdx] sort(seq(1:M, 2)); [~, colIdx] sort(seq(1:N, 3)); imgShuffle img(rowIdx, colIdx);这里sort返回的索引序列是混沌轨迹升序排列后的位置相当于把所有行和列重新排队。解密端只需要用同样的seq再做一次sort然后按逆索引invRow(rowIdx)1:M恢复即可。加入这一步后DNA 异或负责改变像素值置乱负责打乱像素位置两种操作各管一头抗统计攻击的能力会明显增强。另外加密结果一定要存成 BMP 或 PNG 这种无损格式不要存 JPG否则压缩带来的像素变化会让 DNA_jie 解出噪点——这是这类无损加密源码最容易踩的坑。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联
返回资讯列表 →