原始数据,它必须是固定的16字节长度 typedef uint8_t state_t4;
PKCS #7 padding
如果原始数据刚好16字节的倍数,再加上16字节‘16’,否则缺n个补足字节n
加密使用的密钥,它的长度可以是16/24/32字节,分别对应着平时我们说的AES-128/192/256加密算法。
1、字节求逆元 $$B^-1$$
(GF(2^8)中,0没有乘法逆元,我们约定0的乘法逆元就是0)
2、仿射变换 $$S=M_{8\times 8}B^{-1}+C$$
|s0| |1 0 0 0 1 1 1 1| |b0| |1|
|s1| |1 1 0 0 0 1 1 1| |b1| |1|
|s2| |1 1 1 0 0 0 1 1| |b2| |0|
|s3| = |1 1 1 1 0 0 0 1| |b3| + |0|
|s4| |1 1 1 1 1 0 0 0| |b4| |0|
|s5| |0 1 1 1 1 1 0 0| |b5| |1|
|s6| |0 0 1 1 1 1 1 0| |b6| |1|
|s7| |0 0 0 1 1 1 1 1| |b7| |0|
第一步,实际代码中用map硬编码
sbox是Substitution-box的缩写,也就是“替换盒子”
在解密的时候,还应该有一个盒子用于还原混淆后的结果。这个盒子rsbox,叫做Reverse Substitution-box。
行分别左移0,1,2,3
解密就是相反右移
|02 03 01 01|
C= |01 02 03 01| * S
|01 01 02 03|
|03 01 01 02|
这里的矩阵运算,要使用GF(2^8)中的加法和乘法。
例如c0 = 2s0 + 3s5 + s10 + s15。
但别忘了,我们执行的是多项式运算,因此要把2和3替换成对应的多项式:
xs0 + (x+1)s5 + s10 + s15
解密方阵
|0E 0B 0D 09|
|09 0E 0B 0D|
|0D 09 0E 0B|
|0B 0D 09 0E|
k0-k15表示原始key中的每一个字节,我们把它们4字节分成一组,计作W0-W3。
先计算W4,我们要把W3经过一个函数g处理之后,与W0异或;然后用下面的方式计算W5-W7:
W4 = g(W3) xor W0
W5 = W4 xor W1
W6 = W5 xor W2
W7 = W6 xor W3
g函数:
1、将w[3]循环左移一个字节,
2、分别对每个字节按S盒进行映射,
3、与(RC[i],0,0,0)异或 i为轮次
其中RC[1] = 1,RC[i] = 2•RC[i-1] 乘法是定义在域$$GF(2^8)$$上的。
RC = {01, 02, 04, 08, 10, 20, 40, 80, 1B, 36}。
void Cipher(state_t* state, const uint8_t* RoundKey)
{
uint8_t round = 0;
AddRoundKey(0, state, RoundKey);
for (round = 1; round < Nr; ++round)
{
SubBytes(state);
ShiftRows(state);
MixColumns(state);
AddRoundKey(round, state, RoundKey);
}
// The last round is given below.
SubBytes(state);
ShiftRows(state);
AddRoundKey(Nr, state, RoundKey);
}
其中:
state指向要加密的钥匙数据;
RoundKey指向扩展好的密钥;
第0轮,把原始数据和原始密钥进行异或;
第1-9轮,执行SubBytes -> ShiftRows -> MixColumns -> AddRoundKey的完整流程;
第10轮,不执行MixColumns;
void InvCipher(state_t* state, const uint8_t* RoundKey)
{
uint8_t round = 0;
AddRoundKey(Nr, state, RoundKey);
for (round = (Nr - 1); round > 0; --round)
{
InvShiftRows(state);
InvSubBytes(state);
AddRoundKey(round, state, RoundKey);
InvMixColumns(state);
}
InvShiftRows(state);
InvSubBytes(state);
AddRoundKey(0, state, RoundKey);
}
我们先从最简单也最不安全的ECB模式说起。所谓ECB模式,就是直接把原始要加密的数据按照定长分组,然后每一个分组用同样的Key执行加密:
很好理解对不对?但这种模式最大的问题就在于一旦原始数据的存储格式暴露了,攻击者可以无需AES密钥就对加密数据进行攻击。来看个经典的例子,假设银行账号A向账户B转账的信息是通过下面的格式存储的:
账户A: uJb17CO3LSj+SFPSTSRpUg==
账户B: 3HFW8zvoG9f4kB80B+6hrg==
转账金额: T48Km+rTBItaZim/kRvgww==
对于这组加密的数据,出于ECB模式的特性,任意改变这三行的顺序,都不会影响解密结果,因为它们的加密解密过程是完全独立的。于是,攻击者只要尝试把数据的前两行换位,这个转账的顺序就变成了账户B向账户A转账,从而达成了在不知道AES密钥的情况下攻击原始数据。
因此,除非是为了向既有系统兼容,现如今ECB这种工作模式已经不被使用了。
让下一个分组的加密和上一个分组的加密相关,有了这种关联性,移动了加密结果之后,就无法成功解密了。
也就是说,第i+1个分组要加密的数据,要先和第i个分组的加密结果异或。这样一来,我们就要为第一个分组的加密创建一个和分组长度相同的随机向量来参与异或(对于AES来说,就是个16字节的数组),这个随机向量,叫做Initial Vector,也叫做IV。
void AES_CBC_encrypt_buffer(
struct AES_ctx *ctx, uint8_t* buf, uint32_t length)
{
uintptr_t i;
uint8_t *Iv = ctx->Iv;
for (i = 0; i < length; i += AES_BLOCKLEN)
{
XorWithIv(buf, Iv);
Cipher((state_t*)buf, ctx->RoundKey);
Iv = buf;
buf += AES_BLOCKLEN;
}
/* store Iv in ctx for next call */
memcpy(ctx->Iv, Iv, AES_BLOCKLEN);
}
这里,唯一要解释一下的就是最后一个memcpy的调用,它会保存上一次加密时使用的Iv,也就是说,只要在执行AES加密之初设置了Iv,接下来所有的加密使用Iv就都可以自动生成出来了,我们可以把它看成区块链最原始的形式。
除了ECB和CBC之外,AES还支持CFB / OFB / CTR等工作模式,不过理解了分组加密的想法之后,理解它们并不困难,大家感兴趣的话可以自己去研究,我们就不一一展开了。
实例:
总流程:
字节替换
行移位
列混淆
轮密扩展
密钥扩展过程说明:将初始密钥以列为主,转化为4个32 bits的字,分别记为w[0…3];按照如下方式,依次求解w[i],其中i是整数并且属于[4,43]。
1)将w[i]循环左移一个字节。
2)分别对每个字节按S盒进行映射。
3)32 bits的常量(RC[i/4],0,0,0)进行异或,RC是一个一维数组,其中RC = {01, 02, 04, 08, 10, 20, 40, 80, 1B, 36}。
4)除了轮密钥的第一列使用上述方法,之后的二到四列都是w[i]=w[i-4]⊕w[i-1]
5)最终得到的第一个扩展密钥为(之后的每一轮密钥都是在前一轮的基础上按照上述方法得到的):
https://www.cnblogs.com/block2016/p/5596676.html
https://blog.csdn.net/lrwwll/article/details/78069013