常见对称加密原理以及应用

加密算法

所谓对称加密算法,通过密钥将明文加密成密文,并且再通过同一个密钥将密文解密成明文,相对于非对称加密算法速度快效率高,对于明文文本越长效率优势越大。

常见的对称加密算法有AES、DES、3DES等,其中DES由于密钥长度低容易被暴力破解,因此安全性相对较低已经不推荐使用。而3DES则是DES的升级版,安全性有所提升,但依然不如AES,因此推荐安全性更高的AES加密算法。

算法 算法类型 密钥长度 分组长度 安全性
AES 块密码算法 128/192/256比特 128比特 安全
DES 块密码算法 56比特 64比特 不安全
3DES 块密码算法 128/168比特 64比特 安全

分组模式

AES、DES、3DES都是块密码算法,即运算加密解密时不是一次性将整个明文/密文文本进行运算,而是拆成固定长度的数据块后对每个数据块进行运算。

由于文本被拆分成若干个数据块,对于超过一个数据块的场景需要定义数据块之间的拆分和组装方式,即分组模式,场景的分组模式如下表:

分组 是否需要填充 安全性
ECB 不安全
CBC 安全
CTR 安全

ECB

ECB是最简单的一种分组模式,简单地将明文拆成数据块后,每个数据块单独加密解密。如果两个数据块内容相同,那么这两个数据块加密后的密文段也是完全一样,容易受到重放攻击以及密文篡改,因此安全度不高。

常见对称加密原理以及应用_第1张图片

常见对称加密原理以及应用_第2张图片

CBC

CBC引入了一个新的变量初始向量IV,一般是一个随机数,用于在第一块明文数据块加密前对数据块做XOR运算,之后每一块明文数据块加密前都与前一块的密文数据块做XOR运算。

由于每一块数据块的计算结果都与上一块数据块有关联,因此即使有两个相同的数据块,计算出的密文段也不同,解决了ECB的安全问题。但因此,CBC计算只能串行处理,效率不如ECB。

常见对称加密原理以及应用_第3张图片

常见对称加密原理以及应用_第4张图片

CTR

CTR同样引入了一个新的变量初始随机数,每一个数据块计算时先对随机数+1,然后再参与加密解密运算,因此即使有两个相同的数据块,计算出的密文段也不同,解决了ECB的安全问题。并且每个数据块仅与随机数有联系,因此可以并行处理。

常见对称加密原理以及应用_第5张图片

常见对称加密原理以及应用_第6张图片

填充算法

对于块加密算法来说,不是对一次性对整个明文进行加密计算,而是拆分成若干个固定长度(加密算法的分组长度)的数据块,然后对各个数据块进行加密计算,因此明文的字节长度必须是分组长度的整数倍数。如果明文的字节长度不是分组长度的整数倍数,则需要一种填充的机制,在加密前将明文字节长度填充成分组长度的整数倍数,并且在解密后正确移除填充的字节。

常见的填充算法有PKCS#7和PKCS#5,而实际上两者的处理逻辑是相同的,只是PKCS#5只能处理分组长度为8字节的数据,而PKCS#7可以处理分组长度是1-255任意长度字节的数据。从这个角度看,PKCS#5是PKCS#7的子集。

PKCS#7很简单,实现代码如下:

/**
 * PKCS#7填充实现
 * @param src 明文
 * @param cipherBlockSize 加密算法分组长度
 */
public static byte[] padding7(byte[] src, int cipherBlockSize) {
    // PKCS#7的分组最大为255字节
    int blockSize = Math.min(255, cipherBlockSize);
    int padNum = blockSize - src.length % blockSize;
    byte[] dst = new byte[src.length + padNum];
    System.arraycopy(src, 0, dst, 0, src.length);
    for (int i = 0; i < padNum; i++) {
        dst[src.length + i] = (byte) padNum;
    }
    return dst;
}

/**
 * PKCS#5填充实现
 * @param src 明文
 * @param cipherBlockSize 加密算法分组长度
 */
public static byte[] padding5(byte[] src, int cipherBlockSize) {
    // PKCS#5的分组固定为8字节
    final int blockSize = 8;
    if (cipherBlockSize != blockSize) {
        throw new RuntimeException("block length not support");
    }
    int padNum = blockSize - src.length % blockSize;
    byte[] dst = new byte[src.length + padNum];
    System.arraycopy(src, 0, dst, 0, src.length);
    for (int i = 0; i < padNum; i++) {
        dst[src.length + i] = (byte) padNum;
    }
    return dst;
}

如果数据需要填充n个字节,则在数据后添加n个字节长度的字节,并且填充的每个字节的值为n。另外,如果数据长度刚好是分组长度的整数倍时,依然还需要填充,填充长度为分组长度。

0x01
0x02 0x02
0x03 0x03 0x03
0x04 0x04 0x04 0x04
  • 分组长度8字节时数据5字节,需要填充3个字节:
Hello
0x48 0x65 0x6c 0x6c 0x6f
0x48 0x65 0x6c 0x6c 0x6f 0x03 0x03 0x03
  • 分组长度8字节时数据8字节,本应不需要填充,但实际上再额外填充8字节:
EchoEcho
0x45 0x63 0x68 0x6f 0x45 0x63 0x68 0x6f
0x45 0x63 0x68 0x6f 0x45 0x63 0x68 0x6f 0x08 0x08 0x08 0x08 0x08 0x08 0x08 0x08

AES算法的分组长度超过8字节,因此不能使用PKCS#5,但有些程序使用AES算法时若指定PKCS#5时并未报错,实际内部依然使用了PCKS#7。

代码实现

常见的填充算法有PCKS#5和PKCS#7,但JDK的语法中存在PKCS5Padding却不存在PKCS7Padding。查阅官方文档Cipher以及Cipher Algorithm Padding后确认语法中确实不存在PKCS7Padding

JDK中PKCS#7命名问题无从考证,但可以确定的是,JDK中语法上的PKCS5Padding实际上就是PKCS#7的实现,而官方文档Cipher也显示了这一点:

Every implementation of the Java platform is required to support the following standard Cipher transformations with the keysizes in parentheses:

  • AES/CBC/NoPadding (128)
  • AES/CBC/PKCS5Padding (128)
  • AES/ECB/NoPadding (128)
  • AES/ECB/PKCS5Padding (128)
  • DES/CBC/NoPadding (56)
  • DES/CBC/PKCS5Padding (56)
  • DES/ECB/NoPadding (56)
  • DES/ECB/PKCS5Padding (56)
  • DESede/CBC/NoPadding (168)
  • DESede/CBC/PKCS5Padding (168)
  • DESede/ECB/NoPadding (168)
  • DESede/ECB/PKCS5Padding (168)
  • RSA/ECB/PKCS1Padding (1024, 2048)
  • RSA/ECB/OAEPWithSHA-1AndMGF1Padding (1024, 2048)
  • RSA/ECB/OAEPWithSHA-256AndMGF1Padding (1024, 2048)

DES-ECB

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.binary.Hex;

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;

public class DESECBUtils {

    public static String encrypt(String key, String data) throws Exception {
        Cipher cipher = initCipher(key, Cipher.ENCRYPT_MODE);
        byte[] encryptedByte = cipher.doFinal(data.getBytes());
        return Base64.encodeBase64String(encryptedByte);
    }

    public static String decrypt(String key, String data) throws Exception {
        Cipher cipher = initCipher(key, Cipher.DECRYPT_MODE);
        byte[] decryptedByte = cipher.doFinal(Base64.decodeBase64(data));
        return new String(decryptedByte);
    }

    private static Cipher initCipher(String key, int encryptMode) throws Exception {
        Cipher cipher = Cipher.getInstance("DES/ECB/PKCS5Padding");
        cipher.init(encryptMode, new SecretKeySpec(key.getBytes(), "DES"));
        return cipher;
    }

    public static void main(String[] args) throws Exception {
        String key = "12345678";
        String value = "HelloWorld";

        // 16进制的加密参数key
        System.out.println("key hex: " + Hex.encodeHexString(key.getBytes()));

        // 加密
        String encrypt = encrypt(key, value);
        System.out.println("encrypt: " + encrypt);
        // 解密
        String decrypt = decrypt(key, encrypt);
        System.out.println("decrypt: " + decrypt);
    }

}
key hex: 3132333435363738
encrypt: 8SBfruuCs9qh0WiTqhkSbg==
decrypt: HelloWorld

OpenSSL命令行调用:

[root@VM_0_10_centos ~]# echo -n HelloWorld > in.txt
[root@VM_0_10_centos ~]# openssl enc -des-ecb -in in.txt -out encrypt.txt -K 3132333435363738 -a
[root@VM_0_10_centos ~]# cat encrypt.txt
8SBfruuCs9qh0WiTqhkSbg==
[root@VM_0_10_centos ~]# openssl enc -des-ecb -in encrypt.txt -out decrypt.txt -K 3132333435363738 -a -d
[root@VM_0_10_centos ~]# cat decrypt.txt 
HelloWorld

DES-CBC

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.binary.Hex;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

public class DESCBCUtils {

    public static String encrypt(String key, String iv, String data) throws Exception {
        Cipher cipher = initCipher(key, iv, Cipher.ENCRYPT_MODE);
        byte[] encryptedByte = cipher.doFinal(data.getBytes());
        return Base64.encodeBase64String(encryptedByte);
    }

    public static String decrypt(String key, String iv, String data) throws Exception {
        Cipher cipher = initCipher(key, iv, Cipher.DECRYPT_MODE);
        byte[] decryptedByte = cipher.doFinal(Base64.decodeBase64(data));
        return new String(decryptedByte);
    }

    private static Cipher initCipher(String key, String iv, int encryptMode) throws Exception {
        Cipher cipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
        SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(), "DES");
        IvParameterSpec ivSpec = new IvParameterSpec(iv.getBytes());
        cipher.init(encryptMode, keySpec, ivSpec);
        return cipher;
    }

    public static void main(String[] args) throws Exception {
        String key = "12345678";
        String iv = "87654321";
        String value = "HelloWorld";

        // 16进制的加密参数key,iv
        System.out.println("key hex: " + Hex.encodeHexString(key.getBytes()));
        System.out.println("iv hex: " + Hex.encodeHexString(iv.getBytes()));

        // 加密
        String encrypt = encrypt(key, iv, value);
        System.out.println("encrypt: " + encrypt);
        // 解密
        String decrypt = decrypt(key, iv, encrypt);
        System.out.println("decrypt: " + decrypt);
    }

}
key hex: 3132333435363738
iv hex: 3837363534333231
encrypt: 9r2UI+JnSrVP+WqsrFUqsg==
decrypt: HelloWorld

OpenSSL命令行调用:

[root@VM_0_10_centos ~]# echo -n HelloWorld > in.txt
[root@VM_0_10_centos ~]# openssl enc -des-cbc -in in.txt -out encrypt.txt -K 3132333435363738 -iv 3837363534333231 -a
[root@VM_0_10_centos ~]# cat encrypt.txt 
9r2UI+JnSrVP+WqsrFUqsg==
[root@VM_0_10_centos ~]# openssl enc -des-cbc -in encrypt.txt -out decrypt.txt -K 3132333435363738 -iv 3837363534333231 -a -d
[root@VM_0_10_centos ~]# cat decrypt.txt 
HelloWorld

AES-ECB

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.binary.Hex;

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;

public class AESECBUtils {

    public static String encrypt(String key, String data) throws Exception {
        Cipher cipher = initCipher(key, Cipher.ENCRYPT_MODE);
        byte[] encryptedByte = cipher.doFinal(data.getBytes());
        return Base64.encodeBase64String(encryptedByte);
    }

    public static String decrypt(String key, String data) throws Exception {
        Cipher cipher = initCipher(key, Cipher.DECRYPT_MODE);
        byte[] decryptedByte = cipher.doFinal(Base64.decodeBase64(data));
        return new String(decryptedByte);
    }

    private static Cipher initCipher(String key, int encryptMode) throws Exception {
        Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
        cipher.init(encryptMode, new SecretKeySpec(key.getBytes(), "AES"));
        return cipher;
    }

    public static void main(String[] args) throws Exception {
        // 16字节秘钥即128比特
        String key = "1234567890123456";
        String value = "HelloWorld";

        // 16进制的加密参数key
        System.out.println("key hex: " + Hex.encodeHexString(key.getBytes()));

        // 加密
        String encrypt = encrypt(key, value);
        System.out.println("encrypt: " + encrypt);
        // 解密
        String decrypt = decrypt(key, encrypt);
        System.out.println("decrypt: " + decrypt);
    }

}
key hex: 31323334353637383930313233343536
encrypt: VfqXvdGjzea0rgvXO56j5g==
decrypt: HelloWorld

OpenSSL命令行调用:

[root@VM_0_10_centos ~]# echo -n HelloWorld > in.txt
[root@VM_0_10_centos ~]# openssl enc -aes-128-ecb -in in.txt -out encrypt.txt -K 31323334353637383930313233343536 -a
[root@VM_0_10_centos ~]# cat encrypt.txt 
VfqXvdGjzea0rgvXO56j5g==
[root@VM_0_10_centos ~]# openssl enc -aes-128-ecb -in encrypt.txt -out decrypt.txt -K 31323334353637383930313233343536 -a -d
[root@VM_0_10_centos ~]# cat decrypt.txt 
HelloWorld

AES-CBC

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.binary.Hex;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

public class AESCBCUtils {

    public static String encrypt(String key, String iv, String data) throws Exception {
        Cipher cipher = initCipher(key, iv, Cipher.ENCRYPT_MODE);
        byte[] encryptedByte = cipher.doFinal(data.getBytes());
        return Base64.encodeBase64String(encryptedByte);
    }

    public static String decrypt(String key, String iv, String data) throws Exception {
        Cipher cipher = initCipher(key, iv, Cipher.DECRYPT_MODE);
        byte[] decryptedByte = cipher.doFinal(Base64.decodeBase64(data));
        return new String(decryptedByte);
    }

    private static Cipher initCipher(String key, String iv, int encryptMode) throws Exception {
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(), "AES");
        IvParameterSpec ivSpec = new IvParameterSpec(iv.getBytes());
        cipher.init(encryptMode, keySpec, ivSpec);
        return cipher;
    }

    public static void main(String[] args) throws Exception {
        // 16字节秘钥即128比特
        String key = "1234567890123456";
        String iv = "9876543210987654";
        String value = "HelloWorld";

        // 16进制的加密参数key,iv
        System.out.println("key hex: " + Hex.encodeHexString(key.getBytes()));
        System.out.println("iv hex: " + Hex.encodeHexString(iv.getBytes()));

        // 加密
        String encrypt = encrypt(key, iv, value);
        System.out.println("encrypt: " + encrypt);
        // 解密
        String decrypt = decrypt(key, iv, encrypt);
        System.out.println("decrypt: " + decrypt);
    }

}
key hex: 31323334353637383930313233343536
iv hex: 39383736353433323130393837363534
encrypt: 5febt2VH5eaKXn/nrQj/aQ==
decrypt: HelloWorld

OpenSSL命令行调用:

[root@VM_0_10_centos ~]# echo -n HelloWorld > in.txt
[root@VM_0_10_centos ~]# openssl enc -aes-128-cbc -in in.txt -out encrypt.txt -K 31323334353637383930313233343536 -iv 39383736353433323130393837363534 -a
[root@VM_0_10_centos ~]# cat encrypt.txt 
5febt2VH5eaKXn/nrQj/aQ==
[root@VM_0_10_centos ~]# openssl enc -aes-128-cbc -in encrypt.txt -out decrypt.txt -K 31323334353637383930313233343536 -iv 39383736353433323130393837363534 -a -d
[root@VM_0_10_centos ~]# cat decrypt.txt 
HelloWorld

AES-CTR

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.binary.Hex;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

public class AESCTRUtils {

    public static String encrypt(String key, String iv, String data) throws Exception {
        Cipher cipher = initCipher(key, iv, Cipher.ENCRYPT_MODE);
        byte[] encryptedByte = cipher.doFinal(data.getBytes());
        return Base64.encodeBase64String(encryptedByte);
    }

    public static String decrypt(String key, String iv, String data) throws Exception {
        Cipher cipher = initCipher(key, iv, Cipher.DECRYPT_MODE);
        byte[] decryptedByte = cipher.doFinal(Base64.decodeBase64(data));
        return new String(decryptedByte);
    }

    private static Cipher initCipher(String key, String iv, int encryptMode) throws Exception {
        Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");
        SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(), "AES");
        IvParameterSpec ivSpec = new IvParameterSpec(iv.getBytes());
        cipher.init(encryptMode, keySpec, ivSpec);
        return cipher;
    }

    public static void main(String[] args) throws Exception {
        // 16字节秘钥即128比特
        String key = "1234567890123456";
        String iv = "9876543210987654";
        String value = "HelloWorld";

        // 16进制的加密参数key,iv
        System.out.println("key hex: " + Hex.encodeHexString(key.getBytes()));
        System.out.println("iv hex: " + Hex.encodeHexString(iv.getBytes()));

        // 加密
        String encrypt = encrypt(key, iv, value);
        System.out.println("encrypt: " + encrypt);
        // 解密
        String decrypt = decrypt(key, iv, encrypt);
        System.out.println("decrypt: " + decrypt);
    }

}
key hex: 31323334353637383930313233343536
iv hex: 39383736353433323130393837363534
encrypt: Zjh8leeiqcWH0w==
decrypt: HelloWorld

OpenSSL命令行调用:

[root@VM_0_10_centos ~]# echo -n HelloWorld > in.txt
[root@VM_0_10_centos ~]# openssl enc -aes-128-ctr -in in.txt -out encrypt.txt -K 31323334353637383930313233343536 -iv 39383736353433323130393837363534 -a
[root@VM_0_10_centos ~]# cat encrypt.txt 
Zjh8leeiqcWH0w==
[root@VM_0_10_centos ~]# openssl enc -aes-128-ctr -in encrypt.txt -out decrypt.txt -K 31323334353637383930313233343536 -iv 39383736353433323130393837363534 -a -d
[root@VM_0_10_centos ~]# cat decrypt.txt 
HelloWorld

随机密钥

阅读一些java的加解密代码实现时可能会看到如下代码,这实际上是一个密码学的随机数生成,通过指定一个随机种子后生成一个随机数,最后以随机数作为对称加密算法的密钥。

因此随后参与对称加密运算的密钥并不是最开始的程序输入值,而是随机数生成器生成的随机数secretKey.getEncoded()

KeyGenerator generator = KeyGenerator.getInstance("AES");
SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");
secureRandom.setSeed("1234567890123456".getBytes());
generator.init(secureRandom);
SecretKey secretKey = generator.generateKey();
System.out.println(Hex.encodeHexString(secretKey.getEncoded()));

你可能感兴趣的:(Java)