Chen系统与DNA编码的混沌图像加密MATLAB实现解析
2026/9/16 14:02:07 网站建设 项目流程

简介:一套面向图像加密学习与研究者的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(n+1)=μx(n)(1-x(n)),因为代码短、参数少。但它的序列只有一个通道,要生成整幅图像那么多随机数,只能长循环迭代;而且Logistic在部分参数区间会出现周期窗口,密钥空间被压缩。Chen系统是三维常微分方程组,一条轨迹同时输出 x、y、z 三个通道,互不相似,天然适合给图像的行、列、像素值分别生成扰动序列。

Chen系统的标准形式为:

  • dx/dt = a(y - x)
  • dy/dt = (c - a)x - xz + c·y
  • dz/dt = xy - b·z

当参数取 a=35、b=3、c=28 时,系统处于混沌状态。轨迹在三维空间形成双涡卷吸引子,x 分量和 y 分量之间的相关性很低,这对图像加密很重要——加密后的像素分布不应该残留明文的空间特征。

参数常见取值对混沌轨迹的影响
a35控制 x 与 y 之间的耦合强度
b3耗散项,影响 z 轴方向的收缩程度
c28系统呈现混沌状态的关键参数
dt0.001~0.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,c:Chen系统参数,通常用 35, 3, 28 % n:需要生成的序列点数,至少等于图像像素总数 % 输出: % seq:n行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 系统的轨迹对初始值极其敏感,x0=0.1x0=0.1000000001会在迭代几十步后产生完全不同的序列,所以 x0、y0、z0 本身就是加密密钥的一部分。

调用时,先按图像尺寸生成足够长的混沌序列,再转成 0~255 的像素级密钥:

[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)保证结果落在 0~255。注意不要用round(seq(:,1) * 255)这种方式,因为混沌值乘 255 后可能集中在某些整数附近,分布不均匀。用floor配合mod(..., 256)得到的密钥流直方图更接近均匀分布。

混沌序列只取前 n 个点,是因为图像加密通常要求密钥流与图像尺寸一致。如果图像是 512×512,n 就是 262144。生成这段序列在普通PC上耗时约一两秒,完全可接受。如果你发现解密后的图像有一半不对,先检查 n 是否等于像素总数,或者是不是把 seq 的第 1 列和第 2 列用反了。

3. DNA编解码与分块操作在加密流水线中的落位

3.1 像素值到碱基序列的映射规则

DNA 序列由 A、T、C、G 四种碱基组成,每个碱基可以用两位二进制表示。图像像素是 0~255 的整数,占 8 位,所以一个像素可以映射成 4 个碱基。这套源码里的 DNA_bian.m 做的就是这件事:把每个像素拆成 4 个碱基,后续再用 DNA_yunsuan.m 对碱基序列做异或操作。

常见的编码表如下:

二进制碱基互补碱基
00AT
01CG
10GC
11TA

注意这只是一个映射规则。不同的 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 end

dec2bin(pixel, 8)得到 8 位二进制字符串,例如 25 变成'00011001'reshape每两位一组,通过bin2dec转成 0~3 的数字,再在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

这里把字符先映射为 0~3 的数字,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里的dec2binbin2dec。实际源码一般会把它向量化,但用循环展示能让逻辑更清楚:每个像素先被拆成 4 个碱基,密钥像素也被拆成 4 个碱基,然后对应位置做 DNA 异或,最后把 4 个碱基拼回一个像素。

参数说明:x0=0.1, y0=0.2, z0=0.3是整个加密的密钥,只要加解密用这组值,就能还原图像。mod(..., 256)reshape保证密钥矩阵与图像尺寸一致。DNA_jieDNA_bian的逆过程,接收 4 个碱基字符,返回 0~255 的数值。

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,加密后应该接近 0:

p1 = 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 解出噪点——这是这类无损加密源码最容易踩的坑。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询