貌似博客园的“草稿”支持,而“发布文章”不支持数学公式的markdown语法,如果你想要更好的阅读体验请移步我的Hugo个人博客
一、概述
美国政府在1997年9月12日公开征集更高效更安全的替代DES加密算法,第一轮共有15种算法入选,其中5种算法入围了决赛,分别是MARS,RC6,Rijndael,Serpent和Twofish。又经过3年的验证、评测及公众讨论之后Rijndael算法最终入选。
Rijndael算法之所以最终能够被选为AES的原因是其安全、性能好、效率高、实用灵活。
Rijndael算法支持多种分组及密钥长度,介于128-256之间所有32的倍数均可,最小支持128位,最大256位,而AES标准支持的分组大小固定为128位,密钥长度有3种选择:128位、192位及256位。
二、AES算法的数学基础
Rijndaels算法中的许多运算是按字节和4字节的字来定义的。把一个字节看成是在有限域GF(2^8)上的一个元素。有限域(Finite Field)又名伽罗瓦域(Galois field),简单言之就是一个满足特定规则的集合,集合中的元素可以进行加减乘除运算,且运算结果也是属于此集合。
1. AES的基础域是有限域 GF(28)
- 一个字节的全体256种取值构成一个GF(28)
- 一个GF(2)上的8次既约多项式可生成一个 GF(28)
- GF(28)的全体元素构成加法交换群、线性空间。
- GF(28)的非零元素构成乘法循环群。
2. AES的GF(28)表示
有限域GF(2^8)上的元素有多种表示方法,为了方便,Rijndaels算法采用多项式表示法,下面是与还表示方法相关的几个定义
定义1 : 一个由比特位b7b6b5b4b3b2b1b0 组成的字节可表示成系数为(0, 1)的二进制多项式:b7X7 + b6X6 + b5X5 + b4X4 + b3X3 + b2X2 + b1X + b0
例:字节57=01010111的多项式表示为: $$x^6 + x^4 + x^2 + x + 1$$
定义2 : 在GF(28)上的加法定义为二进制多项式的加法,其系数按位模2加。
例: 57+83=D4等价于 $$(x6+x4+x2+x+1)⊕(x7+x+1)= x7+x6+x4+x2$$
定义3 : 在GF(28)上的乘法定义为二进制多项式的乘积模一个次数为8的不可约多项式 $$m(x) = x8+x4+x^3+x+1$$
其系数的十六进制表示为11B
例: 57×83=C1等价于
定义4 : 在GF(28)中,二进制多项式b(x)满足乘法逆为式a(x)b(x) mod m(x) = 1的二进制多项式。
定义5 : 在GF(28)中,倍乘函数xtime(b(x))定义为x·b(x) mod m(x)。具体的运算规则是:把字节B左移一位,若b7=1, 则减去m(x),而在GF(28)上加减法等价,即加上m(x)
例:$$xtime(57) =x(x6+x4+x^2+x+1)= x7+x5+x3+x2+x $$
3. AES的字表示与运算
(1) 字表示
AES数据处理的单位是字节和字,一个字由四个字节组成,表示为系数取自GF(28)上的次数低于4次的多项式
例: 字57 83 4A D1等价于 $$57x3+83x2+4Ax+D1$$
(2) 字运算
字运算是基于上述关于AES在GF(28)中的运算规则的。
字加法 : 两多项式系数按位模2加
字乘法 : 设a和c是两个字, a(x)和c(x)是其字多项式, AES定义a和c的乘积b为 $$b(x)=a(x)c(x) \ mod \ x^4+1$$
如:设a(x)、c(x)和b(x)分别分别如下面(图 2-1)中所示, 由b(x) = a(x)c(x) mod x4+1得(图 2-1)表达式组
写成矩阵形式如下面的(图 2-3)所示,x4+1是可约多项式,字c(x)不一定有逆,但AES选择的c(x)有逆, $$c(x) =03x3+01x2+01x+02$$
从而使得b(x)的表达式可以使用上面(图 2-4)的矩阵表示,从而使得下面的MixColumns变换变为矩阵的乘法运算。
字x乘法 :
需要注意的是:
- 列混合变换属于线性变换,起扩散作用。
- c(x)与x4 +1互素,从而保证c(x)存在逆多项式d(x),而 c(x)d(x) = 1 mod x4+1 。 只有逆多项式d(x)存在,才能正确进行解密
代码实现
/**
* 列混合变换:状态数组与多项式等价矩阵进行有限域GF(2)上的矩阵乘法
* @param state 状态数组
* @param table 多项式等价矩阵
* @return 列混合变换后的新状态
*/
private short[][] mixColumns(short[][] state, short[][] table) {
short[][] result = new short[state.length][4];
for (int i = 0; i < state.length; i++) {
result[i] = matrixMultiply(state[i], table);
}
return result;
}
/**
* 一个字与多项式等价数组在有限域GF(2)上的乘法操作
* multiplication between a word of a state and a irreducible
* polynomial <tt>C(x)=03x^3+01x^2+01^2+01x+02</tt> which is replaced as a
* constant table <tt>AESConstants.CX</tt>
* (aes-128: 4x4 x 4x1 = 4x1)
* @param aWord a word of a state
* @return multiplication result, a new word
*/
private short[] matrixMultiply(short[] aWord, short[][] table) {
short[] result = new short[4];
for (int i = 0; i < 4; i++) {
result[i] = wordMultiply(table[i], aWord);
}
return result;
}
/**
* 两个字在有限域GF(2)上的乘法操作
* multiplication between two words
* @param firstWord first operand
* @param secondWord second operand
* @return multiplication result, a byte actually
*/
private short wordMultiply(short[] firstWord, short[] secondWord) {
short result = 0;
for (int i=0; i < 4; i++) {
result ^= multiply(firstWord[i], secondWord[i]);
}
return result;
}
/**
* 有限域GF(2)上的乘法操作,通过分解操作数将之转化成有限域GF(2)上的倍乘操作
* multiplication in finite field GF(2^8)
* @param a an operand of this kind of multiplication
* @param b another operand of this kind of multiplication
* @return multiplication result
*/
private short multiply(short a, short b) {
short temp = 0;
while (b != 0) {
if ((b & 0x01) == 1) {
temp ^= a;
}
a <<= 1;
if ((a & 0x100) > 0) {
/*
judge if a is greater than 0x80, if then subtract a
irreducible polynomial which can be substituted by 0x1b
cause addition and subtraction are equivalent in this case
it\'s okay to xor 0x1b
*/
a ^= 0x1b;
}
b >>= 1;
}
return (short) (temp & 0xff);
}
6. 对外加密接口
- 将字符串形式的明文和密钥分别转换为Nb个字长的状态和Nk个字长的主密钥数组
- 通过主密钥数组生成轮密钥数组
- 将二维轮密钥数组转换成三维数组方便以Nk个字长为单位获取轮密钥
- 调用coreEncrypt方法,指定S盒运算表,列混合中用到的CX运算表和行变换每个字节的移位规则表
- 将最后获取到密文状态用Base64编码
public String encrypt(String plaintext, String key) {
// transfer plaintext and key from one-dimension matrix
// to (data.length / 4) x 4 matrix
short[][] initialPTState = transfer(transferToShorts(plaintext));
short[][] initialKeyState = transfer(transferToShorts(key));
// obtain raw round keys
short[][] rawRoundKeys = generateRoundKeys(initialKeyState);
// make it easier to obtain a whole block of round key in a round transformation
short[][][] roundKeys = transfer(rawRoundKeys);
short[][] finalState = coreEncrypt(initialPTState, roundKeys, AESConstants.SUBSTITUTE_BOX,
AESConstants.CX, AESConstants.SHIFTING_TABLE);
return Base64Util.encode(transfer2Bytes(finalState));
}
7. 核心加密逻辑
/**
* AES核心操作,通过将可逆操作抽取成可逆矩阵作为参数,使该方法能在加/解密操作中复用
* @param initialPTState 明文或密文的状态数组
* @param roundKeys 加/解密要用到的轮密钥数组
* @param substituteTable 加/解密要用到的S盒
* @param mixColumnTable 列混合中用来取代既约多项式的数组
* @param shiftingTable 行变换中用来决定字间左移的位数的数组
* @return 加/解密结果
*/
private short[][] coreEncrypt(short[][] initialPTState,
short[][][] roundKeys, short[][] substituteTable,
short[][] mixColumnTable, short[][] shiftingTable) {
// 初始轮密钥加,异或操作
short[][] state = xor(roundKeys[0], initialPTState);
// 处理前九轮变换
for (int i = 0; i < 9; i++) {
// 将状态数组的字节替换为S盒中相应位置的字节
state = substituteState(state, substituteTable);
// 行移位变换
state = shiftRows(state, shiftingTable);
// 列混合变换
state = mixColumns(state, mixColumnTable);
// 轮密钥加变换
state = xor(roundKeys[i + 1], state);
}
// 处理最后一轮
state = substituteState(state, substituteTable);
state = shiftRows(state, shiftingTable);
state = xor(roundKeys[roundKeys.length - 1], state);
return state;
}
四. 解密过程
说到加密先纵观整个AES的加解密流程,由于Rijndael算法不是对合运算,所以其解密算法与加密算法不同,根据解密算法应当是加密算法的逆,最直接的做法就是把加密算法倒序执行,便得到解密算法,但是这样不便于工程实现。
由于Rijndael算法的巧妙设计,使得我们只需稍微改变密钥扩展策略,同时把加密算法的基本运变换成逆变换,便得到解密算法,其算法结构其实与加密算法的结构相同。
1. AES基本逆变换
( 1 ) AddRoundKey
轮密钥加变换的逆就是其本身,即
( 2 ) ShiftRows
行移位变换的逆是状态的后三行分别移位Nb-C1, Nb-C2, Nb-C3个字节。
( 3 ) MixColumns
因为列混合变换是把状态的每一列都乘以一个固定的多项式c(x) :
所以列混合变换的逆就是状态的每列都乘以c(x)的逆多项式d(x):
( 4 ) SubBytes
S盒变换的逆要先进行逆仿射变换,再把每个字节用其在GF(28)中的逆来代替。
( 5 ) KeyExpansion
解密的密钥扩展与加密的密钥扩展不同,其定义如下:
- 使用加密算法的密钥扩展
- 把InvMixColumn应用到除第一和最后一轮外的所有轮密钥上
另外得到的逆轮密钥数组在轮变换中需要倒序使用,即Round1使用roundKey(Nr+1)
2. 对外的解密接口
- 使用Base64编码将密文解码并得到初始状态,获取密钥数组
- 调用私有的核心解密函数得到解密后的明文状态
- 将明文状态还原为字符串(不考虑中文编码)
public String decrypt(String encryptedText, String key) {
short[][] initialTextState = transfer(Base64Util.decodeToShorts(encryptedText));
short[][] initialKeyState = transfer(transferToShorts(key));
short[][] decryptState = coreDecrypt(initialTextState, initialKeyState);
return getOrigin(decryptState);
}
3. 核心解密逻辑
获取加密轮密钥逆变换数组,复用核心加密函数即可,主要差别在于逆变换,大体步骤如下
- 使用加密算法的密钥扩展得到轮密钥数组,并对之进行维数处理
- 对中间Nr-1个密钥进行逆列混合变换
- 为了便于操作将上述步骤得到的解密轮密钥数组逆转,这样便可通过改变参数复用加密算法的结构
/**
* 解密逻辑:通过将可逆操作抽取成可逆矩阵, 复用加密核心函数
* @param encryptedTextState initial encrypted text state
* @param keyState initial key state
* @return decrypted state
*/
private short[][] coreDecrypt(short[][] encryptedTextState, short[][] keyState) {
// obtain raw round keys
short[][] rawRoundKeys = generateRoundKeys(keyState);
// make it easier to obtain a whole block of round key in a round transformation
short[][][] roundKeys = transfer(rawRoundKeys);
// 对中间9个密钥进行逆列混合变换
for (int i = 1; i < roundKeys.length - 1; i++) {
roundKeys[i] = mixColumns(roundKeys[i], AESConstants.INVERSE_CX);
}
short[][][] inverseRoundKeys = inverseRoundKeys(roundKeys);
return coreEncrypt(encryptedTextState, inverseRoundKeys, AESConstants.
INVERSE_SUBSTITUTE_BOX, AESConstants.INVERSE_CX, AESConstants.INVERSE_SHIFTING_TABLE);
}
/**
* [解密] 将解密扩展密钥数组逆转,方便复用核心加密操作,
* @param roundKeys 解密扩展密钥数组
* @return 逆转了的解密扩展密钥数组
*/
private short[][][] inverseRoundKeys(short[][][] roundKeys) {
short[][][] result = new short[roundKeys.length][4][4];
int length = roundKeys.length;
for (int i = 0; i < roundKeys.length; i++) {
result[i] = roundKeys[length - 1 - i];
}
return result;
}
五. 测试
@Test
public void testAES() throws UnsupportedEncodingException {
String plaintext = "passwordTextCase", key = "simpleKeyCase123";
CipherService aesService = new AESCipherService();
String encryptedText = aesService.encrypt(plaintext, key);
ArrayUtil.printInfo("encrypted text", encryptedText, false);
aesService.decrypt(encryptedText, key);
}
1. 加密结果
##################### encryption #####################
plaintext text passwordTextCase
key text simpleKeyCase123
initial plaintext state 70617373776f72645465787443617365
initial key state 73696d706c654b657943617365313233
RoundKeys
[RoundKey 1] 73696d706c654b657943617365313233
[RoundKey 2] b54aae3dd92fe558a06c842bc55db618
[RoundKey 3] fb04039b222be6c3824762e8471ad4f0
[RoundKey 4] 5d4c8f3b7f6769f8fd200b10ba3adfe0
[RoundKey 5] d5d26ecfaab5073757950c27edafd3c7
[RoundKey 6] bcb4a89a1601afad4194a38aac3b704d
[RoundKey 7] 7ee54b0b68e4e4a62970472c854b3761
[RoundKey 8] 8d7fa49ce59b403acceb071649a03077
[RoundKey 9] ed7b51a708e0119dc40b168b8dab26fc
[RoundKey 10] 948ce1fa9c6cf0675867e6ecd5ccc010
[RoundKey 11] e9362bf9755adb9e2d3d3d72f8f1fd62
N = 1
SubBytes 7b30727baf67127cd8f7d4c5f75383b1
ShiftRows 7b67d4b1aff7837bd853727cf73012c5
MixColumns 3a636747bfbfc8685094ebaa7264b7b1
RoundKey b54aae3dd92fe558a06c842bc55db618
AddRoundKeys 8f29c97a66902d30f0f86f81b73901a9
N = 2
SubBytes 73a5ddda3360d8048c41a80ca9127cd3
ShiftRows 7360a8d333417cda8c12dd04a9a5d80c
MixColumns 3d8336e003efffc7ecd033486987b385
RoundKey fb04039b222be6c3824762e8471ad4f0
AddRoundKeys c687357b21c419046e9751a02e9d6775
N = 3
SubBytes b4179621fd1cd4f29f88d1e0315e859d
ShiftRows b41cd19dfd8885219f5e96f23117d4e0
MixColumns 1b79ad2bc6430753a370fb8d6f98ae4b
RoundKey 5d4c8f3b7f6769f8fd200b10ba3adfe0
AddRoundKeys 46352210b9246eab5e50f09dd5a271ab
N = 4
SubBytes 5a9693ca56369f6258538c5e033aa362
ShiftRows 5a368c625653a3ca583a936203969f5e
MixColumns 00dbc99030c41d850fe0f98566d052b0
RoundKey d5d26ecfaab5073757950c27edafd3c7
AddRoundKeys d509a75f9a711ab25875f5a28b7f8177
N = 5
SubBytes 03015ccfb8a3a2376a9de63a3dd20cf5
ShiftRows 03a3e6f5b89d0ccf6ad25c373d01a23a
MixColumns eb9a73b1144277c7d206595ee1f82d90
RoundKey bcb4a89a1601afad4194a38aac3b704d
AddRoundKeys 572edb2b0243d86a9392fad44dc35ddd
N = 6
SubBytes 5b31b9f1771a6102dc4f2d48e32e4cc1
ShiftRows 5b1a2dc1774f4cf1dc2eb902e3316148
MixColumns 74d9434382cca8636a529deca76ac8fe
RoundKey 7ee54b0b68e4e4a62970472c854b3761
AddRoundKeys 0a3c0848ea284cc54322dac02221ff9f
N = 7
SubBytes 67eb3052873429a61a9357ba93fd16db
ShiftRows 673457db879316521afd30a693eb29ba
MixColumns 1e2d8b67ffd2ceb3be0d76b4889fff03
RoundKey 8d7fa49ce59b403acceb071649a03077
AddRoundKeys 93522ffb1a498e8972e671a2c13fcf74
N = 8
SubBytes dc00150fa23b19a7408ea33a78758a92
ShiftRows dc3ba392a28e8a0f407515a77800193a
MixColumns dfc617d8532f32e7ad32edf5d36904e5
RoundKey ed7b51a708e0119dc40b168b8dab26fc
AddRoundKeys 32bd467f5bcf237a6939fb7e5ec22219
N = 9
SubBytes 237a5ad2398a26daf9120ff3582593d4
ShiftRows 238a0fd4391293d2f9255ada587a26f3
MixColumns 18e9d05305617b7506871dc0eb356049
RoundKey 948ce1fa9c6cf0675867e6ecd5ccc010
AddRoundKeys 8c6531a9990d8b125ee0fb2c3ef9a059
N = 10
SubBytes 644dc7d3eed73dc958e10f71b299e0cb
ShiftRows 64d70fcbeee1e0d35899c7c9b24d3d71
RoundKey e9362bf9755adb9e2d3d3d72f8f1fd62
AddRoundKeys 8de124329bbb3b4d75a4fabb4abcc013
encrypted text jeEkMpu7O011pPq7SrzAEw==
2. 解密结果
##################### decryption #####################
encrypted text jeEkMpu7O011pPq7SrzAEw==
key text simpleKeyCase123
initial encrypted state 8de124329bbb3b4d75a4fabb4abcc013
initial key state 73696d706c654b657943617365313233
RoundKeys
[RoundKey 1] 73696d706c654b657943617365313233
[RoundKey 2] b54aae3dd92fe558a06c842bc55db618
[RoundKey 3] fb04039b222be6c3824762e8471ad4f0
[RoundKey 4] 5d4c8f3b7f6769f8fd200b10ba3adfe0
[RoundKey 5] d5d26ecfaab5073757950c27edafd3c7
[RoundKey 6] bcb4a89a1601afad4194a38aac3b704d
[RoundKey 7] 7ee54b0b68e4e4a62970472c854b3761
[RoundKey 8] 8d7fa49ce59b403acceb071649a03077
[RoundKey 9] ed7b51a708e0119dc40b168b8dab26fc
[RoundKey 10] 948ce1fa9c6cf0675867e6ecd5ccc010
[RoundKey 11] e9362bf9755adb9e2d3d3d72f8f1fd62
inverse roundKeys
[RoundKey 1] e9362bf9755adb9e2d3d3d72f8f1fd62
[RoundKey 2] 708e03febe111cd46ee2ba036852f407
[RoundKey 3] 513f2d23ce9f1f2ad0f3a6d706b04e04
[RoundKey 4] 16b3b0df9fa032091e6cb9fdd643e8d3
[RoundKey 5] de6e462d891382d681cc8bf4c82f512e
[RoundKey 6] 0dc56d9f577dc4fb08df092249e3dada
[RoundKey 7] e0240c6e5ab8a9645fa2cdd9413cd3f8
[RoundKey 8] e0ec63caba9ca50a051a64bd1e9e1e21
[RoundKey 9] a13297635a70c6c0bf86c1b71b847a9c
[RoundKey 10] 1d7fded0fb4251a3e5f60777a402bb2b
[RoundKey 11] 73696d706c654b657943617365313233
N = 1
SubBytes 8c0dfb5999e0a0a95ef931123e658b2c
ShiftRows 8c6531a9990d8b125ee0fb2c3ef9a059
MixColumns 53040c2a87038f0697c7e0d93028d2f4
RoundKey 708e03febe111cd46ee2ba036852f407
AddRoundKeys 238a0fd4391293d2f9255ada587a26f3
N = 2
SubBytes 32cffb195b39227f69c2467a5ebd237e
ShiftRows 32bd467f5bcf237a6939fb7e5ec22219
MixColumns 8d048eb16c1195259086b3707eb0573e
RoundKey 513f2d23ce9f1f2ad0f3a6d706b04e04
AddRoundKeys dc3ba392a28e8a0f407515a77800193a
N = 3
SubBytes 934971741ae6cffb723f2f89c1528ea2
ShiftRows 93522ffb1a498e8972e671a2c13fcf74
MixColumns 7187e7041833245b0491895b45a8c169
RoundKey 16b3b0df9fa032091e6cb9fdd643e8d3
AddRoundKeys 673457db879316521afd30a693eb29ba
N = 4
SubBytes 0a28da9fea22ff48432108c5223c4cc0
ShiftRows 0a3c0848ea284cc54322dac02221ff9f
MixColumns 85746becfe5cce275de232f62b1e3066
RoundKey de6e462d891382d681cc8bf4c82f512e
AddRoundKeys 5b1a2dc1774f4cf1dc2eb902e3316148
N = 5
SubBytes 5743fadd02925d2b93c3db6a4d2ed8d4
ShiftRows 572edb2b0243d86a9392fad44dc35ddd
MixColumns 0e668b6aefe0c834620d551574e278e0
RoundKey 0dc56d9f577dc4fb08df092249e3dada
AddRoundKeys 03a3e6f5b89d0ccf6ad25c373d01a23a
N = 6
SubBytes d571f5779a75815f587fa7b28b091aa2
ShiftRows d509a75f9a711ab25875f5a28b7f8177
MixColumns ba12800c0ceb0aae07985ebb42aa4ca6
RoundKey e0240c6e5ab8a9645fa2cdd9413cd3f8
AddRoundKeys 5a368c625653a3ca583a936203969f5e
N = 7
SubBytes 4624f0abb95071105ea222abd5356e9d
ShiftRows 46352210b9246eab5e50f09dd5a271ab
MixColumns 54f0b2574714202b9a44f24f2f89cac1
RoundKey e0ec63caba9ca50a051a64bd1e9e1e21
AddRoundKeys b41cd19dfd8885219f5e96f23117d4e0
N = 8
SubBytes c6c451752197677b6e9d35042e8719a0
ShiftRows c687357b21c419046e9751a02e9d6775
MixColumns d2523fb06931ba1a33941cb3b221a290
RoundKey a13297635a70c6c0bf86c1b71b847a9c
AddRoundKeys 7360a8d333417cda8c12dd04a9a5d80c
N = 9
SubBytes 8f906fa966f8017af039c930b7292d81
ShiftRows 8f29c97a66902d30f0f86f81b73901a9
MixColumns 66180a6154b5d2d83da5750b5332a9ee
RoundKey 1d7fded0fb4251a3e5f60777a402bb2b
AddRoundKeys 7b67d4b1aff7837bd853727cf73012c5
N = 10
SubBytes 030a19561b2641032d501e0126083907
ShiftRows 03081e031b0a39012d26190726504156
RoundKey 73696d706c654b657943617365313233
AddRoundKeys 70617373776f72645465787443617365
plaintext passwordTextCase
六. 说明
1. 源码
本AES实现的代码已经发布到我的Github仓库cipher4j,如果你觉得写得还不错,欢迎star和fork; 如果对代码有疑惑,欢迎通过邮箱linfengit@qq.com与我讨论
2. TODO
- 本例子以AES-128为例,明文小于128位应该进行填充; 大于64位应该进行分组,待加密完成后进行密文的合并
- 没有就数据编码进行扩展,暂不支持中文数据加密
- 实现的是最简单的电码本(ECB)工作模式,暂不支持其他模式明文数据模式
七. 参考
- FIPS 197 -- Advanced Encryption Standard
- http://www.formaestudio.com/rijndaelinspector/
- 《密码学引论》(第三版,张焕国、唐明编著,武汉大学出版社)
作者:jordonyang
出处:https://geekeye.cc/post/security/crypto/symmetry/aes/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须在文章页面给出原文连接,否则保留追究法律责任的权利。