项目中经常会对数据库的密码加密后放到配置文件中,启动项目连接数据库时再对其进行解密,此加密与解密过程一般采用对称加密算法。本篇主要介绍什么是对称加密算法及如何用java实现,之后一篇说明项目中的实际应用
对称密码体制的保密通信模型如下图所示,解密是加密的逆运算。通信双方共享同一个密钥,该密钥称为秘密密钥。
对称密码根据加密方式分为两种:一种是对明文的单个位(或字节)进行运算,称为流密码(这里不做介绍),另一种是把明文信息划分成不同的组结构,分别对每个组进行加密与解密,称为分组密码(重点)。
分组密码又有不同的工作模式,如ECB模式,CBC模式,CFB模式等。
目前已知通过java语言实现的对称密码算法有20多种,如常用的DES,DESede,AES,Blowfish等算法。其他算法(如IDEA算法)需要通过第三方机密软件包Bouncy Castle算法提供实现。
DES算法密钥偏短,仅56位,迭代次数偏少,已不安全。笔者在实际项目中使用最多的AES算法
JDK提供了多种算法支持,但并不完善,许多加密强度较高的算法,JDK未能提供,我们可以引入Bouncy Castle
<dependency>
<groupId>org.bouncycastlegroupId>
<artifactId>bcprov-jdk15onartifactId>
<version>1.67version>
dependency>
Commons Codec对JDK原生api进行了良好的封装,提高了方法的易用性
<dependency>
<groupId>commons-codecgroupId>
<artifactId>commons-codecartifactId>
<version>1.14version>
dependency>
密钥长度与安全性成正比,jdk仅支持56位密钥长度,作为补充Bouncy Castle提供了64位密钥长度支持
算法 | 密钥长度 | 长度默认值 | 工作模式 | 填充方式 | 备注 |
---|---|---|---|---|---|
DES | 56 | 56 | ECB、CBC、PCBC、CTR等 | NoPadding PKCS5Pading ISO10126Padding |
java8实现 |
DES | 64 | 56 | 同上 | 同上 PKCS7Pading等 |
Bouncy Castle实现 |
public class DESUtil {
public static void main(String[] args) throws Exception {
byte[] plainTxt = "helloWorld".getBytes(StandardCharsets.UTF_8);
byte[] key = initKey();
System.out.println("key:" + Base64.getEncoder().encodeToString(key));
// Wrong IV length: must be 8 bytes long
byte[] salt = secureRandom(8);
byte[] encrypt = encrypt(plainTxt, salt, key);
System.out.println("after encrypt:" + Base64.getEncoder().encodeToString(encrypt));
byte[] decrypt = decrypt(encrypt, salt, key);
System.out.println("after decrypt:" + new String(decrypt, StandardCharsets.UTF_8));
}
/**
* 密钥算法
*/
private static final String KEY_ALGORITHM = "DES";
/**
* 加密/解密
*
* 算法/工作模式/填充方式
* ECB mode cannot use IV(注意有些工作模式不支持IV)
* Wrong IV length: must be 8 bytes long
*/
private static final String CIPHER_ALGORITHM = "DES/CBC/PKCS5Padding";
private static Key toKey(byte[] key) throws Exception {
DESKeySpec spec = new DESKeySpec(key);
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(KEY_ALGORITHM);
return keyFactory.generateSecret(spec);
}
/**
* 加密
* @param data 明文
* @param salt IV
* @param key 密钥材料
* @return 密文
* @throws Exception Exception
*/
private static byte[] encrypt(byte[] data, byte[] salt, byte[] key) throws Exception {
Key k = toKey(key);
// Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM, new BouncyCastleProvider());
cipher.init(Cipher.ENCRYPT_MODE, k, new IvParameterSpec(salt));
return cipher.doFinal(data);
}
/**
* 解密
* @param data 密文
* @param salt iv(解密跟加密操作需使用同一个iv)
* @param key 密钥
* @return byte[] 明文
* @throws Exception Exception
*/
private static byte[] decrypt(byte[] data, byte[] salt, byte[] key) throws Exception {
Key k = toKey(key);
Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM, new BouncyCastleProvider());
// Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, k, new IvParameterSpec(salt));
return cipher.doFinal(data);
}
/**
* 生成密钥
* Java只支持56位密钥
* Bouncy Castle支持64位密钥
*
* @return byte[] 二进制密钥
* @throws Exception Exception
*/
private static byte[] initKey() throws Exception {
// new BouncyCastleProvider() 表示采用Bouncy Castle实现
KeyGenerator kg = KeyGenerator.getInstance(KEY_ALGORITHM, new BouncyCastleProvider());
// KeyGenerator kg = KeyGenerator.getInstance(KEY_ALGORITHM);
/**
* Wrong keysize: must be equal to 56
* sun的长度只能是56
* bouncycastle的长度可以是64
*/
// kg.init(56);
kg.init(64);
SecretKey secretKey = kg.generateKey();
return secretKey.getEncoded();
}
/**
* 获取指定长度IV值
*
* @param len 长度
* @return byte[]
* @throws NoSuchAlgorithmException
*/
private static byte[] secureRandom(int len) throws NoSuchAlgorithmException {
SecureRandom random = SecureRandom.getInstanceStrong();
byte[] bytes = new byte[len];
random.nextBytes(bytes);
return bytes;
}
}
AES算法成为DES算法的替代者,其实现称为其他对称加密算法实现的参考模型。以下为实现细节
算法 | 密钥长度 | 长度默认值 | 工作模式 | 填充方式 | 备注 |
---|---|---|---|---|---|
AES | 128、192、256 | 128 | ECB、CBC、PCBC、CTR、CTS、CFB等 | NoPadding PKCS5Pading ISO10126Padding |
java8实现 |
AES | 同上 | 同上 | 同上 | 同上 PKCS7Padding ZeroBytePadding |
Bouncy Castle实现 |
public class AESUtil {
public static void main(String[] args) throws Exception {
byte[] plainTxt = "helloWorld".getBytes(StandardCharsets.UTF_8);
byte[] key = initKey();
System.out.println("key:" + Base64.getEncoder().encodeToString(key));
// Wrong IV length: must be 16 bytes long
byte[] salt = secureRandom(16);
byte[] encrypt = encrypt(plainTxt, salt, key);
System.out.println("after encrypt:" + Base64.getEncoder().encodeToString(encrypt));
byte[] decrypt = decrypt(encrypt, salt, key);
System.out.println("after decrypt:" + new String(decrypt, StandardCharsets.UTF_8));
}
/**
* 密钥算法
*/
private static final String KEY_ALGORITHM = "AES";
/**
* 加密/解密
*
* 算法/工作模式/填充方式
* ECB mode cannot use IV
*/
private static final String CIPHER_ALGORITHM = "AES/CBC/PKCS7Padding";
private static Key toKey(byte[] key) throws Exception {
SecretKeySpec keySpec = new SecretKeySpec(key, KEY_ALGORITHM);
return keySpec;
}
private static byte[] encrypt(byte[] data, byte[] salt, byte[] key) throws Exception {
Key k = toKey(key);
Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM,new BouncyCastleProvider());
// Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, k, new IvParameterSpec(salt));
return cipher.doFinal(data);
}
private static byte[] decrypt(byte[] data, byte[] salt, byte[] key) throws Exception {
Key k = toKey(key);
Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM,new BouncyCastleProvider());
// Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, k, new IvParameterSpec(salt));
return cipher.doFinal(data);
}
private static byte[] initKey() throws NoSuchAlgorithmException {
KeyGenerator kg = KeyGenerator.getInstance(KEY_ALGORITHM);
/**
* Wrong keysize: must be equal to 128, 192 or 256
* 128 或者192 或256
*/
kg.init(128);
SecretKey secretKey = kg.generateKey();
return secretKey.getEncoded();
}
private static byte[] secureRandom(int len) throws NoSuchAlgorithmException {
SecureRandom random = SecureRandom.getInstanceStrong();
byte[] bytes = new byte[len];
random.nextBytes(bytes);
return bytes;
}
}
分组密码对固定长度的一组明文进行加密,这一固定长度称为分组长度。分组长度与安全性成正比。
DES算法的分组长度是56位。但56位的分组长度已经不安全了,目前分组密码多选择128位作为算法的分组长度,如AES算法的分组长度就是128位。
DES算法根据其加密算法所定义的明文分组的大小(56位),将数据分隔成若干56位的加密区块,再以加密区块为单位,分别进行加密处理。如果最后剩下不足一个区块的大小,称之为短块,短块的处理方法有填充法,流密码加密法等
根据数据加密时每个加密区块之间的关联方式,可以分为以下4种工作模式
AES算法在DES算法工作模式的基础上,还推荐了一种新的工作模式——计数器模式(CTR)
下面以项目中用到的CBC模式为例,画出其流程图
DES算法已不具备安全性,建议选择AES算法。笔者在项目中使用的对称加密算法为AES/CBC/PKCS5Padding,密钥长度为128和256位两种。AES算法的分组长度固定为128位,其强度由密钥长度决定。