【AES加密专题】3.工具函数的编写(1)
目录
1.兼容 51
2.密钥长度单位的转换
3.定义数据块的大小
4.定义加密轮数
5.有限域
总结一下
拓展:为什么 8 位二进制00011011对应多项式x⁸ + x⁴ + x³ + x + 1?
第一步:二进制与多项式的基础对应
第二步:为什么会多一个x⁸项?
6.子密钥表
(1)static
(2)xdata
(3)unsigned char
(4)g_roundKeyTable
(5)[4*Nb*(Nr+1)]
总结
专题:
【AES加密专题】1.AES的原理详解和加密过程-CSDN博客
【AES加密专题】2.AES头文件详解-CSDN博客
1.兼容 51
/*
* #ifndef __C51__:
__C51__ 是 Keil C51 编译器(专门用于 8051 系列单片机的编译器)在编译时自动定义的宏,
用于标识当前处于 C51 编译环境。#ifndef __C51__ 的意思是:
如果当前不是 C51 编译环境(即未定义__C51__宏),则执行下面的代码。
*/
#ifndef __C51__//在普通 C 编译器(如 PC 端的 GCC、MSVC)中,这些关键字不存在。因此这里将它们定义为 “空宏”#define code#define data#define idata#define xdata#define pdata//在普通 C 环境中,没有专门的 “布尔类型”,因此用unsigned char(无符号字符型,1 字节)//来模拟布尔类型BOOL(通常 0 表示假,非 0 表示真)。typedef unsigned char BOOL;
#elsetypedef bit BOOL;
#endif
-
code
、data
、idata
、xdata
、pdata
是 C51 编译器特有的存储类型关键字(用于指定变量在单片机内存中的存储位置,如code
表示程序存储区 ROM,data
表示内部数据区 RAM 等)。在普通 C 编译器(如 PC 端的 GCC、MSVC)中,这些关键字不存在。因此这里将它们定义为 “空宏”,目的是:当代码中使用这些关键字时(如code unsigned char buf[] = "abc";
),在非 C51 环境下编译时会忽略这些关键字,避免编译报错。 -
typedef unsigned char BOOL;
:在普通 C 环境中,没有专门的 “布尔类型”,因此用unsigned char
(无符号字符型,1 字节)来模拟布尔类型BOOL
(通常 0 表示假,非 0 表示真)。
当处于 C51 编译环境时(定义了C51
),bit
是 C51 特有的位类型(仅占 1 个二进制位,取值 0 或 1),更适合表示布尔值(节省单片机有限的内存)。因此这里将BOOL
定义为bit
类型。
这段代码通过条件编译,让同一套代码既能在 8051 单片机的 C51 环境下编译(使用特有的bit
类型和存储关键字),也能在普通 C 环境下编译(忽略存储关键字,用unsigned char
模拟布尔类型),实现了代码的跨环境兼容性。
#define data、#define idata、#define xdata、#define pdata:这些也是宏定义,分别用于标识不同类型的数据存储:
-
data:表示将变量存储在片上 RAM 的直接寻址区,即 8051 的内部 RAM 的前 128 字节(地址范围为 0x00-0x7F)。
-
idata:表示将变量存储在片上 RAM 的间接寻址区,通常是 8051 的内部 RAM 的后 128 字节(地址范围为 0x80-0xFF)。
-
xdata:这个关键字用于指示变量存储在外部数据存储器(如外部 RAM)中。
-
pdata:表示变量存储在 8051 的片外 RAM 的分页区,即外部 RAM 的一个特定分页区域,通常是 256 字节的页内存。
2.密钥长度单位的转换
/*
* Nk表示AES密钥的长度(单位是字,4字节/字)
* 假设AES_KEY_LENGTH 为128,192或256,则Nk分别为4,6,8
*/
#define Nk (AES_KEY_LENGTH /32)
3.定义数据块的大小
/*
*Nb表示数据块的大小(单位是字),固定为4.这是AES的固定标准,AES处理的是4X4的字节块。
*/
#define Nb 4
4.定义加密轮数
// Nr 表示AES的轮数,取决于密钥长度。通过#if 语句确定 AES_KEY_LENGTH 值为128、192或256时的轮数。
#if AES_KEY_LENGTH == AES_KEY_LENGTH_128#define Nr 10
#elif AES_KEY_LENGTH == AES_KEY_LENGTH_192#define Nr 12
#elif AES_KEY_LENGTH == AES_KEY_LENGTH_256#define Nr 14
#else#error AES_KEY_LENGTH must be 128, 192 or 256 BOOLS!
#endif
Nr 不适合直接使用枚举确定的原因主要在于 AES 算法的实现要求 Nr 的值依赖于编译时的宏定义AES_KEY_LENGTH,这使得轮数值在编译时就必须根据密钥长度来确定。
5.有限域
/*
* BPOLY 用于AES中有限域(GF(2^8))运算。其值0x1B 表示GF(2^8)中的不可约多项式
*(x^8+X^4+X^3+X+1)的低8位。
在AES的 MixColumns 操作中,有限域多项式 BPOLY 被用作乘法的模,确保运算在8位内循环。
*/
#define BPOLY 0x1B
我们知道 AES 是一种加密算法,核心是对数据(比如一段文字、一张图片)做各种复杂的数学变换,让明文变成乱码(密文)。这些变换里,有一步很关键:对 8 位二进制数(比如10110011
这种 8 位数字)做 “乘法”。
但这里的 “乘法” 不是我们平时学的整数乘法,而是一种特殊的 “有限域乘法”。你可以理解为:这些 8 位数字生活在一个 “有限的小圈子” 里(专业名叫GF(2^8)
),圈子里的规则是 “任何运算结果都不能超过 8 位”—— 就像钟表上的数字永远在 1-12 之间,超过了就 “绕回去”。
但是问题来了:乘法很容易 “出圈”
比如两个 8 位数字相乘,结果可能变成 9 位、10 位(就像 12 点的钟表上,11 点加 2 点不能是 13 点,得绕回 1 点)。这时候就需要一个 “规矩” 来把超范围的结果 “拉回” 8 位里。
这个 “规矩” 就是不可约多项式,而0x1B
就是 AES 里规定的这个 “规矩”。
那0x1B
具体是个什么 “规矩”?
0x1B
是十六进制,转成二进制是 0001 1011,但因为是 8 位的 “圈子”,完整的规矩其实是一个多项式:x⁸ + x⁴ + x³ + x + 1
(可以理解为一种 “减法公式”)。
当两个 8 位数字相乘结果超过 8 位时,就用这个多项式 “减一减”(专业叫 “取模”),直到结果变回 8 位。比如:
-
假设相乘后得到一个 9 位的数,用
0x1B
对应的规则一处理,就会变成 8 位,刚好留在 “圈子” 里。
为什么非得是0x1B
,而不是其他数?
主要有三个原因:
-
大家得统一规矩加密和解密必须用同一个 “规矩”,否则解密时就认不出密文了。AES 是国际标准,必须规定一个唯一的 “规矩” 让全世界遵守,
0x1B
就是被选中的那个。 -
计算起来方便
0x1B
对应的多项式x⁸ + x⁴ + x³ + x + 1
,在硬件(比如芯片)和软件里实现起来特别简单。就像选工具时,肯定选顺手的那一个,0x1B
就是那个 “顺手的工具”。 -
安全性经过验证密码算法的核心是 “难破解”。
0x1B
经过大量测试,用它做出来的 AES 加密,破解难度非常高。如果换一个多项式,可能会出现漏洞,让加密变脆弱。
总结一下
0x1B
就像 AES 里的一把 “尺子”:
-
作用是保证 8 位数字相乘后不会 “出圈”,始终在 8 位范围内运算;
-
选它是因为全世界统一用(标准)、算起来方便(效率高)、加密够安全(难破解)。
如果没有这个 “尺子”,AES 的运算就会乱套,加密解密也无法统一,更谈不上安全了。
拓展:为什么 8 位二进制00011011
对应多项式x⁸ + x⁴ + x³ + x + 1
?
第一步:二进制与多项式的基础对应
在有限域 GF (2⁸) 中,一个 8 位二进制数可以对应一个 “8 次以内的多项式”,规则是:
-
二进制的每一位(从右到左,即最低位到最高位)对应多项式中
x⁰
(x 的 0 次方,即 1)、x¹
、x²
……x⁷
的系数。 -
某一位是 “1”,就表示多项式包含这一项;是 “0” 就不包含。
比如00011011
(8 位)的多项式应该是x⁴ + x³ + x + 1
第二步:为什么会多一个x⁸
项?
因为0x1B
的作用是 “处理乘法溢出”—— 当两个 8 位二进制数相乘时,结果可能超过 8 位(比如变成 9 位),这时候需要用 “模运算” 把结果 “砍回” 8 位。
模运算的本质是:如果结果超过 8 位(即出现了x⁸
项,因为 8 位二进制的最高位是x⁷
),就用x⁸ + x⁴ + x³ + x + 1
这个多项式来 “替换”x⁸
项。
具体来说:假设乘法结果里有x⁸
,根据多项式规则,x⁸ = x⁴ + x³ + x + 1
(因为x⁸ + x⁴ + x³ + x + 1 = 0
→ 移项得x⁸ = x⁴ + x³ + x + 1
)。这样一来,x⁸
就被换成了低次项(最高x⁴
),结果自然就变回 8 位以内了。
所以总结:0x1B
的 8 位二进制是00011011
,它直接对应x⁴ + x³ + x + 1
;但因为它的作用是处理 “超过 8 位的x⁸
项”,所以完整的不可约多项式要加上x⁸
,即x⁸ + x⁴ + x³ + x + 1
。
6.子密钥表
// AES子密钥表,当密钥长度为128位时,占用176字节空间
static xdata unsigned char g_roundKeyTable[4*Nb*(Nr+1)];
AES 加密的核心过程是对数据进行多轮(“Round”)变换,每一轮都需要一个特定的 “子密钥”(轮密钥)来参与运算。这条语句就是定义一个数组,专门用来存放所有轮的子密钥,相当于一个 “密钥表”。
(1)static
表示这个数组是 “静态变量”,作用域仅限当前 C 文件(不能被其他文件的代码直接访问),避免了变量名冲突,也让代码结构更清晰。保证被多次调用之间维持其值,而不是每次函数调用时重新初始化,对对于需要多个函数调用之间维持状态的变量是有用的。
(2)xdata
这是 8051 单片机(C51 编译器)特有的关键字,指定数组的存储位置 ——外部数据存储器(片外 RAM)。AES 的轮密钥表需要较大的空间(比如 128 位密钥时要存 176 字节),而 8051 单片机的内部 RAM(data
/idata
)通常很小(比如只有 128 字节),放不下,所以用xdata
放到外部 RAM 中。
(3)unsigned char
数组的元素类型是 “无符号字符型”,每个元素占 1 字节(8 位)。AES 的子密钥本质上是 8 位的字节数据,用unsigned char
刚好能存储,且符合加密中对字节级操作的需求。
(4)g_roundKeyTable
数组名,字面意思是 “轮密钥表”(g_
通常表示全局变量,roundKey
指轮密钥,Table
表示表),直观体现了数组的用途 —— 存放所有轮的子密钥。
(5)[4*Nb*(Nr+1)]
数组的大小,这是最关键的部分,和 AES 的算法参数直接相关:
-
Nb
:AES 中 “数据块大小” 的参数(以 “32 位字” 为单位)。AES 的明文 / 密文数据块固定是 128 位(16 字节),128 位 = 4 个 32 位字(32*4=128),所以Nb=4
。 -
Nr
:AES 的 “轮数”,由密钥长度决定:-
128 位密钥 →
Nr=10
轮; -
192 位密钥 →
Nr=12
轮; -
256 位密钥 →
Nr=14
轮。
-
-
计算逻辑:每一轮需要
4*Nb
个字节的子密钥(因为每轮操作对应一个 4×4 的字节矩阵,刚好4*Nb=16
字节),而 AES 的轮数是Nr
,但初始化时还需要额外 1 轮的密钥(第 0 轮),所以总轮数是Nr+1
。 -
例如 128 位密钥时:
4*Nb*(Nr+1) = 4*4*(10+1) = 176
字节,和注释 “占用 176 字节空间” 完全对应。
总结
这条代码定义了一个 “轮密钥表数组”,专门用来存放 AES 加密中所有轮(包括初始轮)的子密钥。通过xdata
指定存储在外部 RAM 以节省内部空间,大小由 AES 的块大小(Nb
)和轮数(Nr
)动态确定,确保能适配不同密钥长度(128/192/256 位)的需求。