分组密码以及分组密码的模式

摘要

为了保证信息的安全性,在传输过程当中不被攻击(攻击分为主动攻击和被动攻击),一般都会采用把消息加密的方式,使用的过程当中,咱们会常用DES,3DES以及AES这些对称加密的加密算法,而且咱们也常常听到分组密码和流密码这些名词。而且也见到什么ECB,CBC这些东西,本文以DES这种对称加密算法的分组密码作一个浅显的介绍。java

密码算法分为分组密码和流密码两种。算法

分组密码:是每次只能处理特定长度的一块数据的一类密码算法,这里的“一块”就称为分组。此外一个分组的比特数就称为分组长度安全

流密码:流密码是对数据流进行连续处理的一类密码算法,流密码中通常以1比特,8比特或者32比特等为单位进行解密和加密。bash

     分组密码处理完一个分组就结束了,所以不须要经过内部状态来记录加密的进度;相对的流密码是对一串数据流进行连续处理,所以须要保持内部状态。网络

是否是看完以上感受还比较晕,再看一个更进一步的定义,分组加密(Block cipher),又称为分块加密或者块密码,它将明文分红多个等长的模块,使用肯定的算法和对称秘钥对每一个分组进行加解密。app

分组密码算法

接下来以美国政府核定的标准加密算法DES为例来讲明什么分组加密算法。ide

DES的基本机构是有Hors Feistel设计的,所以也叫Feistel网络,这个结构不只仅用在了DES上,其余不少的密码算法也有应用。函数

在Feistel网络中,加密的各个步骤称做轮,整个加密过程就是进行若干次轮的循环。下图展示的是Fesitel网络一轮的计算流程。加密

一轮明文输入会被分为左右两部分进行处理,左右两部分分别是32个bytespa

子秘钥指的是本轮加密所使用的秘钥。在Fesistel网络中,每一轮都须要使用一个不一样的子秘钥。因为子秘钥只在一轮中使用,因此只是一个局部的秘钥,所以才叫子秘钥

轮函数的做用是根据右侧和子秘钥生成左侧进行加密的比特序列,是整个加密的核心。讲轮函数的输出与左侧进行XOR(异或)运算,运算后的结果就是加密后的左侧。也就说用XOR与左侧的输入进行合并,而右侧则会直接成为输出的右侧。

其步骤以下:

(1)将输入的数据等分为左右两部分

(2)将输入的右侧直接输入到右侧

(3)讲输入的右侧发送给轮函数

(4)将轮函数根据右侧数据和子秘钥,计算出一串随机比特序列

(5)将上一步获得的比特序列与左侧数据进行XOR运算,并将结果做为加密后的左侧。

上述过程当中能够看出来,右侧的数据根本就没有加密,因此须要用不一样的子秘钥对这一轮重复处理若干次,而且每次都进行左右对调。

而解密的过程就是一个相反的过程。

以上的一个单位(64byte)就叫作一个分组,而对这组数据进行加密的过程就是分组加密。

分组模式

那什么是模式呢?

从上能够看出分组密码算法只能加密固定长度的分组,可是须要加密的明文长度一般会超过度组密码的分组长度,这时候就须要对分组密码算法进行迭代,才能把全部的明文进行加密,而迭代的方法就称为分组密码的模式。

分组密码有不少模式,比较常见的有下面五种

  1. ECB模式:Electronic CodeBook mode(电子密码本模式)
  2. CBC模式:Cipher Block Chainiing mode(密码分组连接模式)
  3. CFB模式:Cipher FeedBack mode(密文反馈模式)
  4. OFB模式:Output FeedBack mode(输出反馈模式)
  5. CTR模式:CounTeR mode (计数器模式)

ECB模式(电子密码本模式)

ECB模式比较简单,这种模式就是把一段明文,分割成一个分组长度的整数倍(例如DES的一个分组长度是64byte,上文中有提到),若是不是分组长度的整数倍,则会用一些特殊的字符进行填充,而后分别按照上述加密算法,对每个分组进行加密,最后对全部分组加密出来的数据进行合并,成为密文。下图就是对明文为(xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx)采用ECB模式加密的示意图

如下使用代码来演示一下使用AES加密算法,使用ECB模式之后,对字符串加密后而且用16进制显示出来的结果

public class AESTest {
    public static void main(String[] args) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
        String str = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
        byte[] data = str.getBytes();
        String CIPHER_ALGORITHM = "AES/ECB/PKCS5Padding";
        String KEY_ALGORITHM = "AES";
        KeyGenerator keyGenerator = KeyGenerator.getInstance(KEY_ALGORITHM);
        keyGenerator.init(128);
        SecretKey secretKey = keyGenerator.generateKey();
        byte[] encoded = secretKey.getEncoded();
        Key key = new SecretKeySpec(encoded,KEY_ALGORITHM);
        Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
        cipher.init(Cipher.ENCRYPT_MODE,key);
        byte[] bytes = cipher.doFinal(data);
        System.out.println("加密后"+parseByte2HexStr(bytes));
    }

    /**将二进制转换成16进制
     * @param buf
     * @return
     */
    public static String parseByte2HexStr(byte buf[]) {
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < buf.length; i++) {
            String hex = Integer.toHexString(buf[i] & 0xFF);
            if (hex.length() == 1) {
                hex = '0' + hex;
            }
            sb.append(hex.toUpperCase());
        }
        return sb.toString();
    }
}

输出结果为:

C57EC72731C0983EF3FFCB88D8EAB922C57EC72731C0983EF3FFCB88D8EAB922C57EC72731C0983EF3FFCB88D8EAB922C57EC72731C0983EF3FFCB88D8EAB922F628B5120016D660138EBA0EA993A5E7

这样看还不太明显,若是进行换行显示的话能够看到以下规律

C57EC72731C0983EF3FFCB88D8EAB922
C57EC72731C0983EF3FFCB88D8EAB922
C57EC72731C0983EF3FFCB88D8EAB922
C57EC72731C0983EF3FFCB88D8EAB922
F628B5120016D660138EBA0EA993A5E7

其中 C57EC72731C0983EF3FFCB88D8EAB922 这个重复显示,这个就是ECB模式加密的特色,这样只须要观察一下密文的组合,就能根据一些线索来破译密码。

因此ECB模式是存在必定风险的,所以此种加密模式并不推荐采用。

CBC模式(密码分组连接模式)

CBC模式的全称是Cipher Block Chaining模式,是由于密文分组像链条同样互相链接在一块儿。

在CBC模式中,首先将明文分组与前一个密文分组进行XOR运算,而后再进行加密

从上图能够看出,在和ECB模式相比较,在加密以前CBC模式先和上一组加密后的密文进行了一次XOR,而后才进行加密,因为第一次没有前一个分组,因此准备了一个长度为一个分组长度的比特序列用来代替“前一个分组”,这个比特序列叫作初始化向量(initialization vector),CBC模式能够克服ECB模式因为字符相同,解密出来的密文分组相同的缺点。

若是把ECB中章节中程序的模式更改成CBC,则加密结果为:

F354627AE9BA742ECB61C2D9114A3CA2938DFA3A0E7AC12D913E9A48E4AE8CBF9BC93BE23122A583DA00EE6440DD30447920421E410E40211216D126C2641FA21F70E1039D7ADC440928E3CF94E1F982

不会出现重复。

其中初始化向量是必须的,若是仅仅把CIPHER_ALGORITHM 改成AES/CBC/PKCS5Padding,再次运行程序,解密时将会报错:

加密后:FDA2BF43B9A8A4C8E0C4D2315B2B18C497B999D931BD8EE61AD60E70ABAEEE3DF9CF92C1672AE26B45A83DF7B05BF437830CAC49FDC267A53D928E72188EB2F7D26B06A2DE2673D97E5922A80A12D343
Exception in thread "main" java.security.InvalidKeyException: Parameters missing
	at com.sun.crypto.provider.CipherCore.init(CipherCore.java:388)
	at com.sun.crypto.provider.AESCipher.engineInit(AESCipher.java:186)
	at javax.crypto.Cipher.implInit(Cipher.java:787)
	at javax.crypto.Cipher.chooseProvider(Cipher.java:849)
	at javax.crypto.Cipher.init(Cipher.java:1213)
	at javax.crypto.Cipher.init(Cipher.java:1153)
	at com.wtf.crypto.AESTest.main(AESTest.java:30)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:606)
	at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)

 

须要对上述的程序进行修正,初始化一个向量,修改后的程序为:

public static void main(String[] args) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException, InvalidAlgorithmParameterException {
    String str = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
    byte[] data = str.getBytes();

    String CIPHER_ALGORITHM = "AES/CBC/PKCS5Padding";
    String KEY_ALGORITHM = "AES";
    KeyGenerator keyGenerator = KeyGenerator.getInstance(KEY_ALGORITHM);
    keyGenerator.init(128);
    SecretKey secretKey = keyGenerator.generateKey();
    byte[] encoded = secretKey.getEncoded();
    Key key = new SecretKeySpec(encoded,KEY_ALGORITHM);
    Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
    cipher.init(Cipher.ENCRYPT_MODE,key,new IvParameterSpec(new byte[16])); //设置初始向量
    byte[] bytes = cipher.doFinal(data);
    System.out.println("加密后:"+parseByte2HexStr(bytes));
    Cipher decode = Cipher.getInstance(CIPHER_ALGORITHM);
    decode.init(Cipher.DECRYPT_MODE,key,new IvParameterSpec(new byte[16])); //设置初始向量
    byte[] bytes1 = decode.doFinal(bytes);
    System.out.println("解密后:"+new String(bytes1));

}

 

这样加解密之后会产生以下输出:

加密后:976CF9D4DDE0B0544C6008CFE9CB57EFA888E0B8AC2187F1039D837CEAD88BC42A50E033B70CC2FA18BDC507F71C1CD4E0D7AA23F1A9281EB208C815FC3785E4397C00809E9C474F06131A65345B3854
解密后:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

 

CFB模式(秘钥反馈模式)

在CBC模式以及ECB模式中,都是要把明文进行加密,从上图能够看出,在CFB模式中,并无通过对明文进行加密,而是对上一个密文分组直接进行了XOR,初始化向量和CBC模式的初始化向量含义相同。在CFB模式中,密码算法的输出至关于一次性密码本中的随机比特序列。由密码算法所生成的比特序列成为秘钥流,明文数据能够被逐个比特加密,所以能够讲CFB模式看作是一种使用分组密码来实现流密码的方式。

 

OFB(输出反馈模式)

OFB模式和CFB模式的区别仅仅在于密码算法的输入。

CFB模式中,密码算法的输入是前一个密文分组,也就是将密文分组反馈到密码的算法中,所以叫作“密文反馈模式”。因此对于OFB模式,密码算法的输入则是密码算法的前一个输出,也就是将输出反馈给密码算法,故叫作“输出反馈模式”。

 

因为CFB模式中是对密文分组进行反馈的,所以必须从第一个明文分组开始按顺序进行加密,也就是说没法跳过明文分组1而先对明文分组2进行加密。

而在OFB模式中,XOR所须要的比特序列能够事先经过密码算法生成,和明文的分组没什么关系。只要提早准备好所须要的秘钥流,则在实际从明文生成密文的过程当中,就彻底不在须要动秘钥算法了,只须要把准备好的秘钥流和明文分组XOR便可。

CTR模式(计数器模式)

 

CTR模式是经过将逐次累加的计数器进行累加加密后生成秘钥流的流密码

CTR模式中,每一个分组对应一个逐次累加的计数器,并经过对计数器进行加密来生成秘钥流。也就是说,最终的密文分组是经过将计数器加密获得的比特序列,而后与明文XOR而获得的。

各模式比较

分组密码模式对比

模式 名称 优势 缺点 备注
ECB 电子密码本模式
  • 简单、快速
  • 加解密时支持并行计算
  • 明文中的重复排列会反馈到密文中
  • 能够经过删除、替换密文分组进行攻击
  • 不能抵御重放攻击
不推荐使用
CBC 密码分组连接模式
  • 明文中重复排列不会反馈到密文
  • 解密支持并行计算
  • 可以解密任意密文分组
  • 对包含某些错误比特的密文进行解密时,第一个分组的所有比特及后一个分组比特会出错
  • 加密不支持并行计算
推荐使用
CFB 秘钥反馈模式
  • 不须要进行填充
  • 解密支持并行计算
  • 可以解密任意密文分组
  • 加密不支持并行计算
  • 对包含某些错误比特的密文进行解密时,第一个分组的所有比特以及后一个分组比特会出错
  • 不能抵御重放攻击
如今已经再也不使用,推荐使用CTR模式作替代
OFB 输出反馈模式
  • 不须要填充
  • 能够实现准备好秘钥流直接和明文进行XOR加密
  • 加解密使用相同结构
  • 一个分组的错误不影响到另一个分组的加解密
  • 不支持并行计算
  • 主动攻击者反转密文分组中的某些比特时,明文分钟中相应的比特也会被反转
推荐用CTR模式替代
CTR 计数器模式
  • 不须要填充
  • 可事先为加解密作准备
  • 加解密时使用相同的结构
  • 错误分组不影响到其余分组
  • 支持加解密并行计算    
  • 主动攻击者反转密文分组中的某些比特时,明文分钟中相应的比特也会被反转
推荐使用
相关文章
相关标签/搜索