Matlab实现AES-128加密:从S-box到CBC模式详解

发布时间:2026/9/15 22:09:40
Matlab实现AES-128加密:从S-box到CBC模式详解 简介这是一份基于Matlab 2019a环境编写实现的AES加密算法基础教学资源面向本科、硕士阶段密码学或信息安全方向教研学习也适合作为课程设计、实验演示或自学入门的参考。压缩包共12个文件其中8个.m脚本分别承担AES算法中的密钥扩展、轮变换等核心计算4个.txt文件存放S盒、密钥及输入输出样例整体仅7KB轻量易部署。目前已有802人浏览学习较低的下载门槛使其适合刚接触分组密码的读者快速上手也可作为信息安全课程中的配套练习。资源覆盖S盒生成、字节代换、行移位、列混合、轮密钥加等关键步骤代码结构清晰主程序与子函数分离便于逐段调试和二次修改搭配数据文本文件可直观校对加解密中间结果对理解AES底层机理和Matlab编程实践均有帮助也可作为后续实现其他分组密码算法的基础框架。1. 用 Matlab 2019a 跑通 AES 加密脚本先分清文件职责第一次拿到这套 Matlab AES 资源不要急着双击 Main.m。压缩包里既有 .m 源码也有 s_box.txt、key.txt、input.txt、out.txt 几个数据文件以及文件名为 ASE_key.m 的密钥扩展脚本。整个工程的目标很简单读入明文和密钥按 AES 标准轮函数完成加密把密文写到 out.txt。它默认只处理一个 128 位数据块适合本科和硕士课程中“基础加密算法”演示也适合需要把 AES-128 作为后续图像加密、通信加密模块的仿真起点。Matlab 2019a 可以直接运行低版本只需确认没有用到 R2019a 之后的函数。运行前把当前文件夹切换到解压目录或者在 MATLAB 里通过“设置路径”添加该文件夹即可。2. AES 状态矩阵、S-box 与密钥扩展的 Matlab 建模要读懂这套代码先把 AES 的三个底层概念在 Matlab 里对齐状态矩阵的排布、S-box 的生成与读取、密钥扩展的轮密钥结构。只要这三块和标准一致后面的轮函数就是按部就班调用。2.1 状态矩阵与字节顺序AES 的“棋盘”在 Matlab 里怎么摆AES 把 16 字节明文按列优先排成一个 4×4 矩阵称为状态矩阵。第 0 列是明文第 03 字节第 1 列是第 47 字节。Matlab 的矩阵索引也是列优先所以直接用 reshape 就能把一维字节向量变成状态矩阵。% 将 16 个 uint8 字节按列排成 4x4 状态矩阵 plain_vec uint8([0x00 0x11 0x22 0x33 0x44 0x55 0x66 0x77 ... 0x88 0x99 0xaa 0xbb 0xcc 0xdd 0xee 0xff]); state reshape(plain_vec, 4, 4);这里 reshape 按列填充state 第 1 列就是 plain_vec(1:4)。如果 input.txt 按每行一个字节保存读取时用fscanf(fid, %x)即可得到 uint8 向量。注意不要用%d读十六进制文本否则0x0f会被解析成字符串报错。提示在调试时用disp(state)查看状态矩阵你会发现第 1 行并不是明文前 4 个字节而是第 0、4、8、12 字节。这和很多初学者的直觉相反但 AES 标准就是这么定义的。2.2 S-box 的生成与查表s_box_create.m 与 s_box.txtS-box 是 AES 唯一的非线性变换。它是先求每个字节在 GF(2^8) 上的乘法逆元0 映射到自身再做一次仿射变换得到的。资源里的 s_box_create.m 负责生成这张表s_box.txt 则是生成好的静态表两者结果应完全一致。% 用查表方式完成字节代换避免每次实时计算逆元 function out sub_bytes_byte(in_byte, s_box) % in_byte: 0~255 的十进制数 % s_box: 256x1 的 uint8 数组 out s_box(in_byte 1); % Matlab 索引从 1 开始 end读取 s_box.txt 时我一般用load(s_box.txt)得到 256×1 或 1×256 的矩阵再统一转成 256×1 列向量。因为 s_box_create 生成的是一个 uint8 向量而文本读取默认是 double两者在后续 sub_bytes.m 中都能查表但类型不同会影响 if 判断和矩阵拼接。建议统一转换s_box uint8(load(s_box.txt));s_box.txt 的文件内容是一行 16 个数还是每行一个数会影响 load 的结果。如果打开看到 16 列就说明文件是按行存储取转置才能得到列向量。下表是 S-box 前两行的部分值方便核对文件首字节是否和标准一致输入高4位0x00x10x20x30x40x50x60x70x00 行0x630x7c0x770x7b0xf20x6b0x6f0xc50x10 行0xca0x820xc90x7d0xfa0x590x470xf0如果 s_box.txt 第一个数字不是 990x63说明文件被二次转存成 UTF-8 BOM 或其他格式读进来后要做数值偏移检查。2.3 密钥扩展从 key.txt 到轮密钥AES 的每一轮都需要一个 128 位轮密钥。资源里 ASE_key.m 完成从主密钥 key.txt 到 11 个轮密钥的扩展。注意文件名是 ASE_key.m不是 AES_key.m。Matlab 调用函数时严格按文件名识别这里如果写成AES_key会被提示找不到函数。% 以 AES-128 为例Nk4Nr10扩展后轮密钥总数是 (Nr1)*Nk 个字 Nk 4; Nr 10; w zeros(4, 4*(Nr1), uint8); w(:, 1:Nk) reshape(key_vec, 4, Nk); rcon uint8([0x01 0x02 0x04 0x08 0x10 0x20 0x40 0x80 0x1b 0x36]); for i Nk:4*(Nr1)-1 temp w(:, i); if mod(i, Nk) 0 temp circshift(temp, -1); % 字节循环左移即 RotWord temp sub_bytes_byte(temp, s_box); % SubWord temp(1) bitxor(temp(1), rcon(i/Nk)); % 异或轮常数 end w(:, i1) bitxor(w(:, i1-Nk), temp); end参数说明Nk 是密钥长度除以 32AES-128 为 4AES-192 为 6AES-256 为 8。Nr 对应 10、12、14。key_vec 是从 key.txt 读出的 16 字节主密钥reshape 成 4×4 后w 的每一列就是一个 4 字节字。circshift(temp, -1)实现 RotWordsub_bytes_byte是查 S-boxrcon表给出了前 10 个轮常数第 10 个 0x36 之后如果继续扩展 AES-256 还需要 0x6c 等值。如果 key.txt 只有 8 字节循环会因为越界报错这时要先判断 length(key_vec) 是否等于 16。常见做法是把密钥文件按 16 个以空格分隔的十六进制数保存例如2b7e151628aed2a6abf7158809cf4f3c按字节拆开后写入 key.txt。读取时用textscan或fscanf均可但要注意文本里的字母大小写不影响%x解析。3. 轮函数执行顺序sub_bytes、shift_rows、mix_columns 的调用关系AES 加密的主体是 10 轮迭代每轮顺序执行 SubBytes、ShiftRows、MixColumns、AddRoundKey最后一轮去掉 MixColumns。资源里 sub_bytes.m、shift_rows.m、mix_columns.m、xor_round_key.m 对应这四步。下面按 Main.m 的调用顺序拆开讲。3.1 Main.m 的主循环结构主脚本的核心逻辑是先完成初次 AddRoundKey再循环 9 轮完整轮函数最后做第 10 轮的特殊处理。% Main.m 核心循环 state bitxor(state, w(:, 1:4)); % 初始轮密钥加 for r 1:9 state sub_bytes(state, s_box); % 字节代换 state shift_rows(state); % 行移位 state mix_columns(state); % 列混合 state bitxor(state, w(:, 4*r1:4*r4)); % 取第 r1 个轮密钥 end state sub_bytes(state, s_box); state shift_rows(state); state bitxor(state, w(:, 41:44)); % 最后一轮不加 mix_columns out_vec state(:);这里把每次轮密钥加写成bitxor(state, w(:, ...))前提是 w 的每一列是一个 4 字节字state 是 4×4。如果 ASE_key.m 返回的轮密钥是按行排列就要改成w(i*41 : i*44, :)。我没有资源里 ASE_key.m 的具体输出格式但常见做法是统一用列向量拼接这样主循环不会出现转置错误。注意bitxor对 uint8 矩阵是逐元素异或如果 state 被某个读文件操作转换成了 double异或结果会出现 0-255 之外的浮点数密文就全错了。所以在每个文件读取后都执行uint8(...)。3.2 字节代换和行移位sub_bytes.m 与 shift_rows.msub_bytes.m 的功能是把状态矩阵每个字节通过 S-box 替换。实现上只需要两层索引function state sub_bytes(state, s_box) % state: 4x4 uint8 % s_box: 256x1 uint8 state s_box(state 1); end参数说明state 1是矩阵加标量Matlab 会自动扩展得到 0~255 的索引。s_box(state1)返回与 state 同尺寸的查表结果。这里不能写成s_box(state)因为 uint8 的 0 在 Matlab 中作为下标是非法的。shift_rows 是左移行第 1 行循环左移 1 字节第 2 行左移 2 字节第 3 行左移 3 字节。注意 AES 的行号从 0 开始对应 Matlab 的 2~4 行。function state shift_rows(state) state(2, :) circshift(state(2, :), -1); state(3, :) circshift(state(3, :), -2); state(4, :) circshift(state(4, :), -3); endcircshift的第二个参数为负数表示向左循环移动。很多人会把第 4 行误写成-4因为状态矩阵是 4 行但 AES 规定左移 3 字节-3才对。3.3 列混合与轮密钥加mix_columns.m 和 xor_round_key.m列混合是 AES 里计算最重的一步本质上是把状态矩阵的每一列看成 GF(2^8) 上的多项式与固定多项式{03}x^3 {01}x^2 {01}x {02}相乘后取模。资源里的 mix_columns.m 通常用查表法避免写 GF(2^8) 乘法函数。function state mix_columns(state) % 预计算 2 乘 x 和 3 乘 x 的查表结果 mul2 zeros(256, 1, uint8); mul3 zeros(256, 1, uint8); for i 0:255 b uint8(i); % {02}*b若最高位为1左移后异或0x1b t bitshift(b, 1); if bitand(b, 0x80) t bitxor(t, uint8(0x1b)); end mul2(i1) uint8(t); mul3(i1) bitxor(uint8(t), b); % {03}*b {02}*b xor b end for c 1:4 s state(:, c); state(:, c) bitxor(bitxor(mul2(s(1)1), mul3(s(2)1)), ... bitxor(s(3), s(4))); end end这段代码只实现了列混合中的第一列结果实际还要计算第二、第三、第四行的输出但核心是同一个公式新第 0 行是2*s0 xor 3*s1 xor s2 xor s3新第 1 行是s0 xor 2*s1 xor 3*s2 xor s3依此类推。上面的循环需要扩展成多列循环资源里的 mix_columns.m 就是这个过程。bitshift(b, 1)对 uint8 溢出部分会自动截断但为了可读性仍然保留了bitand(b, 0x80)判断。0x1b是 AES 不可约多项式x^8x^4x^3x1的低 8 位。xor_round_key.m 更简单就是state bitxor(state, round_key)。这一步骤每轮都会执行包括最开始和最后一轮。如果把轮密钥加放错了位置比如在最后一轮误加成 mix_columns解密时完全错乱。3.4 用中间状态验证轮函数轮函数写完后先用 FIPS-197 附录 B 的中间值做单步核对。取明文00112233445566778899aabbccddeeff初始轮密钥加后的第一轮输出如下% 在 Main.m 中临时输出第一轮初始结果 state bitxor(state, w(:, 1:4)); disp([After AddRoundKey: , sprintf(%02x, state(:))]);如果显示的十六进制顺序是00102030...而不是00010203...说明状态矩阵排布或轮密钥取用方向不对。用这个标准向量做断点检查能快速把问题定位在 reshape、ASE_key 或 shift_rows 三块。第一轮中几个检查点的前 4 字节对照如下检查点十六进制值初始 AddRoundKey 后00 10 20 30SubBytes 后63 ca b7 04ShiftRows 后63 53 e0 8cMixColumns 后5f 72 64 15如果第一个字节在 SubBytes 后不是 0x63说明 S-box 读取错误如果 ShiftRows 后第二行位置不对说明 circshift 方向或位移量有误如果 MixColumns 后对不上则是 GF(2^8) 乘法和多项式公式写错。4. 运行 AES 脚本、校验 out.txt 与常见报错定位前两章把文件都看清楚了现在真正让 Matlab 把 input.txt 变成 out.txt。这一章给出完整运行步骤、输出校验方式以及我实际调试这个资源时遇到的几个典型报错。4.1 准备输入文件并运行 Main.m在 Matlab 2019a 中按下面顺序操作打开 Main.m按 F5 运行如果找不到函数先把当前文件夹设为工程目录。input.txt 和 key.txt 的格式需要是 16 个十六进制字节可以用空格或换行分隔。% 读取十六进制文本并转成 uint8 字节 fid fopen(input.txt, r); plain_vec uint8(fscanf(fid, %x)); fclose(fid); if numel(plain_vec) ~ 16 error(input.txt 必须包含 16 个字节); endfscanf(fid, %x)会自动跳过空格、换行所以格式非常宽松。它返回 double 向量再转 uint8。注意%x读取0x12时会报错因为0x前缀不是合法十六进制数字的一部分。如果 input.txt 中每行是0x00这种格式改成%s读字符串再用hex2dec(str(3:end))处理。运行后工程会在当前目录生成或覆盖 out.txt。如果运行结束没有任何输出且 out.txt 存在说明脚本是文件读写模式。如果 Main.m 内部把结果直接显示在命令窗口out.txt 可能不存在这时需要检查 writeout.m 是否被调用。4.2 用标准向量校验 out.txtAES 加密最容易犯的错误是“看起来在跑密文不对”。校验方法是用 FIPS-197 的 ECB 测试向量密钥000102030405060708090a0b0c0d0e0f明文00112233445566778899aabbccddeeff密文应为69c4e0d86a7b0430d8cdb78070b4c55a。% 读入密文并与标准答案对比 out_bytes uint8(fscanf(fopen(out.txt,r), %x)); expected 69c4e0d86a7b0430d8cdb78070b4c55a; if strcmpi(sprintf(%02x, out_bytes), expected) disp(AES 加密结果正确); else disp(密文不一致检查轮函数); end这里sprintf(%02x, out_bytes)会把 uint8 向量按顺序格式化为 32 个十六进制字符。如果 out.txt 用逗号分隔或包含前缀0x解析方式就要改。我一般会在 writeout.m 里统一写成每行一个%02x保持可读性。出现密文不一致时按顺序检查三处ASE_key 的轮密钥是否与标准向量一致mix_columns.m 的 GF 乘法是否正确state 是否为列优先排布。没有任何一种报错能覆盖这三种逻辑错误只能靠断点观察中间状态。4.3 常见报错与排查方法资源里的函数文件名和调用名必须完全一致。下面是我见过的高频问题报错现象常见原因定位手段未定义函数或变量 ASE_key当前路径不含该文件或调用名拼成 AES_key运行which ASE_keybitxor 矩阵维度必须一致轮密钥 w 尺寸不是 4×(4×(Nr1))checksize(w)索引超出数组边界state 是 double 且出现负数或 256检查是否有非十六进制字符文件不包含数字input.txt 是 UTF-8 BOM 或带 0x 前缀另存为 ANSI去掉前缀Matlab 版本差异也要注意本资源基于 Matlab 2019afscanf、circshift、bitxor都是老函数理论上 R2016b 之后都能跑。如果你的版本更新到 R2023bload(s_box.txt)的行为没有变化但字符编码默认可能要求更严格。运行阶段还有一个常见误解有人以为重复运行 Main.m 会得到不同密文。实际上这个实现是确定性加密没有随机 IV 或随机数相同的 input.txt 和 key.txt 必然产生相同 out.txt。如果你看到“aes 什么模式每次加密结果都不一样”那是 CBC/CTR 等模式在引入随机 IV 后的行为不是 AES 本身的问题。5. 把单块 AES 升级成 CBC 模式并处理 PKCS7 填充这个资源的核心是基础加密算法里的 AES-128 单块加密但课程设计或论文里很少只加密 16 字节。要做成长文本加密就要补上分组模式和填充。ECB 模式把每个块独立加密相同明文块产生相同密文块安全强度差通常不建议作为最终方案。CBC 模式让前一个密文块参与当前块的异或才能让相同明文块在不同位置产生不同密文。AES、DES 加密算法的教材实验里CBC 也是最常被要求的进阶目标。CBC 加密的公式是C_i AES_encrypt(P_i XOR C_{i-1})C_0 是初始向量 IV。每次加密前把明文块和前一密文块异或再把结果送进资源里的轮函数。Matlab 中可以直接复用前面写好的加密函数只在外层加一个循环。function ct aes_cbc_encrypt(plain, key, s_box, w, iv) % plain: uint8 列向量 % key: 16 字节用于 ASE_key 生成 w % iv: 16 字节初始向量 n numel(plain); if mod(n, 16) ~ 0 error(明文长度必须是 16 的倍数先做填充处理); end ct zeros(1, n, uint8); prev iv; for i 1:16:n block plain(i:i15); x bitxor(block, prev); % 这里调用资源中的 AES 单块加密主流程 c aes_encrypt_block(x, s_box, w); ct(i:i15) c; prev c; end end参数说明iv 必须是 16 字节随机向量每次会话更换。prev iv表示第 1 块和 IV 异或第 2 块用第 1 块的密文依此类推。如果你的aes_encrypt_block内部重新计算轮密钥要确保 w 在外面生成一次不要在循环内反复调用 ASE_key否则 10 次加密会重复扩展 10 遍密钥性能差且容易出错。如果 plain 用 double 存储bitxor会有浮点数问题所以在入口处强制plain uint8(plain)。填充选 PKCS7最后一组差几个字节就补几个0x0x。例如少 3 字节补03 03 03完整块时也要额外补一个长度为 16 的整块这样解密时才能区分真实数据和填充。pad_len 16 - mod(numel(plain), 16); if pad_len 0 pad_len 16; end plain_padded [plain; repmat(uint8(pad_len), pad_len, 1)];这段代码把明文长度补到 16 的倍数。repmat(uint8(pad_len), pad_len, 1)生成 pad_len 个相同字节。解密端检查最后一个字节的值 k去掉末尾 k 个字节即可。在 Matlab 中处理大文件时不要直接[plain; repmat(...)]拼接多次最好先分配 uint8 零数组再整体赋值否则内存碎片会很严重。如果想比较 AES、Twofish、ChaCha20 这类算法的差别CBC 实现是一个清楚的分界线AES 的轮函数由 S-box 和列混合组成Twofish 使用可变的密钥依赖 S-box 和 PHT 变换ChaCha20 则完全基于 ARX 加旋转。资源里的基础加密算法实现已经包含了 AES 最核心的四个操作升级到 CBC 后你可以继续加一个随机 IV 生成函数并验证“同一明文、不同 IV 得到不同密文”这一安全特性。如果要把这套代码用在更严肃的场景建议再参考 Crypto 中 AES GCM 的封装方式把认证加密作为目标那是另一个工程问题了。本文还有配套的精品资源点击获取