加密算法
参考链接
[原创]密码学基础:AES加密算法-密码应用-看雪-安全社区|安全招聘|kanxue.com
前置知识
有限域(伽罗瓦域)
有限域有时也称伽罗瓦域,它指的是由有限个元素组成的集合,在这个集合内可以执行加、减、乘和逆运算。而在密码编码学中,我们只研究拥有有限个元素的域,也就是有限域。域中包含元素的个数称为域的==阶==。只有当m是一个素数幂时,即$m=p^n$(其中n为正整数,p为素数),阶为m的域才存在。p称为这个有限域的特征。
也就是说,有限域中元素的个数可以是11(p=11是一个素数,n=1)、可以是81(p=3是一个素数,n=4)、也可以是256(p=2是一个素数,n=8)…但有限域的中不可能拥有12个元素,因为12=2·2·3,因此12也不是一个素数幂。
有限域中最直观的例子就是阶为素数的域,即n=1的域。域GF(p)的元素可以用整数0、1、…、p-1来表示。域的两种操作就是:
- 模整数加法
- 整数乘法模p。
由于p是一个素数,整数环Z(Z表示为GF(p))也成为拥有素数个元素的素数域或者伽罗瓦域。GF(p)中所有的非零元素都存在逆元,GF(p)内所有的运算都是模p实现的。
素数域内的算数运算规则如下:
- 加法和乘法都是通过模p实现的
- 任何一个元素a的加法逆元都是由$(a+a的逆元) \quad mod\quad p=0$得到的
- 任何一个非零元素a的乘法逆元定义为$a·a的逆元 \quad mod \quad p=1$
[!tip]
举个例子,在素域GF(5)={0、1、2、3、4}中,2的加法逆元为3,这是因为2+(3)=5,5mod5=0,所以2+3=5mod5=0。2的乘法逆元为3,这是因为2·3=6,6mod5=1,所以2·3=6mod5=1。(在很多地方a的加法逆元用-a表示,a的乘法逆元用1/a表示)
[!note]
注:GF(2)是一个非常重要的素域,也是存在的最小的有限域,由于GF(2)的加法,即模2加法与异或(XOR)门等价,GF(2)的乘法与逻辑与(AND)门等价,所以GF(2)对AES非常重要。
如果有限域的阶不是素数,则这样的有限域内的加法和乘法运算就不能用模整数加法和整数乘法模p表示。而且m>1的域被称为扩展域,为了处理扩展域,我们就要使用不同的符号表示扩展域内的元素,使用不同的规则执行扩展域内元素的算术运算。
在扩展域$GF(2^m)$中,元素并不是用整数表示的,而是用系数为域GF(2)中元素的多项式表示。这个多项式最大的度(幂)为m-1,所以每个元素共有m个系数,在AES算法使用的域$GF(2^8)$中,每个元素$A∈GF(2^8)$都可以表示为:
$$
A(x)=a_7x^7+a_6x^6+\ldots+a_1x+a_0\quad,a_i\in GF(2)=0,1
$$
[!note]
注意:在域$GF(2^8)$中这样的多项式共有256个,这256个多项式组成的集合就是扩展域$GF(2^8)$。每个多项式都可以按一个8位项链的数值形式存储:
$$
A=(a7,a6,a5,a4,a3,a2,a1,a0)
$$
像$x^7$、$x^6$等因子都无需存储,因为从位的位置就可以清楚地判断出每个系数对应的幂。
在AES算法中的密钥加法层中就使用了这部分的知识,但是不是很明显,因为我们通常把扩展域中的加法当作异或运算进行处理了,因为在扩展域中的加减法处理都是在底层域GF(2)内完成的,与按位异或运算等价。假设$A(x)、B(x)∈GF(2^m)$,计算两个元素之和的方法就是:
$$
C(x)=A(x)+B(x)=\sum_{i=0}^{m-1}C_ix^i\quad c_i\equiv(a_i+b_i)mod2
$$
而两个元素之差的计算公式就是:
$$
C(x)=A(x)-B(x)=\sum_{i=0}^{m-1}c_ix^i\quad c_i\equiv(a_i-b_i)mod2\equiv(a_i+b_i)mod2
$$
[!note]
注:在减法运算中减号之所以变成加号,这就和二进制减法的性质有关了,大家可以试着验算下。从上述两个公式中我们发现在扩展域中加法和减法等价,并且与XOR等价(异或运算也被称作二进制加法)。
扩展域的乘法主要运用在AES算法的列混淆层(Mix Column)中,也是列混淆层中最重要的操作。我们项要将扩展域中的两个元素用多项式形式展开,然后使用标准的多项式乘法规则将两个多项式相乘:
$$
\begin{aligned}&A(x)\cdot B(x)=(a_{m-1}x^{m-1}+\ldots+a_0)\cdot(b_{m-1}x^{m-1}+\ldots+b_0)\&C^{\prime}(x)=c_{2m-2}^{\prime}x^{2m-2}+\ldots+c_0^{\prime}\&\text{其中:}\&c_0^{\prime}=a_0b_0mod2\&c_1^{\prime}=(a_1b_0+a_0b_1)mod2\&c_{2m-2}^{\prime}=a_{m-1}b_{m-1}mod2\end{aligned}
$$
注意:通常在多项式乘法中C(x)的度会大于m-1,因此需要对此进行化简,而化简的基本思想与素域内乘法情况相似:在素域GF(p)中,将两个整数相乘得到的结果除以一个素数,化简后的结果就是最后的余数。而在扩展域中进行的操作就是:将两个多项式相乘的结果除以一个不可约多项式,最后的结果就是最后的余数。(这里的不可约多项式大致可以看作一个素数)
假设$A(x),B(x)\in GF(2^m)$,且$P(x)=\sum_{i=0}^{m}p_ix^i$其中$p\in GF(2)$是一个不可约多项式,那么两个元素$A(x)$和$B(x)$的乘法运算为:
$$
C(x)=A(x) * B(x) \quad mod \quad P(x)
$$
[!tip]
在AES算法中使用的不可约多项式为:
$$
P(x)=x^8+x^4+x^3+x^1+1
$$
MAC
消息认证码 (Message Authentication Code) 是一种确认完整性并进行认证的技术。消息认证码是一种与密钥相关联的单向散列函数。

| 实现方法 | 简要描述 |
|---|---|
| HMAC (Hash-based Message Authentication Code) | 结合散列函数和密钥,使用密钥对消息进行哈希运算,生成固定长度的哈希值 |
| CMAC (Cipher-based Message Authentication Code) Poly1305 | 使用对称加密算法(如AES)和密钥来生成MAC |
| GCM (Galois/Counter Mode) HMAC-based Key Derivation Functions (HKDF) | 一种用于生成消息认证码(Message Authentication Code,MAC)的专用算法 一种带有认证的加密模式,通常与AES一起使用 一种基于HMAC的密钥派生函数 |
AES
AES计算过程如下:
AES算法主要有四种操作处理:
- 密钥加法层(也叫轮密钥加,英文Add Round Key)
- 字节代换层(SubByte)
- 行位移层(Shift Rows)
- 列混淆层(Mix Column)。
而明文x和密钥k都是由16个字节(128位)组成的数据(当然密钥还支持192位和256位的长度,暂时不考虑)。

明文和密钥都是按照字节的先后顺序从上到下、从左到右进行排列的。而加密出的密文读取顺序也是按照这个顺序读取的,相当于将数组还原成字符串的模样了,然后再解密的时候又是按照4·4数组处理的。

AES算法在处理的轮数上只有最后一轮操作与前面的轮处理上有些许不同(最后一轮只是少了列混淆处理),在轮处理开始前还单独进行了一次轮密钥加的处理。在处理轮数上,我们只考虑128位密钥的10轮处理。接下来,就开始一步步的介绍AES算法的处理流程了。

具体流程如下图所示:

密钥轮加层
在密钥加法层中有两个输入的参数,分别是明文和子密钥k[0],而且这两个输入都是128位的。k[0]实际上就等同于密钥k,具体原因在密钥生成中进行介绍。我们前面在介绍扩展域加减法中提到过,在扩展域中加减法操作和异或运算等价,所以这里的处理也就异常的简单了,只需要将两个输入的数据进行按字节==异或==操作就会得到运算的结果。

1 | int AddRoundKey(unsigned char(*PlainArray)[4], unsigned char(*ExtendKeyArray)[44], unsigned int MinCol) |
字节代换层
字节代换层的主要功能就是让输入的数据通过==S_box(s盒)表==完成从一个字节到另一个字节的映射,这里的S_box表是通过某种方法计算出来的。
S_box表是一个拥有256个字节元素的数组,可以将其定义为一维数组,也可以将其定义为16·16的二维数组,如果将其定义为二维数组,读取S_box数据的方法就是要将输入数据的每个字节的高四位作为第一个下标,第四位作为第二个下标,略有点麻烦。这里建议将其视作一维数组即可。逆S盒与S盒对应,用于解密时对数据处理,我们对解密时的程序处理称作逆字节代换,只是使用的代换表盒加密时不同而已。
==S盒==

==逆S盒==

那么处理过程如下:

假设p1数据为0x19。那么我们需要将这个数据分为高四字节1和低四字节9,高四字节对应行,低四字节对应列。如果S盒算法0x19为0xd4
1 | //S盒 |
行移位层
行位移操作最为简单,它是用来将输入数据作为一个4·4的字节矩阵进行处理的,然后将这个矩阵的字节进行位置上的置换。
行移位层属于AES手动的扩散层,目的是将单个位上的变换扩散到影响整个状态,从而达到雪崩效应。
在加密时行位移处理与解密时的处理相反,我们这里将解密时的处理称作逆行位移。它之所以称作行位移,是因为它只在4·4矩阵的行间进行操作,每行4字节的数据。在加密时,保持矩阵的第一行不变,第二行向左移动8Bit(一个字节)、第三行向左移动2个字节、第四行向左移动3个字节。而在解密时恰恰相反,依然保持第一行不变,将第二行向右移动一个字节、第三行右移2个字节、第四行右移3个字节。操作结束!
==正向行移位==

我们可以将移位看作是循环左移,不同的行移不同数量字节
1 | int ShiftRows(unsigned int *PlainArray) |
==逆向行移位==

我们可以将移位看作是循环右移,不同的行移不同数量字节
1 | int ReShiftRows(unsigned int *CipherArray) |
列混淆层
列混淆子层是AES算法中最为复杂的部分,属于扩散层,列混淆操作是AES算法中主要的扩散元素,它混淆了输入矩阵的每一列,使输入的每个字节都会影响到4个输出字节。行位移子层和列混淆子层的组合使得经过三轮处理以后,矩阵的每个字节都依赖于16个明文字节成可能。其中包含了矩阵乘法、伽罗瓦域内加法和乘法的相关知识。
在加密的正向列混淆中,我们要将输入的4·4矩阵==左乘==一个==给定的==4·4矩阵。而它们之间的加法、乘法都在扩展域$GF(2^8)$中进行。
==正向列混淆矩阵==

==逆向列混淆矩阵==

1 | //列混淆左乘矩阵 |
我们发现在上述的矩阵乘法中,出现了加法和乘法运算,我们前面也提到过在扩展域中加法操作等同于异或运算,而乘法操作需要一个特殊的方式进行处理,于是我们就先把代码中的加号换成异或符号,然后将伽罗瓦域的乘法定义成一个有两个参数的函数,并让他返回最后计算结果。于是列混淆的代码就会变成下面的样子:
1 | const unsigned char MixArray[4][4] = |
接下来我们就只用处理伽罗瓦域乘法相关处理了,由于前面介绍过相关概念,所以代码就不在此进行讲解了,大家可以参考下方的代码注释进行理解:
1 | /////////////////////////////////////////////////////////////// |
在解密的逆向列混淆中与正向列混淆的不同之处在于使用的左乘矩阵不同,它与正向列混淆的左乘矩阵互为逆矩阵,也就是说,数据矩阵同时左乘这两个矩阵后,数据矩阵不会发生任何变化。

最后只需要将得到的密文矩阵写成128位的即可
子密钥生成
子密钥的生成是以列为单位进行的,一列是32Bit,四列组成子密钥共128Bit。生成子密钥的数量比AES算法的轮数多一个,因为第一个密钥加法层进行密钥漂白时也需要子密钥。密钥漂白是指在AES的输入和输出中都使用的子密钥的XOR加法。子密钥在图中都存储在W[0]、W[1]、…、W[43]的扩展密钥数组之中。k1-k16表示原始密钥对应的字节,而图中子密钥k0与原始子密钥相同。

在生成的扩展密钥中W的下标如果是4的倍数时(从零开始)需要对异或的参数进行G函数处理。扩展密钥生成有关公式如下:
$$
W[i]=W[i-4] \oplus G(W[i-1])\quad 4< i <44
$$

如果不是4的倍数的话,那么为:
$$
W[i]=W[i-4] \oplus W[i-1]\quad 4< i <44
$$

经过以上计算即可得到子密钥

函数G()首先将4个输入字节进行翻转,并执行一个按字节的S盒代换,最后用第一个字节与轮系数Rcon进行异或运算。轮系数是一个有10个元素的一维数组,一个元素1个字节。G()函数存在的目的有两个,一是增加密钥编排中的非线性;二是消除AES中的对称性。这两种属性都是抵抗某些分组密码攻击必要的。
| 轮系数 | 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80 | 0x1B | 0x36 |
|---|
1 | //用于密钥扩展 Rcon[0]作为填充,没有实际用途 |
解密

我们可以看到解密就是加密的反过程,其中用到了逆字节代换和逆向列混淆矩阵
代码实现
1 |
|
1 |
|
RSA
RSA的加密步骤为
-
选择一对不相等且足够大的质数
p、q -
计算出
p和q的乘积
$$
n=p*q
$$ -
计算出
n的欧拉函数
$$
\varphi(n)=(p-1)*(q-1)
$$ -
选一个与$\varphi(n)$互质的整数$e$,那么$1<e<\varphi(n)$
-
计算出e对于$\varphi(n)$的模反元素d,
$$
d*e \quad mod \quad \varphi(n)=1
$$ -
计算出公钥为
$$
KU=(e,n)
$$ -
计算出私钥为
$$
KR=(d,n)
$$ -
发送端通过公钥来加密明文(M)得到密文©:
$$
M^e \quad mod \quad n = C
$$ -
接受端通过私钥来解密密文:
$$
C^d \quad mod \quad n = M
$$
[!tip]
欧拉函数($\varphi(n)$)是⼩于n的正整数中与n互质的数的数⽬。
互质是公约数只有1的两个整数,叫做互质整数。
质数是指在⼤于1的⾃然数中,除了1和它本身以外不再有其他因数的⾃然数。
[!tip]
如果n可以分解成2个互质的整数之积,那么n的欧拉函数等于这两个因⼦的欧拉函数之积。
即若$n=pq$,且p,q互质, 则$φ(n)=φ(pq)=φ(p)*φ(q)$。
如果两个正整数e和φ(n)互质,那么⼀定可以找到⼀个整数d,使得$ed-1$被φ(n)整 除,或者说$ed$除以φ(n)所得余数为1。 此时,d就叫做e的模反元素
==证明过程==
现要证明$C^d\quad mod \quad n = M$,证明结果如下
$$
C^d \quad mod \quad n \
=(M^e \quad mod \quad n)^d \quad mod \quad n\
=M^{ed} \quad mod \quad n
$$
那么此时只需要证明$M^{ed}mod \quad n=M$即可,其中$ed-1=k\varphi(n)$,此时以上公式变为
$$
M^{ed} \quad mod \quad n\
=M^{k\varphi(n)+1} mod \quad n\
=(M^{k\varphi(n)}*M)mod \quad n
$$
而此时M小于n,那么$M \quad mod \quad n =M$,此时只需要证明$M^{k\varphi(n)}=1$即可,由欧拉定理可知只要$M^k$和$n$互质,即可证明$M^{k\varphi(n)}=1$,而这里的n是两个很大的质数相乘,也就是n的因子为$p、q$和1,而M小于$p和q$中的任意一个,所以$M^k$和$n$必然互斥,由此得证。
[!tip]
假设有两个数字a和n,如果a和n互质,由欧拉定理可知$a^{\varphi(n)}=1\quad mod \quad n$
CMAC
初始化
- 密钥选择:选择一个对称密钥K,用于加密操作(这个K)
- 子密钥生成:通过密钥K生成两个子密钥K1和K2.
- 使用密钥K通过AES算法对128bit的0000…0000进行加密,得到加密后的中间值L(128bit);
- 根据L的最高位有效位(MSB)生成K1:
- 若L的MSB为0, 则$K1 = L <<1$,
- 若L的MSB为1, $K1 = (L << 1)\oplus R_b$。其中$R_b$是一个常数,如AES中为
0x87
- 根据K1的最高有效位生成K2:
- 若K1的MSB为0, 则$K2 = K1 <<1$
- 若K1的MSB为1, $K2 = (K1 << 1)\oplus R_b$
[!tip]
可以认为CMAC是一种工作模式,支持的算法为AES,DES、3DES等。
不同的算法的$R_b$不同(b表示块位数,AES块位数为128,DES和3DES的块位数为64)
AES的$R_b$为$R_{128}=0^{120}10000111$其中10000111为固定值
DES、3DES的$R_b$为$R_{64}=0^{59}11011$,其中11011为固定值
消息处理及加密计算
现有一个消息M(明文)。
-
消息分块:将消息按分组密码的块大小(如 AES 为 128 位)进行分块,。假设分为n块。
那么就有$M=M_1||M_2||M_3 ||\ldots||M_n$,其中
||为拼接操作,$M_1$至$M_{n-1}$都为完整块。$M_n$可能为完整块,也可能是填充块。 -
块填充:
- 如果最后一块为完整块,则$M_n=K1\oplus M_n$
- 如果最后一块为填充块,则$M_n=K2 \oplus (M_n||10^j)$,这里的$||10^j$可以理解为将最后一块填充至128位。
-
块加密:
-
首先设置一个常量$x=0^{128}=0$
-
循环n轮得到最终的MAC,使用以下公式计算
$$
Y=M_i \oplus x,其中1 \leq i \leq n \
x=AES-128(K,Y)
$$ -
最终算出来的$x$就是MAC
-
代码实现
1 |
|
1 |
|