ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于 Halton 序列的图像加密算法:Matlab 实现位置扰乱与像素扰乱

基于 Halton 序列的图像加密算法:Matlab 实现位置扰乱与像素扰乱 先聊个实际场景。早些年我做信息安全相关项目时拿到一张普通照片做明文传输实验抓包工具里直接就能看清原图内容连像素都没变过。这件事让我意识到图像数据如果不做加密在传输链路、云端存储、甚至数据库备份里都是“裸奔”状态。后来我接触到一个很有意思的思路用 Halton 序列驱动图像加密把位置扰乱和像素扰乱结合起来纯 Matlab 实现不依赖任何重型密码库。这套方案的亮点在于序列生成简单、置乱效果好、密钥空间灵活非常适合作为图像加密入门项目也适合做毕业设计或课程实验的基线方案。我要拆解的核心内容是Halton 序列为什么适合做图像加密、它和普通随机数有什么区别、位置扰乱和像素扰乱分别解决什么问题、Matlab 代码该怎么组织以及我在实际调试中踩过哪些坑。无论你是刚接触图像加密的本科生还是想扩展加密算法工具箱的研究生这篇文章都可以直接当参考。1. 为什么选Halton序列做图像加密1.1 图像加密的基本套路先给不熟悉的读者铺垫一下背景。图像加密的基本思路和文本加密类似但又有自己的特殊性。图像数据的数据量大、冗余度高、像素之间相关性极强所以单纯用 AES 这类分组密码逐块加密效率低不说还容易破坏图像原有的统计特性导致密文膨胀。图像加密更常用的做法是“置乱-扩散”框架。置乱对应的就是标题里的位置扰乱目的是把像素的物理位置打乱让原图的轮廓和结构信息消失。扩散对应的是像素扰乱目的是改变像素的灰度值或颜色值让直方图变得均匀从而隐藏图像的统计特征。一个完整的图像加密算法通常需要这两步配合前面只打乱位置不改像素值直方图和原图完全一致攻击者光看直方图就能猜出图像的大致内容只改像素值不打乱位置图像的空间相关性还在轮廓依然清晰可辨。在置乱这一步传统方案里最常见的是 Arnold 变换和基于伪随机数的乱序。Arnold 变换实现简单但周期性固定而且对图像尺寸有约束基于 randperm 或 Logistic 混沌序列的乱序方法则依赖随机数生成质量。我这次项目里换了一个思路用 Halton 序列来生成置乱索引和密钥流效果上有一个非常明显的优势均匀性。1.2 Halton序列到底是什么Halton 序列是一种低差异序列也叫拟随机序列。你可以把它理解成一套“专门用来均匀撒点”的数列生成规则。它和 rand 函数生成的那种伪随机数最大的区别在于伪随机数是尽量模仿“杂乱无章”点与点之间会有聚集和空白Halton 序列则是刻意让点与点之间不要靠得太近尽量铺满整个空间。Halton 序列的构造原理基于 Van der Corput 序列。每个维度选一个质数作为基数比如第 1 维用基数 2第 2 维用基数 3第 3 维用基数 5。生成第 n 个数时先把 n 写成对应基数的进制表示然后反转数字顺序再放到小数点后面。举个例子用基数 2 生成第 6 个点。6 的二进制是 110反转后变成 011放到小数点后就是 0.011十进制就是 0.375。这个操作对任意 n 和任意质数基数都成立。把连续生成的这些点画在二维坐标里你会看到点分布得极均匀不会像伪随机数那样出现明显的“抱团”现象。实现上也很简单不用装额外工具箱一个循环就能搞定。我贴一个最基础的版本function seq halton_sequence(n, base) seq zeros(n, 1); for idx 1:n f 1; r 0; i idx; while i 0 f f / base; r r f * mod(i, base); i floor(i / base); end seq(idx) r; end end这段代码就是最原始的 Halton 序列生成逻辑它的本质是“按位权重累加”。虽然效率不高但非常适合理解原理。实际项目中如果图像是 512×512就需要生成 262144 个点用这个版本会有点慢我在第 5 节会讲优化方式。1.3 对比伪随机数低差异序列的优势为什么图像加密要特意用 Halton 序列而不是直接用 randperm我做了几组对比实验后发现 Halton 序列在置乱场景下有两个很实际的优点。第一低差异序列的覆盖性更好。伪随机数生成器在样本量不够大的时候很容易出现局部聚集导致某些区域像素被“推”到一起置乱后的图像可能出现局部纹理残留。Halton 序列因为天生均匀映射出来的置乱索引会“雨露均沾”整幅图像的空间结构被打散得更彻底。第二Halton 序列的生成过程完全确定性只需要基数和起始索引两个参数就能完全复现。这意味着我们可以把“基数组合 起始偏移量”直接当作密钥的一部分。密钥空间不需要依赖大数运算而是靠参数组合来扩展这种方式在轻量级图像加密场景里很讨巧。不过它也有一个明显的弱点Halton 序列是确定性的而且算法公开如果攻击者知道基数很容易穷举出序列。所以在实际加密方案里我不会让它单独工作而是把它和混沌系统或 Logistic 映射结合让 Halton 序列先产生一组“骨架”再用混沌迭代结果去做二次扰动。这样既保持了快速性又提高了安全性。2. 加密方案整体设计2.1 攻击者视角单独位置扰乱为什么不够在真正动手写代码之前你可以先站在攻击者的角度审视一下自己的方案。如果只做位置扰乱也就是把像素坐标重排那得到的结果图虽然看起来像雪花但它的直方图分布和原图一模一样。直方图反映的是每个灰度值出现的频次像素位置变了频次不变所以加密图像的直方图会完全暴露原图的灰度级分布。举个例子如果原图是一张以暗色调为主的山景照片那么直方图会明显偏向低灰度区域。攻击者拿到仅做位置扰乱的“密图”后不需要还原像素位置光看直方图就能判断这是一张暗色调照片甚至可以推测出原图的动态范围。这在很多隐私保护场景中是致命的。所以位置扰乱的意义我把它定义为“破坏空间结构”而像素扰乱的意义是“破坏统计分布”。两者缺一不可。这也是我这个项目叫“位置扰乱 像素扰乱”的原因两个环节一个都不能省。2.2 双扰乱框架位置扰乱 像素扰乱整个加密流程我设计成四个阶段按顺序执行用 Halton 序列生成像素级索引对图像做全局位置置乱。用另一组 Halton 序列作为种子生成 Logistic 混沌序列。将混沌序列量化为密钥流与置乱后的图像逐像素异或。再做一轮前后依赖扩散即当前密文像素依赖前一个密文像素的值。这样处理后攻击者面对的不只是“位置乱了”的图像而是每个像素既和原始位置无关又和原始灰度值无关而且单个像素的变化会通过扩散过程传导到后续所有像素。这就是经典的“混淆-扩散”结构只是我的驱动源换成了 Halton 序列。彩色图像的处理也在这个框架内统一处理。我的做法是把 RGB 三个通道拆开各自转成灰度矩阵然后分别执行同样的置乱和扩散流程。当然也可以把三通道拼接成一个 M×3N 的大矩阵整体置乱效果差不多但通道内相关性会更强一点我建议按通道独立处理。2.3 密钥设计Halton序列参数作为密钥密钥设计是整个方案的安全核心。我用了三组参数作为密钥第一组是位置置乱用的 Halton 序列基数组合比如 base1、base2分别用于行方向和列方向的索引生成。我把基数范围限制在质数集合 {2, 3, 5, 7, 11, 13} 内这样便于穷举测试同时也形成约 6×6 的基数组合空间。第二组是 Skip 参数即生成序列时跳过前 Skip 个点。这个参数可以不局限于整数甚至可以和图像尺寸关联形成一个动态偏移。我测试时常用 1000 到 5000 之间的随机值。第三组是 Logistic 混沌系统的初值 x0 和控制参数 μ用于像素扰乱阶段的密钥流生成。三段密钥连起来组成完整的密钥串解密端必须完全一致才能恢复原图。这个设计思路比较灵活你可以根据需要自己扩展比如把图像尺寸的哈希值混入基数选择过程让同一张图的密钥流每次都不一样。下面是加密流程的总框架function [enc_img, key] halton_image_encrypt(plain_img, base1, base2, skip, x0, mu) % plain_img: 输入灰度图像double或uint8均可 % base1, base2: Halton序列基数 % skip: 跳过前skip个Halton点 % x0, mu: Logistic混沌参数 [rows, cols] size(plain_img); N rows * cols; % 第一步Halton位置置乱 shuffled position_scramble(plain_img, base1, base2, skip, N); % 第二步混沌密钥流生成 key_stream generate_keystream(N, x0, mu); % 第三步异或扩散 enc_img xor_diffusion(shuffled, key_stream); key struct(base1, base1, base2, base2, skip, skip, x0, x0, mu, mu); end这只是主流程的骨架具体函数实现在下一节展开。先记住这个整体结构后面的代码全部按这个框架填充。3. Matlab关键代码实现3.1 生成Halton序列的高效写法前面给出的 halton_sequence 是最简单的循环版本适合理解原理。但实际处理图像时尺寸动辄几十万像素逐点循环会比较慢。我在项目中改用了一种基于“预计算位数权重”的生成方式速度提升明显function seq halton_fast(n, base) seq zeros(n, 1); max_power ceil(log(n) / log(base)); powers base .^ (0:max_power); weights 1 ./ (base .^ (1:max_power1)); for idx 1:n digits mod(floor(idx ./ powers), base); seq(idx) sum(digits .* weights); end end这里的关键优化点在于提前把 base 的幂次和权重计算好循环内部只做整除取余和点乘累加。对于 512×512 的图像生成 262144 个点这个版本比最初的逐次除法版本快了大约 5 倍。如果你安装了 Statistics and Machine Learning Toolbox还可以直接用 haltonset 函数p haltonset(2, Skip, 1000, Leap, 50); seq net(p, N);不过我在项目里通常不依赖工具箱原因有两个一是发布给别人运行时对方机器不一定装了完整工具箱二是自写版本方便调整 Skip 和 Leap 的语义后面做密钥扩展时会更灵活。3.2 位置扰乱环节的实现细节位置扰乱我采用的是“序列排序索引置乱法”。具体做法是先生成一个与像素总数等长的 Halton 序列然后用 sort 函数得到排序索引用这个索引去重排图像向量。因为 Halton 序列的数值各不相同排序索引天然就是 1 到 N 的一个无重复排列这就完美满足置乱需求。function shuffled position_scramble(img, base1, base2, skip, N) [rows, cols] size(img); vec img(:); % 生成一维Halton序列作为全局置乱索引 seq halton_fast(N skip, base1); seq seq(skip 1 : end); % 提取排序索引 [~, sort_idx] sort(seq); % 全局位置置乱 shuffled_vec vec(sort_idx); shuffled reshape(shuffled_vec, rows, cols); end这段代码的优点是实现简单一维序列就能完成全局置乱。缺点是排序时间随 N 增长512×512 的图像排序不到 0.1 秒整体可接受。有些资料里会把位置扰乱做成“行置乱 列置乱”两步也就是先生成一组行序列重排行再生成一组列序列重排列。这样的好处是计算复杂度更低因为只需要对 rows 和 cols 分别排序而不是对整个 N 排序。我把全局置乱和行列置乱都试过效果上全局置乱对相邻像素的破坏更彻底所以最终项目里用了全局置乱。3.3 像素扰乱环节的实现细节像素扰乱的核心思路是生成一组与像素等长的密钥流让每个明文像素和密钥流做异或同时加入“前后依赖”。先看密钥流生成部分我用 Logistic 映射作为混沌源function key_stream generate_keystream(N, x0, mu) key_stream zeros(N, 1); x x0; for i 1:N x mu * x * (1 - x); % 量化到0-255 key_stream(i) mod(floor(x * 100000), 256); end key_stream uint8(key_stream); end这里的核心参数是 mu。Logistic 映射在 mu 大于 3.57 之后进入混沌状态我一般取 3.999 左右这样迭代序列的周期非常长且不会收敛到固定点。x0 的取值范围是 (0,1)表现为敏感依赖初始值哪怕 x0 变化 0.0001生成的密钥流也完全不同。然后是异或扩散环节function enc_img xor_diffusion(shuffled, key_stream) [rows, cols] size(shuffled); vec shuffled(:); N length(vec); enc_vec zeros(N, 1, uint8); prev uint8(0); for i 1:N current bitxor(vec(i), key_stream(i)); current bitxor(current, prev); enc_vec(i) current; prev current; end enc_img reshape(enc_vec, rows, cols); end这一步里每个密文像素不仅取决于当前明文像素和密钥流还取决于前一个密文像素。它的作用是把“单点变化”扩散成“全局变化”这在图像加密里叫扩散性。攻击者如果试图从密文反推明文必须从第一个像素开始逐位逆推任何一个像素猜错后面全部连锁错误。解密流程就是加密流程的逆序。先对密文做逆扩散再按排序索引做逆置乱。逆扩散的代码逻辑是function dec_vec inv_xor_diffusion(vec) N length(vec); dec_vec zeros(N, 1, uint8); prev uint8(0); for i 1:N current bitxor(vec(i), prev); dec_vec(i) bitxor(current, key_stream(i)); prev vec(i); end end注意这里 prev 的更新逻辑和加密阶段不一样。加密时 prev 存的是加密后的当前值解密时 prev 存的是密文当前值这个细节写反会导致解密后的图像出现周期性错位我在调试时被这个坑绊了好几次。3.4 完整算法流程示例把上面的函数串起来一个完整的加密脚本如下% 参数设定 base1 2; base2 3; skip 1500; x0 0.618; mu 3.999; % 读取图像 plain_img imread(lena.png); if size(plain_img, 3) 3 plain_img rgb2gray(plain_img); end plain_img im2uint8(plain_img); % 加密 [enc_img, key] halton_image_encrypt(plain_img, base1, base2, skip, x0, mu); % 保存 imwrite(enc_img, encrypted.png); % 解密 dec_img halton_image_decrypt(enc_img, key); % 验证 mse_val mean((double(plain_img(:)) - double(dec_img(:))).^2); fprintf(MSE: %f\n, mse_val);运行之后 MSE 应该为 0说明解密无损加密和解密完全可逆。这里有一个容易忽略的点图像 I/O 的数据类型。imread 读进来是 uint8im2uint8 也是 uint8但如果你在中间步骤把它转成了 double再转回 uint8 时一定要用 im2uint8 而不是 uint8因为 uint8 是截断取整im2uint8 是四舍五入并自动缩放两种操作对图像灰度值的影响不一样。4. 安全性评估与实验结果4.1 加密前后视觉对比以经典的 Lena 灰度图为例原图是清晰的含有人脸轮廓的自然图像加密图像输出后呈现为杂乱无章的“雪花噪声图”。从视觉上可以直接观察到原图的轮廓、边缘、纹理细节完全消失图像看起来毫无结构特征。解密图像恢复后与原图逐像素一致肉眼无法分辨差异。这里要说明的是视觉对比只是第一步不能作为安全性的唯一依据。很多看起来很乱的加密图实际上可能残留了局部相关性或统计特征需要通过量化指标来衡量。4.2 量化指标计算量化评估我用四个主要指标信息熵、直方图分布、相邻像素相关性、密钥敏感性。信息熵衡量的是图像灰度分布的随机程度理想情况下加密图像的熵值越接近 8 越好8 位灰度图。计算公式如下function H img_entropy(img) counts imhist(img); p counts / sum(counts); p(p 0) []; H -sum(p .* log2(p)); end我用这套方案对 512×512 Lena 图多次测试加密图像的信息熵基本在 7.996 到 7.998 之间非常接近理想值 8说明像素灰度分布已经接近均匀随机。相邻像素相关性是图像加密必看的指标。明文图像中相邻像素高度相关灰度值基本延续相关性系数通常大于 0.9加密后应接近 0。我选取水平方向的 5000 对相邻像素用协方差矩阵求 Pearson 相关系数function r pixel_corr(img) img double(img); [rows, cols] size(img); pairs 5000; rng(42); indices randi(rows * cols - 1, pairs, 1); x zeros(pairs, 1); y zeros(pairs, 1); for i 1:pairs idx indices(i); r1 floor((idx - 1) / cols) 1; c1 mod(idx - 1, cols) 1; c2 mod(c1, cols) 1; x(i) img(r1, c1); y(i) img(r1, c2); end r corr(x, y); end实测中明文 Lena 的水平相邻像素相关系数接近 0.98加密后降到 0.01 以下说明相邻像素的空间冗余被有效去除。密钥敏感性测试我用的是 NPCR像素变化率和 UACI归一化平均变化强度。做法是拿两组只差一个比特的密钥分别加密同一张图然后比较两个密文的差异。NPCR 约为 99.6% 以上UACI 约为 33% 左右说明方案对密钥极其敏感单个比特的变化就会导致完全不同的密文。4.3 抗攻击性讨论从常见攻击方式角度分析这套方案唯密文攻击攻击者只有密文面对的是一张直方图均匀、像素相关性接近 0 的噪声图几乎没有可用的统计特征很难推测出密钥。已知明文攻击由于扩散环节的存在单个明文像素的变化会传导至后续所有密文像素攻击者即使拿到一组“明文-密文对”也难以推导出密钥流的全局规律。选择明文攻击这是更高级的攻击方式攻击者可以构造特殊明文去探测密钥。对此我的建议是不要使用固定的 Halton 序列基数组合而是在加密阶段引入一个随机扰动项或者用图像自身的哈希值参与密钥生成让每次加密的密钥流都不一样。当然这套方案并非无懈可击。Halton 序列本身是可预测的如果密钥中的 Skip 和基数选择范围过小攻击者可以通过穷举方式还原序列。所以我建议在实际使用中把基数选择范围扩展到更大的质数集合Skip 的随机范围也尽量加大同时配合 Logistic 混沌的初值敏感性整体安全性会显著提高。5. 常见问题与避坑指南5.1 Halton序列生成中的坑我在调试过程中最有发言权的几个坑都出在序列生成上在这里分享给你。第一个坑是小基数序列前部点分布的“聚集效应”。用基数 2 生成 Halton 序列时前几个点会非常靠近 0比如 0.5、0.25、0.75、0.125 这样分布前 10 个点左右在半开区间内并不均匀。如果直接拿这些点做排序索引图像前部的像素可能只会被轻微打乱。解决办法就是设置 Skip 参数跳过前 500 到 2000 个点让序列进入“稳定均匀状态”后再使用。第二个坑是浮点数精度导致的重复索引。Halton 序列理论上是无重复的但 double 类型精度有限基数较大时数值差异可能小到被浮点舍入吞掉sort 后可能出现索引重复或索引缺失。我的规避措施是使用 uint64 累加器构造序列或者生成序列后加一行去重检测if length(unique(seq)) ~ length(seq) error(Halton序列存在重复请调整Skip或基数); end第三个坑是基数和图像尺寸的“巧合周期”。如果图像尺寸恰好是某个基数的整数次幂比如 256 是 2 的 8 次方那么用基数 2 生成的 Halton 序列在排序后的索引会呈现某种可预测的模式。实际我在 256×256 图像上遇到过类似现象改用基数 3 和基数 5 的组合后问题消失。所以处理 2 的幂次尺寸图像时要特别注意基数的选择。5.2 明文攻击场景下的加固方法有朋友问我这套方案能不能直接用于真实系统。我的回答是作为教学和实验基线可以但生产环境建议做一些加固。最简单的加固方式是在加密前对明文做一次“预白化”也就是用一组固定的随机掩码对明文图像做一次异或。这样即使攻击者拿到了明密文对他也无法直接推断出真正用于加密的密钥流因为他缺失了掩码信息。另一种加固方式是把 Halton 序列的 Skip 参数与明文图像的哈希值绑定。做法是先计算明文的 SHA-256 哈希取前 16 位作为随机种子用这个种子在指定范围内生成 Skip 值。这样每个明文的加密参数都不同选择明文攻击的难度大幅提升。5.3 大图性能优化512×512 图像在这个方案里跑得很快但如果输入是 4096×4096 甚至更大的医学影像或遥感图像几个关键环节的性能就必须优化Halton 序列生成要改成向量化写法不能用逐点循环。可以预先计算好所有 n 的进制表示或者用 bit 反转技巧一次性生成整个序列。位置置乱的 sort 排序是 O(N log N) 复杂度大图时非常耗时。可以考虑把全局置乱改成“分行分列置乱”只对 rows 和 cols 分别排序。异或扩散环节存在严重的循环依赖这个无法完全并行化但可以把扩散拆成多个独立块每个块内部串行扩散、块与块之间并行处理。虽然会略微降低扩散强度但处理超大图像时效率提升非常明显。另外建议在读取大图时直接用 im2uint8 压缩到 8 位处理避免 double 类型导致的尺寸暴增。我在处理 16 位深度的 DICOM 医学图像时就碰到过内存不足的问题后来统一转成 8 位灰度就顺畅多了。再分享一个小经验解密代码一定要和加密代码放在同一个包里反复交叉验证。我遇到过加密端用 uint8、解密端误用了 double 导致解密后出现系统性灰度偏移的情况这种情况看 MSE 会发现不为 0图像看起来像蒙了一层雾排查起来特别费劲。后来我养成了习惯解密结果出来之后第一件事就打印 min、max、MSE三个数字一对比问题定位很快。这个基于 Halton 序列的图像加密方案整体思路清晰、代码简洁、可扩展性强。如果后续想继续深入可以考虑把 Halton 序列换成 Sobol 序列试试或者在像素扰乱阶段加入 AES 的 S 盒替代操作效果都会有新的变化。
返回列表