加密算法
所谓对称加密算法,通过密钥将明文加密成密文,并且再通过同一个密钥将密文解密成明文,相对于非对称加密算法速度快效率高,对于明文文本越长效率优势越大。
常见的对称加密算法有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是最简单的一种分组模式,简单地将明文拆成数据块后,每个数据块单独加密解密。如果两个数据块内容相同,那么这两个数据块加密后的密文段也是完全一样,容易受到重放攻击以及密文篡改,因此安全度不高。


CBC
CBC引入了一个新的变量初始向量IV,一般是一个随机数,用于在第一块明文数据块加密前对数据块做XOR运算,之后每一块明文数据块加密前都与前一块的密文数据块做XOR运算。
由于每一块数据块的计算结果都与上一块数据块有关联,因此即使有两个相同的数据块,计算出的密文段也不同,解决了ECB的安全问题。但因此,CBC计算只能串行处理,效率不如ECB。


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


填充算法
对于块加密算法来说,不是对一次性对整个明文进行加密计算,而是拆分成若干个固定长度(加密算法的分组长度)的数据块,然后对各个数据块进行加密计算,因此明文的字节长度必须是分组长度的整数倍数。如果明文的字节长度不是分组长度的整数倍数,则需要一种填充的机制,在加密前将明文字节长度填充成分组长度的整数倍数,并且在解密后正确移除填充的字节。
常见的填充算法有PKCS#7和PKCS#5,而实际上两者的处理逻辑是相同的,只是PKCS#5只能处理分组长度为8字节的数据,而PKCS#7可以处理分组长度是1-255任意长度字节的数据。从这个角度看,PKCS#5是PKCS#7的子集。
PKCS#7很简单,实现代码如下:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 
 | 
 
 
 
 public static byte[] padding7(byte[] src, int cipherBlockSize) {
 
 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;
 }
 
 
 
 
 
 
 public static byte[] padding5(byte[] src, int cipherBlockSize) {
 
 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。另外,如果数据长度刚好是分组长度的整数倍时,依然还需要填充,填充长度为分组长度。
| 12
 3
 4
 
 | 0x010x02 0x02
 0x03 0x03 0x03
 0x04 0x04 0x04 0x04
 
 | 
| 12
 3
 
 | Hello0x48 0x65 0x6c 0x6c 0x6f
 0x48 0x65 0x6c 0x6c 0x6f 0x03 0x03 0x03
 
 | 
- 分组长度8字节时数据8字节,本应不需要填充,但实际上再额外填充8字节:
| 12
 3
 
 | EchoEcho0x45 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
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 
 | 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";
 
 
 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);
 }
 
 }
 
 | 
| 12
 3
 
 | key hex: 3132333435363738encrypt: 8SBfruuCs9qh0WiTqhkSbg==
 decrypt: HelloWorld
 
 | 
OpenSSL命令行调用:
| 12
 3
 4
 5
 6
 7
 
 | [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
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 
 | 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";
 
 
 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);
 }
 
 }
 
 | 
| 12
 3
 4
 
 | key hex: 3132333435363738iv hex: 3837363534333231
 encrypt: 9r2UI+JnSrVP+WqsrFUqsg==
 decrypt: HelloWorld
 
 | 
OpenSSL命令行调用:
| 12
 3
 4
 5
 6
 7
 
 | [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
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 
 | 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 {
 
 String key = "1234567890123456";
 String value = "HelloWorld";
 
 
 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);
 }
 
 }
 
 | 
| 12
 3
 
 | key hex: 31323334353637383930313233343536encrypt: VfqXvdGjzea0rgvXO56j5g==
 decrypt: HelloWorld
 
 | 
OpenSSL命令行调用:
| 12
 3
 4
 5
 6
 7
 
 | [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
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 
 | 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 {
 
 String key = "1234567890123456";
 String iv = "9876543210987654";
 String value = "HelloWorld";
 
 
 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);
 }
 
 }
 
 | 
| 12
 3
 4
 
 | key hex: 31323334353637383930313233343536iv hex: 39383736353433323130393837363534
 encrypt: 5febt2VH5eaKXn/nrQj/aQ==
 decrypt: HelloWorld
 
 | 
OpenSSL命令行调用:
| 12
 3
 4
 5
 6
 7
 
 | [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
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 
 | 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 {
 
 String key = "1234567890123456";
 String iv = "9876543210987654";
 String value = "HelloWorld";
 
 
 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);
 }
 
 }
 
 | 
| 12
 3
 4
 
 | key hex: 31323334353637383930313233343536iv hex: 39383736353433323130393837363534
 encrypt: Zjh8leeiqcWH0w==
 decrypt: HelloWorld
 
 | 
OpenSSL命令行调用:
| 12
 3
 4
 5
 6
 7
 
 | [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()。
| 12
 3
 4
 5
 6
 
 | 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()));
 
 |