您的位置:首页 > 编程语言 > Java开发

[转]AES加密算法及java代码实现

2017-08-22 14:59 183 查看
转自:http://www.cnblogs.com/block2016/p/5596676.html

AES加密

AES是一个对称密码,旨在取代DES成为广泛使用的标准。

一、AES的加密过程
 


二、AES的数据结构
加密解密算法的输入是一个128位分组。这些分组被描述成4×4的字节方阵,这个分组被复制到state数组中,并在加密和解密的每一阶段都被修改。在字节方阵中,每一格都是一个字,包含了4字节。在矩阵中字是按列排序的。
 


 加密由N轮构成,轮数依赖于密钥长度:16字节密钥对应10轮,24字节密钥对应12轮,32字节对应14轮。
 


三、加密解密的详细结构
AES未使用Feistel结构。其前N-1轮由4个不同的变换组成:字节代替、行移位、列混淆和轮密钥加。最后一轮仅包含三个变换。而在第一轮前面有一个起始的单变换(轮密钥加),可以视为0轮。
  字节代替(SubBytes):用一个S盒完成分组的字节到字节的代替。
  行移位(ShiftRows):一个简单的置换。
  列混淆(MixColumns):利用域GF(28)上的算术特性的一个代替。
  轮密钥加(AddRoundKey):当前分组和扩展密钥的一部分进行按位异或XOR。
 


首尾使用轮密钥加的理由:若将其他不需要密钥的阶段放在首尾,在不知道密钥的情况下就能计算其逆,这就不能增加算法的安全性。
加密原理:轮密钥加实际是一种Vernam密码形式,其本身不难被破解。另外三个阶段一起提供了混淆、扩散和非线性功能。这三个阶段没有涉及密钥,就它们自身而言,并未提供算法的安全性。然而,该算法经历一个分组的XOR加密(轮密钥加),再对该分组混淆扩散(其他三个阶段),再接着又是XOR加密,如此交替进行,这种方式非常有效非常安全。
可逆原理:每个阶段均可逆。对字节代替、行移位和列混淆,在解密算法中用它们相对应的逆函数。轮密钥加的逆就是用同样的轮密钥和分组相异或,其原理就是A⊕B⊕B
= A。和大多数分组密码一样,AES解密算法按逆序利用扩展密钥,然而其解密算法和加密算法并不一样,这是由AES的特定结构决定的。图5.3中加密和解密流程在纵向上是相反的,在每个水平点上,state数组在加密和解密函数中都是一样的。
 


四、AES的变换函数
1、字节代替变换
 


字节代替变换是一个简单的查表操作。AES定义了一个S盒,它是由16×16个字节组成是矩阵,包含了8位所能表示的256个数的一个置换。State中每个字节按照如下方式映射为一个新的字节:把该字节的高4位作为行值,低4位作为列值,以这些数值为索引从S盒的对应位置取出元素作为输出。如,十六进制数{95}所对应的S盒行值是9,列值是5。S盒中在此位置的值是{2A},相应的,{95}被映射为{2A}。
S盒的构造
(1)按字节值的升序逐行初始化S盒(相当于每个值都代表了坐标)
(2)把S盒的每个字节映射为它在有限域GF(28)中的逆
(3)把S盒中的每个字节的8个构成位记为(b7,b6,b5,b4,b3,b2,b1,b0)。对S盒的每个字节的每个位作如下变换:
  bi’=b(i+4)mod8⊕b(i+5)mod8⊕b(i+6)mod8⊕b(i+7)mod8⊕ci          (5.1)
  这里ci 是指值为{63}的字节c的第i位。
  AES标准用矩阵形式描述了这个变换:
   


逆字节代替变换则采用逆S盒,
逆S盒的构造
(1)按字节值的升序逐行初始化S盒
(2)利用式5.1的逆变换,该逆变换如下:
  bi’=b(i+2)mod8⊕b(i+5)mod8⊕b(i+7)mod8⊕di          (5.3)
  这里di 是指值为{05}的字节d的第i位。也可以用矩阵形式描述:
   


(3)求其在GF(28)内的乘法逆
 
可逆证明:
令字节代替变换和逆字节代替变换中的矩阵分别为XY,常量c和d的向量表示分别为CD。对于某个8位的向量B,式5.2变成了B’=XBC。我们需证明Y(XBC)⊕D=B
 


与DES加密的S盒的区别
  1、AES的S盒的原理是运用了GF(28)的乘法逆和矩阵的可逆运算来保证加密与解密过程的可逆性。DES的S盒设计主要是为了确保非线性关系,并不需要可逆。因而在设计理念上有极大的不同。
  2、AES的S盒与DES的S盒形式上差别也很大,AES的S盒输入和输出的位数相同,均为128位,大小为16×16的字节矩阵,且只有1组;而DES的S盒输入6位,输出只有4位,大小是4×16的位矩阵,并且有8组。在输入的坐标选择规定上亦有不同。
 
2、行移位变换
操作本身很简单,将state数组的第一行保持不变,第二行循环左移一个字节,第三行循环左移两个字节,第四行循环左移三个字节。
 


其逆变换则将移位的几行执行相反方向的移位操作即可。
基本原理
由于轮密钥加、字节代替变换都是逐列地作用在state数组上,每一轮的行移位变换将会打乱列排列,使得保密性得到很大的提升。
3、列混淆变换
列混淆变换实际上是使用乘法矩阵(注意:其运算中涉及的加法和乘法都是定义在GF(28)上的加法和乘法,目的就是为了确保运算结果不会溢出定义域),可用以下式子描述。
 


逆向列混淆变换可由如下矩阵乘法定义
 


其可逆性可以简单运算得到证明
基本原理
式5.3中矩阵的系数是基于码字间有最大距离的线性编码,这使得在每列的所有字节中有良好的混淆性。列混淆变换和行移位变换使得经过几轮变换后,所有的输入位和所有的输出位相关。
此外,列混淆变换的系数,即{01}{02}{03}是基于算法实现角度考虑的。不过,逆向列混淆变换的系数则更加难以实现,然而加密被视为比解密更重要,因为:
  1、对于CFB和OFB密码模式,仅用到加密算法
  2、和任何其他分组密码一样,AES能用于构造消息验证码,这仅仅用到了加密过程。
 
4、轮密钥加变换
这个比较简单,没有太多好说的,密钥扩展的复杂性是确保算法安全性的重要部分。
 
以下是描述单轮AES的另一个视角,强调各变换的机制和输入。
 


 
 
五、AES的密钥扩展
AES密钥扩展算法的输入值是4个字(16字节),输出值是一个由44个字组成(176字节)的一维线性数组。以下伪码描述了这个扩展:
  KeyExpansion(byte key[16], word w[44]){
    word temp
    for(i=0; i<4; i++)  //将输入的密钥直接复制到扩展密钥数组的前四个字
      w[i]=word(key[4*i],key[4*i+1],key[4*i+2],key[4*i+3]);
    temp = w[i-1];
    if(i mod 4 == 0)  //对w数组下标为4的倍数的元素采用更复杂的函数来计算
      temp = SubWord(RotWord(temp))⊕Rcon[i/4];
    w[i] = w[i-4] + temp; //每一个新增的字w[i]依赖于w[i-1] 和w[i-4] 
  }
  RotWord的功能是字循环,即使一个字的4个字节循环左移1个字节。
  SubWord是利用S盒对输入字的每个字节进行字节代替。
  Rcon[i]是轮常量,代表一个字,这个字最右边三个字节总是0,因此字与Rcon异或,其结果只是与该字最左边的那个字节相异或。每一轮的轮常量都不相同,其定义为
  Rcon[i] = (RC[i],0,0,0),其中RC[1] = 1,RC[i] = 2•RC[i-1] 乘法是定义在域GF(28)上的。
  RC[i]的值按照十六进制表示为
 


轮常量取不同值就是为了消除不同轮密钥产生方式上的对称性或相似性。
 
密钥扩展算法的设计规范:
1、找到密钥或轮密钥的部分位不足以计算出轮密钥的其他位;
2、它是一个可逆的变换(即知道扩展密钥中任何连续的Nk个字能够重新产生整个扩展密钥,Nk是构成密钥所需的字数);
3、能够在各种处理器上有效地执行;
4、使用轮常量消除对称性;
5、将密钥差异性扩散到轮密钥中的能力,即密钥的每个位能影响轮密钥的许多位;
6、足够的非线性以防止轮密钥的差异完全由密钥的差异所决定。
 
改进--等价的逆算法
上文所述的标准解密流程与标准加密流程并不完全一致,加密每一轮的流程是:字节代替-->行移位-->列混淆-->轮密加。而解密每一轮的流程是:逆向行移位-->逆向字节代替-->轮密加-->逆向列混淆。
可以对解密构成进行改进,使得解密流程与加密流程等效。
1、交换逆向行移位和逆向字节代替:
由于逆向行移位并不影响state数组中字节的内容,而逆向字节代替也不会影响state数组中字节的位置,因而两者可以交换顺序而不影响解密。
2、交换轮密钥加和逆向列混淆:
这两种操作均不会改变state中字节的顺序,给定状态Si和给定轮密钥wi,可证明
逆向列混淆(Si⊕wi)= [逆向列混淆(Si)]⊕[逆向列混淆(wi)]
这个等式显然是正确的,因而如果要改变这两种操作的顺序,则必须改进逆向列混淆的操作,即先对轮密钥应用逆向列混淆(注意,无需对首尾的轮密钥应用逆向列混淆)。最终,改进后的解密流程如下图:
 


加密代码实现:
public static byte[] decrypt(byte[] content, String password) {
try {
KeyGenerator kgen = KeyGenerator.getInstance("AES");
kgen.init(128, new SecureRandom(password.getBytes()));
SecretKey secretKey = kgen.generateKey();
byte[] enCodeFormat = secretKey.getEncoded();
SecretKeySpec key = new SecretKeySpec(enCodeFormat, "AES");
Cipher cipher = Cipher.getInstance("AES");// 创建密码器
cipher.init(Cipher.DECRYPT_MODE, key);// 初始化
byte[] result = cipher.doFinal(content);
return result; // 加密
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (NoSuchPaddingException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
} catch (BadPaddingException e) {
e.printStackTrace();
}
return null;
}

遇到的问题:

程序中用到了AES加密和Base64加密,通过这些算法得出的结果均是字节码,但是我程序中使用一个通讯接口,其接受的参数类型为String。所以在发送时,需要转换为String。
       
对于java来说,byte只能表示有符号的数据即范围为-128~127,所以对于编码后,如果原本字节流中的信息有大于127的话,将其转换成String类型,发送的时候再转换为byte[]时,会出现与原始字节码不一致的现象。
       
因为在java中如果找不到合适的字符的话,默认会用'?'代替,如对于0xC9,很显然无法表示成字符,所以在进行byte[]->String->byte[]的时候,就会变成0x3F('?')。

此时可以通过字符编码的方式来解决:在进行byte[]->String的转换时,利用"new
String(byteArray, "ISO-8859-1");"得到String。在进行String->byte[]的时候,再通过pkt.getBytes("ISO-8859-1")得到原始byte[],这样数据就不会出现错误。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: