【AES加密专题】3.工具函数的编写(1)

目录

[1.兼容 51](#1.兼容 51)

2.密钥长度单位的转换

3.定义数据块的大小

4.定义加密轮数

5.有限域

总结一下

[拓展:为什么 8 位二进制00011011对应多项式x⁸ + x⁴ + x³ + x + 1?](#拓展:为什么 8 位二进制00011011对应多项式x⁸ + x⁴ + x³ + x + 1?)

第一步:二进制与多项式的基础对应

第二步:为什么会多一个x⁸项?

6.子密钥表

(1)static

(2)xdata

[(3)unsigned char](#(3)unsigned char)

(4)g_roundKeyTable

(5)[4*Nb*(Nr+1)]

总结


AES加密专题:

【AES加密专题】1.AES的原理详解和加密过程-CSDN博客

【AES加密专题】2.AES头文件详解-CSDN博客

【AES加密专题】3.工具函数的编写(1)-CSDN博客

【AES加密专题】4.Sbox的解析和生成-CSDN博客

【AES加密专题】5.功能函数的编写(2)-CSDN博客

【AES加密专题】6.功能函数的编写(3)-CSDN博客

【AES加密专题】7.AES全局函数的编写-CSDN博客

【AES加密专题】8.实战-测试加密网站和代码-CSDN博客

1.兼容 51

cpp 复制代码
/*
* #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;
#else
    typedef bit BOOL;
#endif
  • codedataidataxdatapdata 是 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.密钥长度单位的转换

cpp 复制代码
/*
* Nk表示AES密钥的长度(单位是字,4字节/字)
* 假设AES_KEY_LENGTH 为128,192或256,则Nk分别为4,6,8
*/
#define Nk (AES_KEY_LENGTH /32)

3.定义数据块的大小

cpp 复制代码
/*
*Nb表示数据块的大小(单位是字),固定为4.这是AES的固定标准,AES处理的是4X4的字节块。
*/
#define Nb  4

4.定义加密轮数

cpp 复制代码
// 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.有限域

cpp 复制代码
/*
* 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 ,而不是其他数?

主要有三个原因:

  1. 大家得统一规矩 加密和解密必须用同一个 "规矩",否则解密时就认不出密文了。AES 是国际标准,必须规定一个唯一的 "规矩" 让全世界遵守,0x1B就是被选中的那个。

  2. 计算起来方便 0x1B对应的多项式x⁸ + x⁴ + x³ + x + 1,在硬件(比如芯片)和软件里实现起来特别简单。就像选工具时,肯定选顺手的那一个,0x1B就是那个 "顺手的工具"。

  3. 安全性经过验证 密码算法的核心是 "难破解"。0x1B经过大量测试,用它做出来的 AES 加密,破解难度非常高。如果换一个多项式,可能会出现漏洞,让加密变脆弱。

总结一下

0x1B就像 AES 里的一把 "尺子":

  • 作用是保证 8 位数字相乘后不会 "出圈",始终在 8 位范围内运算;

  • 选它是因为全世界统一用(标准)、算起来方便(效率高)、加密够安全(难破解)。

如果没有这个 "尺子",AES 的运算就会乱套,加密解密也无法统一,更谈不上安全了。

拓展:为什么 8 位二进制00011011对应多项式x⁸ + x⁴ + x³ + x + 1

第一步:二进制与多项式的基础对应

在有限域 GF (2⁸) 中,一个 8 位二进制数可以对应一个 "8 次以内的多项式",规则是:

  • 二进制的每一位(从右到左,即最低位到最高位)对应多项式中x⁰(x 的 0 次方,即 1)、......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.子密钥表

cpp 复制代码
// 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 位)的需求。

相关推荐
嵌入式知行合一2 小时前
时间管理方法论
笔记
儒雅的晴天2 小时前
git笔记
笔记·git
半夏知半秋3 小时前
kcp学习-通用的kcp lua绑定
服务器·开发语言·笔记·后端·学习
中屹指纹浏览器4 小时前
指纹浏览器底层沙箱隔离技术实现原理与架构优化
经验分享·笔记
小裕哥略帅5 小时前
PMP知识--五大过程组
笔记·学习
Aliex_git5 小时前
提示词工程学习笔记
人工智能·笔记·学习
0和1的舞者5 小时前
力扣hot100-链表专题-刷题笔记(二)
笔记·算法·leetcode·链表·职场和发展
航Hang*5 小时前
Photoshop 图形与图像处理技术——第9章:实践训练6——滤镜特效
图像处理·笔记·学习·ui·photoshop
IMPYLH6 小时前
Lua 的 String(字符串) 模块
开发语言·笔记·单元测试·lua