AES - Advanced Encryption Standard AES 是取代 DES 称为新标准的一种对称密码算法
AES 的选拔 AES 由 NIST (National Institute of Standards and Technology, 国家标准技术研究所) 组织公开竞选,最终所选拔的密码算法,成为美国的国家标准,即联邦信息处理标准(FIPS ), 但最终成为一个世界性的标准。
AES 选拔对全世界公开,参加者提供密码算法的详细规格书、算法强度的证明和实现代码,由全世界的企业和密码学家共同完成评审。2000 年, Rijndael 算法被 NIST 从最终候选算法 名单中选定成为 AES 标准。
被选为 AES 密码算法必须无条件地免费供全世界使用。
Rijndael 密码算法 两位比利时密码学家设计,也属于*分组密码算法 * (block cipher)
分组长度 : 128 比特位
密钥长度 : 128、192 和 256 比特
Rijndael 的加密和解密 和 DES 一样,Rijndael 算法由多轮构成,其中每一轮分为 SubBytes、ShiftRows、MixColumns 和 AddRoundKey 个步骤。并且使用了一种称为 SPN 结构。
1. SubBytes-逐字节替换 Rijndael 的分组是 128 比特位,即 16 字节。对 16 个字节进行 SubBytes 处理,就是将每个字节所代表的无符号整数值(0-255)作为索引,从一个拥有 256 个值的替换表 (S-Box, S盒) 中查找对应的值。简单来说,就是每个字节查表映射为新的值。
2. ShiftRows-平移行 将以 4 字节为单位的行 (row) 按照一定的规则向左平移,且每一行平移的字节数是不同的。图示为对其中一行进行处理的情形:
3. MixColumns-混合列 对一个 4 字节的值进行比特运算,将其变为另一个 4 字节的值,图示为对其中一列(column)进行处理的情形:
4. AddRoundKey-轮密钥异或运算 将 MixColumns 的输出与轮密钥进行 XOR,图示为对其中一个字节进行处理的情形:
输入的所有比特在一轮中都会被加密,相比于 DES 的 Feistel 网络相比(每一轮只加密一半输入的比特位),加密所需的轮数更少,一般重复进行10 到 14 轮,而且其中的步骤,可以分别以字节、行和列为单位进行并行计算。
注:并行计算也是衡量密码算法的一个重要指标,并行意味着加密解密的速度块 。
加密的过程:
1 SubBytes --> SbiftRows --> MixColumns --> AddRoundKey
解密,就是按照相反的顺序来进行就可以:
1 2 3 AddRoundKey --> InvMixColumns --> InvShiftRows --> InvSubBytes * Inv 表示逆运算
Rijndael 的算法背后有着严谨的数学结构,明文到密文的过程可以全部用数学公式来表达,此外,轮密钥的计算也涉及到稍微复杂的过程,没有在这里涉及。
使用 openssl 和 JCA 进行加密解密的验证 opesnssl 加密和解密 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 $ openssl enc -aes-128-cbc -e -base64 -salt -p <<< hello salt=3631C9DA382CE487 key=421990ECB00E9A9B8372D0E46727F6AA iv =5973EAA7D1C1D5240B78CB8A4458D663 U2FsdGVkX182McnaOCzkh7BPMUJaKwGCIc8cAGAtbNw= $ openssl enc -aes-128-cbc -d -base64 -salt -p <<< U2FsdGVkX182McnaOCzkh7BPMUJaKwGCIc8cAGAtbNw= salt=3631C9DA382CE487 key=421990ECB00E9A9B8372D0E46727F6AA iv =5973EAA7D1C1D5240B78CB8A4458D663 hello
使用 JCA (Java Cryptography Architecture) 进行验证 openssl 会生成 8 个比特的 salt,并以 Salted__${salt} 附加在加密的密文前,一共占 16 个字节,验证如下:
1 2 3 4 $ echo -n "U2FsdGVkX182McnaOCzkh7BPMUJaKwGCIc8cAGAtbNw=" | base64 -d | hexdump -C 00000000 53 61 6c 74 65 64 5f 5f 36 31 c9 da 38 2c e4 87 |Salted__61..8,..| 00000010 b0 4f 31 42 5a 2b 01 82 21 cf 1c 00 60 2d 6c dc |.O1BZ+..!...`-l.| 00000020
所以实际加密后密文的 16 进制为:b04f31425a2b018221cf1c00602d6cdc
使用 JCA 进行解密验证:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 public class AESCryptoTest { public static final String AES = "AES" ; public static final String AES_CBC_PKCS_5_PADDING = "AES/CBC/PKCS5Padding" ; public static void main (String[] args) { String keyHex = "421990ECB00E9A9B8372D0E46727F6AA" ; String ivHex = "5973EAA7D1C1D5240B78CB8A4458D663" ; String cipherTextHex = "b04f31425a2b018221cf1c00602d6cdc" ; byte [] key = HexUtils.fromHexString(keyHex); byte [] iv = HexUtils.fromHexString(ivHex); byte [] cipherText = HexUtils.fromHexString(cipherTextHex); byte [] clearText = aesDecrypt(key, iv, cipherText); System.out.println(new String(clearText, StandardCharsets.UTF_8)); } @SneakyThrows public static byte [] aesDecrypt(byte [] key, byte [] iv, byte [] cipherText) { SecretKeySpec secretKey = new SecretKeySpec(key, AES); AlgorithmParameters algorithmParameters = AlgorithmParameters.getInstance(AES); IvParameterSpec ivParameterSpec = new IvParameterSpec(iv); algorithmParameters.init(ivParameterSpec); Cipher cipher = Cipher.getInstance(AES_CBC_PKCS_5_PADDING); cipher.init(Cipher.DECRYPT_MODE, secretKey, algorithmParameters); return cipher.doFinal(cipherText); } }