我们在上一篇《详细解析DES系列加密技术(一)》中提到说DES在1999年1月被破解,并且有分析报告提出DES算法在理论上存在的一些漏洞,另外,2001年,DES作为一个标准已经被取代了.一旦一种加密技术被破解,那么,被取代也就是必然的事情了,对于DES来说,取代他的又是谁呢?今天我们来讨论一下DES的后辈,也就是3DES和AES. 3DES(triple-DES)是为了增加DES的强度,将DES重复3次所得到的一种密码算法,也被称为TDEA(Triple Data Encryption Algorithm),缩写为3DES,其过程如下图所示:

明文经过3次DES处理才能变成最后的密文,由于DES密钥的长度实质上是56bit,因此3DES的密钥长度就是168bit. 虽然3DES是将DES重复了3次,但是透过上图就能够发现3DES并不是进行了三次DES的加密(加密—加密—加密),而是加密—解密—加密.这个是由IMB设计出来的,目的是为了兼容普通的DES,比如说当3DES的三个密钥都相同时,3DES也就相当于是普通的DES了.也就是说3DES具有向下兼容性. 在上一篇博文中我们谈到DES时说,DES的加密和解密过程只是改变了子密钥的顺序,而实际上处理都是相同的.所以说,如果所有密钥都使用相同的比特序列,那么其结果与普通的DES就是等价的. 我们看刚刚那幅图,如果密钥1和密钥3使用相同的密钥,而密钥2使用不同的密钥(也就是只使用两个DES密钥),这种三重DES就成为DES-EDE2.EDE表示的是加密(Encryption)—解密(Decryption)—加密(Encryption),如下图所示:

3DES的解密过程与加密过程正好相反,是以密钥3,密钥2,密钥1的顺序执行解密—加密—解密的操作.以下是Java实现3DES加/解密的过程: 附带代码1.java package org.shangzeng.cipher;

import javax.crypto.*; import javax.crypto.spec.SecretKeySpec; import java.io.UnsupportedEncodingException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException;

public class ThreeDESTest {

public static void main(String[] args) throws IllegalBlockSizeException, InvalidKeyException, BadPaddingException, NoSuchAlgorithmException, NoSuchPaddingException {
    String content="Hello,ThreeDES Test!";
    String key="shangzengxueyuan2018";
    byte[] encryptResult=encryThreeDES(content,key);
    String result=convertByteToHexString(encryptResult);
    System.out.println("3DES加密后为:"+result);
    byte[] decryptResult=decryptThreeDES(encryptResult,key);
    System.out.println("3DES解密后的内容为:"+new String(decryptResult));
}


/***
 *  将byte数组转换成16进制输出
 */
public static String convertByteToHexString(byte[] byteArray){
    String result="";
    for (int i = 0; i < byteArray.length; i++) {
        int temp=byteArray[i]& 0xff;
        String tempHex= Integer.toHexString(temp);
        if(tempHex.length()<2){
            result+="0"+tempHex;
        }else{
            result+=tempHex;
        }
    }
    return result;
}

public static byte[] get3DESKey(String pass){
    byte[] key=new byte[24];//3DES里的密钥key为24位
    byte[] temp;
    try {
        temp=pass.getBytes("UTF-8");//将字符串转成字节数组
        if(key.length>temp.length){
            //如果temp不够24位,则拷贝temp数组整个长度的内容到key数组中
            System.arraycopy(temp,0,key,0,temp.length);
        }else{
            //如果temp大于24位,则拷贝temp24个长度的内容到key数组中
            System.arraycopy(temp,0,key,0,key.length);
        }
    } catch (UnsupportedEncodingException e) {
        e.printStackTrace();
    }
    return key;
}

private static final String ALGORITHM="DESede";
public static byte[] encryThreeDES(String message,String key) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
    return threeDES(message.getBytes(),key,Cipher.ENCRYPT_MODE);
}
public static byte[] decryptThreeDES(byte[] contentArray,String key) throws IllegalBlockSizeException, InvalidKeyException, BadPaddingException, NoSuchAlgorithmException, NoSuchPaddingException {
    return threeDES(contentArray,key,Cipher.DECRYPT_MODE);
}

private static byte[] threeDES(byte[] contentArray,String key,int mode) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
    SecretKey desKey=new SecretKeySpec(get3DESKey(key), ALGORITHM);
    Cipher cipher= null;
    cipher = Cipher.getInstance(ALGORITHM);
    cipher.init(mode,desKey);
    return cipher.doFinal(contentArray);
}

}

3DES虽然比DES要安全一些,但是他的处理速度不高.所以除了特别重视向下兼容性的情况以外,很少被用于新的用途.接下来我们来谈谈AES. AES(Advanced Encryption Standard)也就是高级加密标准,他是为取代DES而成为新标准的一种对称加密算法.谈到这里我们发现,AES和DES一样,只是加密的标准,而不负责具体的加密算法,DES是由Feistel实现的,而AES的实现,则由NIST(National Institute of Standards and Technology,国家标准技术研究所)组织一个公开竞选活动去竞选.这个研究所选拔的密码算法,会成为美国的国家标准,也就是我们在上一篇文章中提到的FIPS.因为参加这个竞选的条件是:被选择AES的密码算法必须无条件地免费供全世界使用,所以AES虽然是美国的标准,却也和DES一样,成为了世界性的标准. AES竞选的参与者还必须提交密码算法的详细规格书、以ANSI C和Java编写的实现代码以及抗密码破译强度的评估等材料.所以,参加者所提交的密码算法,必须在详细设计和程序代码完全公开的情况下,依然保证较高的强度,这样呢,就杜绝了隐蔽式安全性. AES竞选的活动虽然是由NIST组织的,但密码算法的评审却不是由NIST完成的,这个评审是由全世界的企业和密码学家共同完成的.所以参加者都会努力从各个角度寻找其他密码算法的弱点,并向其他参与评审的人进行证明.这种方式就是通过竞争来实现标准化. 在前面我们提到过,3DES虽然从安全性上来说优于DES,但是,他的处理速度并不高.因此,我们必须要考虑实现AES的算法的处理速度,除此之外,算法本身是否存在弱点、实现的容易性,密钥准备的速度,能否在各种平台,比如智能卡,8位CPU等低性能平台以及工作站等高性能平台上有效工作,这些均是AES选拔参与者需要考虑的范围. 从1997年,NIST开始募集,到1999年,15个算法有5个算法入围,最终2000年10月2日,Rijndael力压群雄,被NIST选定为AES标准.Rijndael是由比利时密码学家Joan Daemen和Vincent Rijmen设计的分组密码算法.不知道Rijndal是否和这两位密码学家的名字有关系. 下面我们来详细说明一下Rijndael的加密和解密: 首先Rijndael的分组长度和密钥长度可以分别以32bit为单位在128bit到256bit的范围内进行选择.不过,在AES的规格中,分组长度固定位128bit,密钥长度只有128,192和256三种. 和DES一样,Rijndael算法也是由多个轮构成的,只不过,DES使用Feistel网络作为其基本结构,而Rijndael则是使用了SPN结构.其一个单位的加解密过程如下图所示:

现在我们对这四种处理进行详细解析: 通过上图我们知道Rijndael的单位输入分组为128bit,也就是16字节.首先需要对这16个字节进行SubBytes处理.所谓SubBytes处理可以简单理解为输入的每个字节的值都在另一张替换表中拥有其对应的值,然后按着这种对应关系进行一一替换.如下图所示:

在SubBytes之后,就是ShiftRows处理,这一步是将以4字节为一个单位的行按着一定的规则向左平移,并且每一行平移的字节数不相同,如下图所示:

之后我们开始进行第三步处理,即MixColumns处理,这一步仍然是对以4字节为单位的值进行比特运算,将其变为另外一个4字节值,类似于第一步的SubBytes,如下图所示:

最后我们要将MixColumns的输出与轮密钥进行XOR,即AddRoundKey处理,到这里,Rijndael的一轮就结束了. 因为在Rijndael中所有输入的bit都会在一轮中进行加密,因此与Feistel网络相比,加密所需要的轮数就减少了.并且以上这四种方式可以并行进行.至于解密过程,除了最后一步AddRoundKey与加密时相同之外,其他三种处理都是与原步骤相对应的的逆运算.以下是AES的Java实现: 附加代码2.javapackage org.shangzeng.testsignature;

import javax.crypto.*; import java.io.UnsupportedEncodingException; import java.nio.charset.Charset; import java.security.InvalidKeyException; import java.security.Key; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom;

public class AESTest {

private static String convertByteToHexString(byte[] byteArray){
    String result="";
    for (int i = 0; i < byteArray.length; i++) {
        int temp=byteArray[i]& 0xff;
        String tempHex= Integer.toHexString(temp);
        if(tempHex.length()<2){
            result+="0"+tempHex;
        }else{
            result+=tempHex;
        }
    }
    return result;
}

static final String ALGORITHM="AES";
static SecretKey secretKey=null;
public static SecretKey generateKey() throws NoSuchAlgorithmException {
    if(null==secretKey){
        KeyGenerator keyGenerator=KeyGenerator.getInstance(ALGORITHM);
        SecureRandom secureRandom=new SecureRandom();
        keyGenerator.init(secureRandom);
        secretKey=keyGenerator.generateKey();
    }
    return secretKey;
}

public static byte[] encrypt(String content) throws NoSuchAlgorithmException, IllegalBlockSizeException, InvalidKeyException, BadPaddingException, NoSuchPaddingException {
    SecretKey secretKey=generateKey();
    return aes(Cipher.ENCRYPT_MODE,secretKey,content.getBytes(Charset.forName("UTF-8")));
}

public static String decrypt(byte[] contentArray) throws NoSuchAlgorithmException, IllegalBlockSizeException, InvalidKeyException, BadPaddingException, NoSuchPaddingException, UnsupportedEncodingException {
    SecretKey secretKey=generateKey();
    byte[] resultByte= aes(Cipher.DECRYPT_MODE,secretKey,contentArray);
    return new String(resultByte,"UTF-8");
}


private static byte[] aes(int mode,Key key,byte[] contentArray) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
    Cipher cipher=Cipher.getInstance(ALGORITHM);
    cipher.init(mode,key);
    byte[] result=cipher.doFinal(contentArray);
    return result;
}

public static void main(String[] args) throws InvalidKeyException, BadPaddingException, NoSuchAlgorithmException, IllegalBlockSizeException, NoSuchPaddingException, UnsupportedEncodingException {
    String content="你好,我是熵熷学院Jacob老师.";
    byte[] encryptResult=encrypt(content);
    String resultByHex=convertByteToHexString(encryptResult);
    String resultByUnicode=new String(encryptResult,"UTF-8");
    System.out.println("加密后的结果16进制表示为:"+resultByHex+"\n 加密后的结果转换成字符串为:"+resultByUnicode);
    String decryptResult=decrypt(encryptResult);
    System.out.println("解密后的结果为:"+decryptResult);
}

}

对于Rijndael来说,因为它具有非常严谨的数学结构,也就是说从明文到密文的计算过程可以全部用公式来表达,这是之前其他算法所不具备的性质,所以,严谨的数学结构这个性质也就意味着Rijndael能够通过数学方法进行破译,当然这目前只是一种假设.目前来看,还没有出现针对Rijndael的有效×××,因此以Rijndael为实现的AES还是比较安全的.