简介
对称加密算法是应用较早的加密算法,技术成熟。在对称加密算法中,数据发信方将明文(原始数据)和加密密钥(mi
yao)一起经过特殊加密算法处理后,使其变成复杂的加密密文发送出去。收信方收到密文后,若想解读原文,则需要使用加密用过的密钥及相同算法的逆算法对密文进行解密,才能使其恢复成可读明文。在对称加密算法中,使用的密钥只有一个,发收信双方都使用这个密钥对数据进行加密和解密,这就要求解密方事先必须知道加密密钥。
特点
优点:
计算量小、加密速度快、加密效率高。
缺点:
算法是公开的,安全性得不到保证。
通信双方每次使用对称加密算法时,都需要使用其他人不知道的惟一密钥,这会使得通信双方所拥有的密钥数量呈几何级数增长,密钥管理成为用户的负担。对称加密算法在分布式网络系统上使用较为困难,主要是因为密钥管理困难,使用成本较高。
而与公钥、密钥加密算法比起来,对称加密算法能够提供加密和认证却缺乏了签名功能,使得使用范围有所缩小。
常用算法
对称加密算法主要有DES、3DES(TripleDES)、AES、IDEA、RC2、RC4、RC5和Blowfish等。
原理
对称加密要求加密与解密使用同一个密钥,解密是加密的逆运算。由于加密、解密使用同一个密钥,这要求通信双方必须在通信前商定该密钥,并妥善保存该密钥。
对称加密体制分为两种:
一种是对明文的单个位(或字节)进行运算,称为流密码,也称为序列密码;
一种是把明文信息划分为不同的组(或块)结构,分别对每个组(或块)进行加密、解密,称为分组密码。
假设甲乙方作为通信双方。假定甲乙双方在消息传递前已商定加密算法,欲完成一次消息传递需要经过如下步骤。
工作模式
以DES算法的工作模式为例,DES算法根据其加密算法所定义的明文分组的大小(56位),将数据分割成若干56位的加密区块,再以加密区块为单位,分别进行加密处理。如果最后剩下不足一个区块的大小,称之为短块。短块的处理方法有填充法、流密码加密法、密文挪用技术。
根据数据加密时每个加密区块见得关联方式来区分,可以分为以下种工作模式:
(1) 电子密码本模式(Electronic Code Book, ECB)
用途:适合加密密钥,随机数等短数据。例如,安全地传递DES密钥,ECB是最合适的模式。
(2) 密文链接模式(Cipher Booki Chaining, CBC)
用途:可加密任意长度的数据,适用于计算产生检测数据完整性的消息认证MAC。
(3) 密文反馈模式(Cipher Feed Back, CFB)
用途:因错误传播无界,可以用于检查发现明文密文的篡改。
(4) 输出反馈模式(Output Feed Back, OFB)
用途:使用于加密冗余性较大的数据,比如语音和图像数据。
AES算法除了以上4中模式外,还有一种新的工作模式:
(5) 计数器模式(Counter, CTR)
用途:适用于各种加密应用。
本文对于各种工作模式的原理展开描述。个人认为,作为工程应用,了解其用途即可。
填充方法
Java中对称加密对于短块的处理,一般是采用填充方式。
常采用的是:NoPadding(不填充)、Zeros填充(0填充)、PKCS5Padding填充。
ZerosPadding
方式:全部填充为0的字节
结果如下:
F1 F2 F3 F4 F5 F6 F7 F8 //第一块
F9 00 00 00 00 00 00 00 //第二块
PKCS5Padding
方式:每个填充的字节都记录了填充的总字节数
结果如下:
F1 F2 F3 F4 F5 F6 F7 F8 //第一块
F9 07 07 07 07 07 07 07 //第二块
术语
明文(Plaintext):指待加密信息。明文可以是文本文件、图片文件、二进制数据等。
密文(Ciphertext):指经过加密后的明文。密文通常以文本、二进制等形式存在。
加密(Encryption):指将明文转换为密文的过程。
解密(Decryption):指将密文转换为明文的过程。
加密密钥(Encryption Key):指通过加密算法进行加密操作用的密钥。
解密密钥(Decryption Key):指通过解密算法进行解密操作用的密钥。
信道(Channel):通信的通道,是信号传输的媒介。
Java对于对称加密的支持
基于密钥加密的流程(DES、DESede、AES和IDEA)
DES、DESede、AES和IDEA等算法都是基于密钥加密的对称加密算法,它们的实现流程也基本一致。步骤如下:
(1)生成密钥
KeyGenerator kg =
KeyGenerator.getInstance("DES");
SecureRandom random = new SecureRandom();
kg.init(random);
SecretKey secretKey = kg.generateKey();
建议使用随机数来初始化密钥的生成。
(2)初始化密码对象
Cipher cipher =
Cipher.getInstance("DES/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
ENCRYPT_MODE:加密模式
DECRYPT_MODE:解密模式
(3)执行
String plaintext = "Hello World";
byte[] ciphertext =
cipher.doFinal(plaintext.getBytes());
完整实例
一个完整的DES加密解密范例
1 import org.bouncycastle.util.encoders.Base64;
2
3 import javax.crypto.BadPaddingException;
4 import javax.crypto.Cipher;
5 import javax.crypto.IllegalBlockSizeException;
6 import javax.crypto.KeyGenerator;
7 import javax.crypto.NoSuchPaddingException;
8 import javax.crypto.spec.IvParameterSpec;
9 import java.security.InvalidAlgorithmParameterException;
10 import java.security.InvalidKeyException;
11 import java.security.Key;
12 import java.security.NoSuchAlgorithmException;
13 import java.security.NoSuchProviderException;
14 import java.security.SecureRandom;
15
16 /**
17 * @Title DESCoder
18 * @Description DES安全编码:是经典的对称加密算法。密钥仅56位,且迭代次数偏少。已被视为并不安全的加密算法。
19 * @Author zhangpeng0913
20 * @Date 2016年7月14日
21 */
22 public class DESCoder {
23 public static final String KEY_ALGORITHM_DES = "DES";
24 public static final String CIPHER_DES_DEFAULT = "DES";
25 public static final String CIPHER_DES_ECB_PKCS5PADDING = "DES/ECB/PKCS5Padding"; // 算法/模式/补码方式
26 public static final String CIPHER_DES_CBC_PKCS5PADDING = "DES/CBC/PKCS5Padding";
27 public static final String CIPHER_DES_CBC_NOPADDING = "DES/CBC/NoPadding";
28 private static final String SEED = "%%%today is nice***"; // 用于生成随机数的种子
29
30 private Key key;
31 private Cipher cipher;
32 private String transformation;
33
34 public DESCoder() throws NoSuchAlgorithmException, NoSuchPaddingException, NoSuchProviderException {
35 this.key = initKey();
36 this.cipher = Cipher.getInstance(CIPHER_DES_DEFAULT);
37 this.transformation = CIPHER_DES_DEFAULT;
38 }
39
40 public DESCoder(String transformation)
41 throws NoSuchAlgorithmException, NoSuchPaddingException, NoSuchProviderException {
42 this.key = initKey();
43 this.cipher = Cipher.getInstance(transformation);
44 this.transformation = transformation;
45 }
46
47 /**
48 * @Title decrypt
49 * @Description 解密
50 * @Author zhangpeng0913
51 * @Date 2016年7月20日
52 * @param input 密文
53 * @return byte[] 明文
54 * @throws InvalidKeyException
55 * @throws IllegalBlockSizeException
56 * @throws BadPaddingException
57 * @throws InvalidAlgorithmParameterException
58 */
59 public byte[] decrypt(byte[] input) throws InvalidKeyException, IllegalBlockSizeException, BadPaddingException,
60 InvalidAlgorithmParameterException {
61 if (transformation.equals(CIPHER_DES_CBC_PKCS5PADDING) || transformation.equals(CIPHER_DES_CBC_NOPADDING)) {
62 cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(getIV()));
63 } else {
64 cipher.init(Cipher.DECRYPT_MODE, key);
65 }
66 return cipher.doFinal(input);
67 }
68
69 /**
70 * @Title encrypt
71 * @Description 加密
72 * @Author zhangpeng0913
73 * @Date 2016年7月20日
74 * @param input 明文
75 * @return byte[] 密文
76 * @throws InvalidKeyException
77 * @throws IllegalBlockSizeException
78 * @throws BadPaddingException
79 * @throws InvalidAlgorithmParameterException
80 */
81 public byte[] encrypt(byte[] input) throws InvalidKeyException, IllegalBlockSizeException, BadPaddingException,
82 InvalidAlgorithmParameterException {
83 if (transformation.equals(CIPHER_DES_CBC_PKCS5PADDING) || transformation.equals(CIPHER_DES_CBC_NOPADDING)) {
84 cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(getIV()));
85 } else {
86 cipher.init(Cipher.ENCRYPT_MODE, key);
87 }
88 return cipher.doFinal(input);
89 }
90
91 /**
92 * @Title initKey
93 * @Description 根据随机数种子生成一个密钥
94 * @Author zhangpeng0913
95 * @Date 2016年7月14日
96 * @Return Key
97 * @throws NoSuchAlgorithmException
98 * @throws NoSuchProviderException
99 */
100 private Key initKey() throws NoSuchAlgorithmException, NoSuchProviderException {
101 // 根据种子生成一个安全的随机数
102 SecureRandom secureRandom = null;
103 secureRandom = new SecureRandom(SEED.getBytes());
104
105 KeyGenerator keyGen = KeyGenerator.getInstance(KEY_ALGORITHM_DES);
106 keyGen.init(secureRandom);
107 return keyGen.generateKey();
108 }
109
110 private byte[] getIV() {
111 String iv = "01234567"; // IV length: must be 8 bytes long
112 return iv.getBytes();
113 }
114
115 public static void main(String[] args) throws Exception {
116 DESCoder aes = new DESCoder(CIPHER_DES_CBC_PKCS5PADDING);
117
118 String msg = "Hello World!";
119 System.out.println("[DES加密、解密]");
120 System.out.println("message: " + msg);
121 byte[] encoded = aes.encrypt(msg.getBytes("UTF8"));
122 String encodedBase64 = Base64.toBase64String(encoded);
123 System.out.println("encoded: " + encodedBase64);
124
125 byte[] decodedBase64 = Base64.decode(encodedBase64);
126 byte[] decoded = aes.decrypt(decodedBase64);
127 System.out.println("decoded: " + new String(decoded));
128 }
129 }
基于口令加密的流程(PBE)
DES、DESede、AES、IDEA这几种算法的应用模型几乎如出一辙。
但是,并非所有对称加密算法都是如此。
基于口令加密(Password Based Encryption,
PBE)是一种基于口令加密的算法。其特点是:口令由用户自己掌管,采用随机数(这里叫做盐)杂凑多重加密等方法保证数据的安全性。
PBE没有密钥概念,密钥在其他对称加密算法中是经过计算得出的,PBE则使用口令替代了密钥。
流程:
步骤如下:
(1) 产生盐
SecureRandom
secureRandom = new SecureRandom();
byte[] salt =
secureRandom.generateSeed(8); // 盐长度必须为8字节
(2) 根据密码产生Key
String password = "123456";
PBEKeySpec keySpec
= new PBEKeySpec(password.toCharArray());
SecretKeyFactory
keyFactory = SecretKeyFactory.getInstance(KEY_ALGORITHM);
SecretKey secretKey
= keyFactory.generateSecret(keySpec);
(3) 初始化加密或解密对象
PBEParameterSpec
paramSpec = new PBEParameterSpec(salt, ITERATION_COUNT);
Cipher cipher =
Cipher.getInstance(KEY_ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, secretKey, paramSpec);
(4) 执行
byte[] plaintext = "Hello World".getBytes();
byte[] ciphertext =
cipher.doFinal(plaintext);