恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
基于Halton序列的图像加密算法与Matlab实现
首页
资讯中心
/
基于Halton序列的图像加密算法与Matlab实现
基于Halton序列的图像加密算法与Matlab实现
发布时间:2026/10/15 12:31:06
图像加密这个方向大家见得最多的是基于混沌系统的方案比如 Logistic、Tent、Arnold 置乱等。这次我拆一个不太常见但思路很干净的实现用 Halton 序列同时完成位置扰乱和像素扰乱并且整套逻辑用 Matlab 落地。这个方案适合图像处理课程设计、信息安全方向的小型项目也适合想玩转“低差异序列 置乱扩散框架”的开发者。文章里我会把原理、代码、解密逆操作、常见报错坑一次性讲清楚你甚至可以直接把它改成自己的加密函数库。1. 整体设计思路为什么选择 Halton 序列1.1 低差异序列带来的均匀覆盖优势Halton 序列属于低差异序列简单理解就是“既不随机又看起来随机”的确定性序列。和纯随机数序列相比它最大的特点是均匀覆盖生成的坐标点不会出现大片空白区域也不会扎堆成团而是近似均匀地铺满整个空间。这个特性对图像加密非常重要。加密过程中如果用到采样点来做置换映射或密钥流生成均匀性直接决定了加密图像的扩散效果。比如用随机数生成器做置乱索引理论上总有一些区域会聚集较多像素导致密文图像局部还残留原始图像的纹理痕迹Halton 序列则能有效避免这种“采样偏置”。另外Halton 序列是纯确定性的生成逻辑不依赖系统时间或硬件熵源。这意味着只要密钥一致任何人在任何机器上都能复现完全相同的加密序列解密端不需要传输庞大的序列文件只需要同步密钥参数即可。相比之下很多基于 Logistic 混沌的方案需要额外处理浮点迭代误差对初始条件的位宽要求很高Halton 序列则简单很多。1.2 置乱与扩散的双阶段模型这套加密框架采用了经典的【位置扰乱 像素扰乱】双阶段结构也叫置乱-扩散架构。位置扰乱解决的是空间相关性问题。图像中相邻像素的灰度值通常高度相关如果直接把像素按行读取然后加密统计特征仍然容易被破解。置乱阶段通过重新排列像素位置把相邻像素打散到图像的各个位置相当于把图像“搅浑”。Halton 序列在这里的作用是生成行置换索引和列置换索引经过排序后得到一组确定性排列按这个排列重新组织图像矩阵。像素扰乱解决的是统计特征问题。即使做了置乱图像直方图仍然保留原始数据的分布规律只是位置变了。扩散阶段通过密钥流对像素值本身进行改变让密文图像的直方图趋近均匀分布从而掩盖明文图像的灰度分布信息。Halton 序列在这里生成一串 0~255 的密钥流与置乱后的像素按位异或完成像素级别的扰动。这里有一个值得注意的设计思路置乱和扩散必须是互补的不能互相替代。如果只做位置扰乱直方图完全不改变攻击者可以直接统计密文来推算明文如果只做像素扰乱相邻像素的相关性依然很高空间结构没有破坏。只有两阶段叠加才能同时破坏“空间相关性”和“灰度分布特征”这也是这套方案安全性的根基。2. Halton 序列的原理与 Matlab 实现2.1 Van der Corput 序列的基础概念Halton 序列是多维低差异序列它的基础是一维 Van der Corput 序列。构成逻辑很简单把一个正整数 n 写成基数 b 下的进制表示然后把各个位上的数字反序放到小数点后面得到一个 0 到 1 之间的数。以基数 b2 为例数字的二进制表示和对应的 Van der Corput 值如下n1二进制是 1反序后是 0.1即 0.5n2二进制是 10反序后是 0.01即 0.25n3二进制是 11反序后是 0.11即 0.75n4二进制是 100反序后是 0.001即 0.125可以发现同样长度的区间内Van der Corput 序列点分布得非常均匀。Halton 序列就是把多个不同基数的 Van der Corput 序列组合起来构成多维坐标。常用组合是二维的 (基数2, 基数3)也可以扩展到更高维只要各维基数互不相同即可。需要特别说明的是Halton 序列在高维场景下会出现周期退化问题但二维的应用场景完全不用担心基数 2 和基数 3 的组合在图像尺寸范围内表现稳定。2.2 二维 Halton 序列的生成代码Matlab 里没有直接提供 Halton 序列的现成函数但实现并不复杂核心就是写一个进制反转函数。function phi vdc(n, b) % 生成第1~n个Van der Corput序列值 % b: 基数(素数), n: 序列长度 phi zeros(n, 1); for i 1:n idx i; f 1 / b; while idx 0 phi(i) phi(i) f * mod(idx, b); idx floor(idx / b); f f / b; end end end在此基础上再加一个“跳跃”能力从某个偏移量之后开始取序列这在密钥设计中非常有用可以让不同的密钥对应不同的序列段。function phi vdc_skip(n, b, skip) % 从skip1开始取n个Van der Corput值 phi zeros(n, 1); for i 1:n idx skip i; f 1 / b; while idx 0 phi(i) phi(i) f * mod(idx, b); idx floor(idx / b); f f / b; end end end二维 Halton 坐标就更加简单了把两个基数生成的序列并成一列即可function H halton2d(n, b1, b2, skip1, skip2) H zeros(n, 2); H(:, 1) vdc_skip(n, b1, skip1); H(:, 2) vdc_skip(n, b2, skip2); end实测下来这段代码在 512×512 的图像尺寸下完全够用生成 26 万个序列点的耗时在几秒钟量级。如果你需要处理更大尺寸的图像可以考虑把 while 循环改成预计算表或直接用函数句柄提升效率这部分我在第 5 节再展开。2.3 参数选择与密钥设计Halton 序列本身是公开算法但它的初始参数可以作为加密密钥。常规做法是把这几个参数组成密钥向量参数含义推荐取值范围b1行置乱基数2, 3, 5 等素数b2列置乱基数与 b1 不同的素数b3像素密钥流基数7, 11, 13 等素数skip1行序列起始偏移100~10000 之间随机skip2列序列起始偏移100~10000 之间随机skip3密钥流序列起始偏移100~10000 之间随机我实践中的密钥格式固定为key [b1, b2, b3, skip1, skip2, skip3]例如key [2, 3, 5, 137, 421, 919]。这里要注意一个细节b1 和 b2 不能相等否则行列使用完全相同的序列置乱效果会退化成一维置换安全性大打折扣。从安全角度看跳数 skip 的作用相当于把序列从某个随机起点“剪”掉一段即使攻击者猜到了基数还要同时猜对起始偏移才能复现序列这能有效扩大密钥空间。3. 位置扰乱与像素扰乱的具体实现3.1 用排序索引构造像素置换置乱的核心思路是用 Halton 序列生成一组无序参考值然后通过排序操作产生一个置换索引。比如图像有 M 行就先生成 M 个一维 Halton 值再对这 M 个值排序。排序后的索引perm_r就是行置换向量新图像的第 i 行取原图像的第perm_r(i)行。对列方向同理。phi_r vdc_skip(M, b1, skip1); phi_c vdc_skip(N, b2, skip2); [~, perm_r] sort(phi_r); [~, perm_c] sort(phi_c); img_scrambled img(perm_r, :); img_scrambled img_scrambled(:, perm_c);这一步用排序而不是直接对 Halton 值取整是因为排序得到的置换天然是“一对一”的不会出现多个像素挤在同一个位置、某些位置却没有来源像素的情况。直接取整会造成冲突严重破坏可逆性我在早期实验里踩过这个坑后面细说。解密时则反向操作从perm_r求逆置换inv_r让img_restored img_scrambled(inv_r, :)。[~, inv_r] sort(perm_r); [~, inv_c] sort(perm_c);这里需要想明白一点加密时用的是行置乱再列置乱解密时的逆操作顺序是“先反列、再反行”因为列置乱是在行置乱之后施加的恢复时要从最后一步往前撤。第 4 节完整代码里我会严格按这个顺序写。3.2 像素扰乱异或密钥流的构造像素置乱完成后图像像素值本身没有改变直方图依然保留明文特征因此必须做像素级扩散。最简单可靠的方案是构造一串与图像尺寸相同的 0~255 整数密钥流与像素值逐位异或。密钥流的构造方式phi_k vdc_skip(M * N, b3, skip3); key_stream mod(floor(phi_k * 256), 256); key_stream uint8(reshape(key_stream, M, N)); enc bitxor(img_scrambled, key_stream);为什么用异或而不是加法有两个原因。一是异或是自逆运算解密端只需要再做一次同样的异或就能恢复明文不需要额外处理模数回绕问题二是按位异或对像素的每个二进制位独立作用扩散效果更彻底比如明文中一个像素从 127 变到 128映射到密文上可能多位同时翻转。我建议把mod(floor(...))两层嵌套保留floor(phi_k * 256)把 0~1 之间的值放大到 0~255 的整数范围mod(..., 256)则保证当浮点数值在边界上时不会越界成 256。如果你直接uint8(...)截断Matlab 默认四舍五入可能在边界处产生不可预估的结果这是解密还原时最容易出现“个别像素偏色”的根源。3.3 解密端的逆操作顺序解密是加密的镜像操作但很多人第一次实现时会把顺序搞反。加密流程是位置扰乱 → 像素扰乱那么解密必须是像素反扰乱 → 位置反扰乱。理由其实很直观加密时位置扰乱在像素扰乱之前解密时要从最后一个操作开始撤销所以先撤销像素扰乱对密文再次异或同一密钥流再撤销位置扰乱先反列再反行。这里有一个容易混淆的点异或不改变像素的位置信息置乱不改变像素的值信息两阶段互相独立。所以“先反哪一个”貌似都应该能成立理论上如果有完整的逆置乱和逆扩散顺序确实可以交换但在密钥流依赖图像尺寸、置乱索引依赖图像尺寸的前提下保持严格逆序最不容易出错也更符合加密协议设计的规范。我建议直接固定这个顺序避免以后扩展多轮加密时逻辑混乱。4. 完整加密/解密流程与代码4.1 加密主函数下面是完整封装好的加密函数。输入为灰度图像矩阵和六维密钥向量输出为加密后的 uint8 图像矩阵。function enc halton_encrypt(img, key) % 输入: img - 灰度图像(uint8或double), key - [b1,b2,b3,skip1,skip2,skip3] % 输出: enc - 加密图像(uint8) if isa(img, double) img uint8(round(img * 255)); elseif isa(img, uint16) img uint8(img / 257); end [M, N] size(img); b1 key(1); b2 key(2); b3 key(3); skip1 key(4); skip2 key(5); skip3 key(6); % 位置扰乱 phi_r vdc_skip(M, b1, skip1); phi_c vdc_skip(N, b2, skip2); [~, perm_r] sort(phi_r); [~, perm_c] sort(phi_c); img_scrambled img(perm_r, :); img_scrambled img_scrambled(:, perm_c); % 像素扰乱 phi_k vdc_skip(M * N, b3, skip3); key_stream mod(floor(phi_k * 256), 256); key_stream uint8(reshape(key_stream, M, N)); enc bitxor(img_scrambled, key_stream); end这个函数里的vdc_skip就是第 2 节给出的函数。你需要把它单独保存为vdc_skip.m或者直接写在这个脚本文件的下方。Matlab 在同一个脚本文件里可以包含多个函数但要放在主函数之后。4.2 解密主函数解密函数的核心是重新生成同样的密钥流和置乱索引然后按逆序操作。function dec halton_decrypt(enc, key) % 输入: enc - 加密图像(uint8), key - 与加密时相同的密钥 % 输出: dec - 解密还原图像(uint8) if isa(enc, double) enc uint8(round(enc * 255)); end [M, N] size(enc); b1 key(1); b2 key(2); b3 key(3); skip1 key(4); skip2 key(5); skip3 key(6); % 重新生成置乱索引并求逆置换 phi_r vdc_skip(M, b1, skip1); phi_c vdc_skip(N, b2, skip2); [~, perm_r] sort(phi_r); [~, perm_c] sort(phi_c); [~, inv_r] sort(perm_r); [~, inv_c] sort(perm_c); % 先撤销像素扰乱 phi_k vdc_skip(M * N, b3, skip3); key_stream mod(floor(phi_k * 256), 256); key_stream uint8(reshape(key_stream, M, N)); img_scrambled bitxor(enc, key_stream); % 再撤销位置扰乱先反列置乱再反行置乱 dec img_scrambled(:, inv_c); dec dec(inv_r, :); end核心就两步bitxor(enc, key_stream)把像素恢复成“仅置乱但未扩散”的状态再用逆置换inv_c和inv_r把像素位置归位。4.3 单次完整的可视化测试把整个流程跑起来非常简单用 Matlab 自带的灰度测试图即可% 读取测试灰度图 img imread(cameraman.tif); if size(img, 3) 3 img rgb2gray(img); end % 设置密钥 key [2, 3, 5, 137, 421, 919]; % 加密与解密 enc halton_encrypt(img, key); dec halton_decrypt(enc, key); % 可视化 figure; subplot(1, 3, 1); imshow(img); title(原始图像); subplot(1, 3, 2); imshow(enc); title(加密图像); subplot(1, 3, 3); imshow(dec); title(解密图像); % 验证一致性 fprintf(像素差异总数: %d\n, sum(abs(double(img(:)) - double(dec(:))) 0));正常输出应该是“像素差异总数: 0”也就是解密图像与原图逐像素完全一致。这里强调一点如果解密后图像肉眼看着没问题、细看却有零星噪点几乎一定是uint8类型转换问题下一节专门梳理这类坑。5. 常见问题与排查技巧实录5.1 解密回来图像不对像“打码雪花片”这是初学者最常遇到的问题加密图像显示正常但解密图像完全看不出原图轮廓。排查顺序我建议这样看第一检查加密和解密是否使用了同一个密钥向量。注意key传给加密函数后在解密时必须原样传入任何一个skip值错一位整个序列就会错位图像自然无法还原。第二检查图像类型是否统一。bitxor对uint8和double的处理逻辑完全不同bitxor(uint8(...), uint8(...))是按位异或而double类型会被当作浮点数处理结果完全不可控。我在加密函数里已经做了类型收敛但如果你在外部提前把加密结果转成了double解密时就必须先转回uint8。第三检查置乱和解密的逆置换方向。逆置换并不是把perm_r直接当作反向索引使用而是要先求逆。比如某一行在原图中是第 5 行加密后跑到第 2 行那么解密时必须找到“第 2 行现在属于原图的第 5 行”这个映射关系。直接复用perm_r会导致行位置来回跳图像变成网格状乱码。5.2 彩色图像和多通道处理标题里的方案核心是灰度图像处理但真正的图像不一定是灰度图。彩色图像可以拆成 R、G、B 三个通道分别加密再把三个通道重新拼合成 RGB 图像。function enc_rgb halton_encrypt_rgb(img, key) [M, N, ~] size(img); enc_rgb zeros(M, N, 3, uint8); for ch 1:3 enc_rgb(:, :, ch) halton_encrypt(img(:, :, ch), key); end end这里有个细节值得留意三个通道用同一个key加密会保留通道间的相关性严格来说安全性略有折扣。更保守的改进方式是给每个通道分配不同的skip3偏移比如通道 R 用key中的skip3通道 G 用skip3 500通道 B 用skip3 1000。这样处理后三个通道的密钥流互不相同能明显提升彩色图像的扩散效果。5.3 大图像速度太慢如何优化vdc_skip本质上是一个逐点循环的进制反转过程。对一张 1024×1024 的图像生成 104 万个序列点在普通配置的电脑上可能要十几秒这个速度在批量测试时会有些难受。优化方向有两个。一是把 while 循环改成查表法预先计算 “数字 → Van der Corput 值” 的映射表后续直接索引取值。二是改用生成器循环替代向量内逐点循环用 Matlab 的arrayfun或前面的预计算思路减少解释器开销。我实测过的改进思路是function phi vdc_fast(n, b, skip) idx (1:n) skip; phi zeros(n, 1); % 预分配最大位数, 逐位累加 for k 1:ceil(log(n skip) / log(b)) bit_digits mod(floor(idx / b^(k-1)), b); phi phi bit_digits / b^k; end end这个版本把“逐点进制反转”改成了“逐位批处理”代码可读性稍差但速度能提升一个数量级。如果你只处理 256×256 或 512×512 的图像用最初的vdc_skip完全够用不需要额外优化。5.4 逆排列写法的常见错乱逆排列是解密里最容易写错的一环。我见过不少同学把解密写成dec img_scrambled(perm_r, :)这实际上会做第二次置乱而不是还原。求逆排列的正确写法是[~, inv_r] sort(perm_r)。原理是对任意排列 pp经过sort后第二个输出参数记录的是“各元素原来的位置”这正好就是逆映射。以行置乱为例如果perm_r [3, 1, 2]表示加密时第 1 行取原图第 3 行第 2 行取原图第 1 行第 3 行取原图第 2 行sort(perm_r)后得到inv_r [2, 3, 1]解密时用inv_r依次取第 2 行、第 3 行、第 1 行顺序就回到原图。位置加密时取原图第几行解密时取密文第几行1322133216. 安全性评估与后续改进方向6.1 直方图和相邻像素相关性验证一个加密方案合不合格先看两个量化指标密文直方图是否均匀相邻像素相关性是否被打破。直方图测试代码很简单读入明文和密文后各画一张直方图肉眼对比即可。明文图像的直方图通常有明显起伏密文图像的直方图应该趋于平坦近似均匀分布。相邻像素相关性可以按方向计算。以水平方向为例从图像中随机取 N 对左右相邻像素计算相关系数function corr h_correlation(img, num_pairs) [M, N] size(img); rng(42); row_idx randi([1, M], num_pairs, 1); col_idx randi([1, N-1], num_pairs, 1); p1 double(img(sub2ind([M, N], row_idx, col_idx))); p2 double(img(sub2ind([M, N], row_idx, col_idx 1))); corr corrcoef(p1, p2); corr corr(1, 2); end明文图像的水平相邻相关性通常接近 0.9 以上加密后应该掉到 0.05 以下。如果相关性没有明显下降建议加密时增加一轮置乱和扩散把整个流程迭代 2~3 次。6.2 密钥敏感性与密钥空间密钥敏感性是指密钥只有一位微调解密结果应该完全不同。用实际操作验证的方式非常直观key1 [2, 3, 5, 137, 421, 919]; key2 [2, 3, 5, 138, 421, 919]; % 只把skip1从137改成138 enc1 halton_encrypt(img, key1); dec_wrong halton_decrypt(enc1, key2); % 计算与原图的差异比例 diff_ratio sum(abs(double(dec_wrong(:))) - double(img(:)) ~ 0) / numel(img);密钥空间方面要坦诚说基础版本的可用密钥主要来自三个基数可交换排列和三个跳数若跳数上限为 1 万则有约 10 的 12 次方组合对一般课程项目和内部工具足够但如果要做更严格的加密系统建议把 6 参数密钥扩展为 8~10 参数比如加入图像尺寸校验、多轮次迭代轮数、以及通道独立偏移。6.3 可以落地的升级方案这套框架最大的优点是结构清晰、可扩展性强。基于现有代码我建议从三个方向做升级。第一把纯 Halton 密钥流升级为 “Halton 混沌” 混合模式。Halton 序列均匀性好但伪随机性不如高质量混沌序列。先用 Halton 序列生成随机坐标再用这些坐标作为混沌系统的初始值扰动这样既保留低差异序列的均匀性又增强了不可预测性。第二改为多轮迭代结构。把加密函数中的置乱扩散封装成一轮循环执行 2~3 轮。每轮可以切换不同的跳数偏移让置乱和扩散充分混合。解密端只需要反向执行循环即可。第三引入分块处理。对大尺寸图像把图像切分成若干块分别加密块边界可以引入交叉混淆这样能提升局部抗攻击能力也方便并行化加速。个人实操经验最后分享一个小技巧调试这套代码时不要一开始就用真实图像。建议先用 8×8 或 16×16 的纯色块矩阵测试加密-解密闭环确认还原误差为 0 后再切换到大图。小矩阵的置乱效果肉眼可见像素错位一眼就能看出来而大图的细节问题容易被视觉掩盖。另外密钥向量里的跳数尽量不要取太小的值比如skip1和skip2的序列只差一个点位置攻击者容易通过穷举试探。把跳数范围设置成 100 到 10000 之间的随机数密钥空间和安全性都有明显提升。如果你最终要写实验报告或论文记得把第 6 节的直方图、相关性、密钥敏感性三个指标全部跑一遍配上数据表格比任何文字描述都有说服力。