随着对称密码的发展,DES数据加密标准算法由于密钥长度较小(56位),已经不适应当今分布式开放网络对数据加密安全性的要求,因此1997年NIST 公开征集新的数据加密标准,即AES[1]。经过三轮的筛选,比利时Joan Daeman和Vincent Rijmen提交的Rijndael算法被提议为AES的最终算法。此算法将成为美国新的数据加密标准而被广泛应用在各个领域中。尽管人们对AES还有不 同的看法,但总体来说,AES作为新一代的数据加密标准汇聚了强安全性、高性能、高效率、易用和灵活等优点。AES设计有三个密钥长 度:128,192,256位,相对而言,AES的128密钥比DES的56密钥强1021倍[2]。AES算法主要包括三个方面:轮变化、圈数和密钥扩 展。
AES 是一个新的可以用于保护电子数据的加密算法。明确地说,AES 是一个迭代的、对称密钥分组的密码,它可以使用128、192 和 256 位密钥,并且用 128 位(16字节)分组加密和解密数据。与公共密钥密码使用密钥对不同,对称密钥密码使用相同的密钥加密和解密数据。通过分组密码返回的加密数据 的位数与输入数据相同。迭代加密使用一个循环结构,在该循环中重复置换(permutations )和替换(substitutions)输入数据。Figure 1 显示了 AES 用192位密钥对一个16位字节数据块进行加密和解密的情形。
Figure 1 部分数据
AES算法概述
AES 算法是基于置换和代替的。置换是数据的重新排列,而代替是用一个单元数据替换另一个。AES 使用了几种不同的技术来实现置换和替换。为了阐明这些技术,让我们用 Figure 1 所示的数据讨论一个具体的 AES 加密例子。下面是你要加密的128位值以及它们对应的索引数组:00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
192位密钥的值是:
00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17 0 1 2 3 4 5 6 7 8 9 10 1112 13 14 15 16 17 18 19 20 21 22 23
Figure 2 S-盒( Sbox )
当 AES 的构造函数(constructor)被调用时,用于加密方法的两个表被初始化。第一个表是代替盒称为S-盒。它是一个16×16的矩阵。S-盒的前五行 和前五列如 Figure 2 所示。在幕后,加密例程获取该密钥数组并用它来生成一个名为w[]的密钥调度表,Figure 3 所示。
Figure 3 密钥调度表(Key Sched)
w[] 最初的 Nk (6) 行被作为种子,用原始密钥值(0x00 到0x17)。剩余行从种子密钥来产生。变量 Nk 代表以 32 位字为单位的种子密钥长度。稍后我分析 AES 实现时你将清楚地看到 w[] 是怎样产生的。 关键是这里现在有许多密钥使用而不只是一个。这些新的密钥被称为轮密钥(round keys)以将它们与原始种子密钥区别开来。
Figure 4 State (态)数组
AES 加密例程开始是拷贝 16 字节的输入数组到一个名为 State (态)的 4×4 字节矩阵中。(参见 Figure 4)。AES 加密算法 取名为 Cipher,它操作 State[],其过程描述的伪代码参见 Figure 5 。
在规范中,加密算法实现的一个预备的处理步骤被称为 AddRoundKey(轮密钥加)。AddRoundKey 用密钥调度表中的前四行对 State 矩阵实行一个字节一个字节的异或(XOR)操作,并用轮密钥表 w[c,r] 异或 输入 State[r,c]。
举个例子,如 果 State 矩阵的第一行保存的字节是{ 00, 44, 88, cc },第一列密钥调度表是{ 00, 04, 08, 0c },那么新的 State[0,2] 值是用 w[2,0]( 0x08 或 0x80 )异或 State[0,2](0x88)的结果:
AES 算法的主循环对 State 矩阵执行四个不同的操作,在规范中被称为 SubBytes(字节替换)、ShiftRows(行位移变换)、MixColumns(列混合变换) 和 AddRoundKey。除了每次循环 AddRoundKey 都被调用并使用密钥调度表的下面四行外,AddRoundKey 与预备处理步骤中的 AddRoundKey 相同。SubBytes 例程是一个代替操作,它将 State 矩阵中的每个字节替换成一个由 Sbox 决定的新字节。比如,如果 State[0,1]的值是 0x40 如果你想找到它的代替者,你取 State[0,1] 的值 (0x40) 并让 x 等于左边的数字(4)并让 y 等于右边的数字(0)。然后你用 x 和 y 作为索引 进到 Sbox 表中寻找代替值,如 Figure 2 所示。
ShiftRows 是一个置换操作,它将 State 矩阵中的字节向左旋转。Figure 6 示范了 ShiftRows 如何操作 State[]。State 的第0行被向左旋转0个位置,State 的第1行被向左旋转1个位置,State 的第2行被向左旋转2个位置,而 State 的第3行被向左旋转3个 位置。
Figure 6 对 State 进行 ShiftRows 操作
MixColumns 是一个代替操作,它是理解 AES 算法时最具技巧(或者说是最需要动脑筋的部分)的部分。它用 State 字节列的值进行数学域加和域乘的结果代替每个字节。我将在下一节中 详细解释专门的域加和域乘细节。
假设 State[0,1] 的值是0x09,并且列1上的其它值分别为 0x60,0xe1 和 0x04,那么State[0,1]的新值计算如下:
此处加法和乘法是专门的数学域操作,而不是平常整数的加法和乘法。
SubBytes、ShiftRows、 MixColumns 和 AddRoundKey 四个操作在一个执行 Nr 次的循环里被调用,Nr 为给定密钥大小的轮数减 1。加密算法使用的轮数要么是10,12,要么是14,这依赖于种子密钥长度是128位、192 位还是 256 位。在这个例子中,因为 Nr 等于12, 则这四个操作被调用11次。该迭代完成后,在拷贝 State 矩阵到输出参数前,加密算法调用 SubBytes、ShiftRows 和 AddRoundKey 后结束。
大致说来,AES 加密算法的核心有四个操作。AddRoundKey 使用从种子密钥值中生成的轮密钥代替 4 组字节。SubBytes 替换用一个代替表 替换单个字节。ShiftRows 通过旋转 4字节行 的 4 组字节进行序列置换。MixColumns 用域加和域乘的组合来替换字节。
有限域GF(28)的加法和乘法
正如你所看到的,AES 加密算法使用相当简单明了的技术来代替和置换,除 MixColumns 例程以外。MixColumns 使用特殊的加法和乘法。AES 所用的加法和乘法是基于数学(译者注:近世代数)的域论。尤其是 AES 基于有限域GF(28)。
GF(28)由一组从 0x00 到 0xff 的256个值组成,加上加法和乘法,因此是(28)。GF代表伽罗瓦域,以发明这一理论的数学家的名字命名。GF(28) 的一个特性是一个加法或乘法的操作的结果必须是在{0x00 ... 0xff}这组数中。虽然域论是相当深奥的,但GF(28)加法的最终结果却很简单。GF(28) 加法就是异或(XOR)操作。
然 而,GF(28)的乘法有点繁难。正如你稍后将在 C# 实现中所看到的,AES的加密和解密例程需要知道怎样只用七个常量 0x01、0x02、0x03、0x09、0x0b、0x0d 和 0x0e 来相乘。所以我不全面介绍GF(28)的乘法,而只是针对这七种特殊情况进行说明。
在GF(28)中用0x01的乘法是特殊的;它相当于普通算术中用1做乘法并且结果也同样—任何值乘0x01等于其自身。
现在让我们看看用0x02做乘法。和加法的情况相同,理论是深奥的,但最终结果十分简单。只要被乘的值小于0x80,这时乘法的结果就是该值左移1比特 位。如果被乘的值大于或等于0x80,这时乘法的结果就是左移1比特位再用值0x1b异或。它防止了“域溢出”并保持乘法的乘积在范围以内。
一旦你在GF(28)中用0x02建立了加法和乘法,你就可以用任何常量去定义乘法。用0x03做乘法时,你可以将 0x03 分解为2的幂之和。为了用 0x03 乘以任意字节b, 因为 0x03 = 0x02 + 0x01,因此:
例:
============================
imp
imp
public class AES {
public static String asHex(byte buf[]) {
StringBuffer strbuf = new StringBuffer(buf.length * 2);
int i;
for (i = 0; i < buf.length; i++)
{
if (((int) buf[i] & 0xff) < 0x10)
strbuf.append("0");
strbuf.append(Long.toString((int) buf[i] & 0xff, 16));
}
return strbuf.toString();
}
public static void main(String[] args) throws Exception {
String message = "这是个加密的例子";
System.out.println(" 原文: " + message);
System.out.println(" 原文转换格式显示:" +asHex(message.getBytes()));//string=》byte=》Hex 显示
//======生成密码
KeyGenerator kgen = KeyGenerator.getInstance("AES");// 获取密匙生成器
kgen.init(128);//生成128位的AES密码生成器
SecretKey skey = kgen.generateKey();// 生成密匙
byte[] raw = skey.getEncoded();//编码格式
SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");//生成一组扩展密钥,并放入一个数组之中
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec);//用ENCRYPT_MODE模式,用skeySpec密码组,生成AES加密方法
// ========加密message
byte[] encrypted = cipher.doFinal(message.getBytes());// 加密message
System.out.println(" 加密后: " + encrypted);//打印密文
System.out.println(" 密文转换格式后:"+asHex(encrypted));//把密文转换成16进制格式
//=======解密
cipher.init(Cipher.DECRYPT_MODE, skeySpec);
byte[] original = cipher.doFinal(encrypted);// 解密
String originalString = new String(original, "UTF8");// 重新显示明文
System.out.println(" 解密后:" + originalString);
System.out.println(" 解密出的消息转换格式显示:" +asHex(original));//byte型原文 转换成16进制型字符 现实
}
}
==================================
运行结果:
原文: 这是个加密的例子
原文转换格式显示:e8bf99e698afe4b8aae58aa0e5af86e79a84e4be8be5ad90
加密后: [B@7e80fa6f
密文转换格式后:6ab7212486090091888c15d30d65a362c9abd56be3e34541b513cd80a6716099
解密后:这是个加密的例子
解密出的消息转换格式显示:e8bf99e698afe4b8aae58aa0e5af86e79a84e4be8be5ad90