上一篇文章回顾了对称加密的加解密模式、填充模式、向量
iv
,这一篇用代码实现一个通用的对称加密工具类。我们按照安全密钥生成、iv
向量生成、加解密模式配置、落地加解密的先后顺序一步一步的实现这个工具类。
一、安全密钥生成
Java
生成安全密钥有两种方法,一个是从随机的数字序列生成,或者是从用户设置的密钥中生成一个密钥来进行密码的保护。在Java
的类库中,有一个类是叫做SecureRandom
,它提供了一个加密的强随机数生成器。我们还可以使用keyGenerator
类去生成密钥。
/** * 获取加密密钥 * * @param symmetricMode 对称加密模式 * @param lenOfSymmetricKey 密钥长度 * @param password 指定的种子,用于生成密钥 * @return 加密密钥 */ @SneakyThrows static SecretKey symmetricKey(@MagicConstant(valuesFromClass = SymmetricMode.class) String symmetricMode, int lenOfSymmetricKey, @Nullable String password) { final SecureRandom secureRandom = (password == null || password.trim().isEmpty()) ? new SecureRandom() : new SecureRandom(password.getBytes(StandardCharsets.UTF_8)); final KeyGenerator keyGenerator = KeyGenerator.getInstance(symmetricMode); keyGenerator.init(lenOfSymmetricKey, secureRandom); return keyGenerator.generateKey(); }
这个是KeyGenerator
结合SecureRandom
使用指定的password去生成指定的密钥。SecureRandom
和Random
类似,如果种子一样,产生的随机数也一样。
/** * 获取加密密钥 * * @param symmetricMode 对称加密模式 * @param lenOfSymmetricKey 密钥长度 * @return 加密密钥 */ @SneakyThrows static SecretKey symmetricKey(@MagicConstant(valuesFromClass = SymmetricMode.class) String symmetricMode, int lenOfSymmetricKey) { final KeyGenerator keyGenerator = KeyGenerator.getInstance(symmetricMode); keyGenerator.init(lenOfSymmetricKey); return keyGenerator.generateKey(); }
这个是只使用KeyGenerator
去生成密钥SecretKey
,产生的密钥是随机的。
SecureRandom
和KeyGenerator
还有很多有意思的用法,请移步Java
官方文档。
二、iv向量生成
IV 初始向量是另一种用于加密的伪随机值。它必须与要加密的数据块的大小相同。在同一个类中,SecureRandom
用于生成一个随机的 IV 数。
/** * 获取初始向量 * * @return 初始向量 */ static byte[] initializationVector(int length) { final byte[] initializationVector = new byte[length]; final SecureRandom secureRandom = new SecureRandom(); secureRandom.nextBytes(initializationVector); return initializationVector; }
这个只是用SecureRandom
生成指定长度的随机数,以产生一个初始化向量。
三、加解密模式配置
对于DES
,AES
有很多的加解密模式,需要有一个地方统一管理起来。
配置SymmetricMode
管理所有的对称加密类型。
``` /** * 对称加密类型 */ interface SymmetricMode {
String AES = "AES";
String DES = "DES";
List<String> SymmetricModes = Arrays.asList(
AES,
DES
//TODO add Modes
);
static boolean contains(String mode) {
return SymmetricModes.contains(mode);
}
}
```
配置CipherMode
管理具体的加解密模式。
``` /** * 加密的具体模式 */ interface CipherMode {
String AES_ECB_PKCS5PADDING = "AES/ECB/PKCS5Padding";
String AES_CBC_PKCS5PADDING = "AES/CBC/PKCS5Padding";
String DES_CBC_PKCS5PADDING = "DES/CBC/PKCS5Padding";
/**
* CipherModes
*/
List<String> CipherModes = Arrays.asList(
AES_ECB_PKCS5PADDING,
AES_CBC_PKCS5PADDING,
DES_CBC_PKCS5PADDING
//TODO add Modes
);
/**
* CipherModesWithVector
*/
List<String> CipherModesWithVector = Arrays.asList(
AES_CBC_PKCS5PADDING,
DES_CBC_PKCS5PADDING
);
static String parseSymmetricMode(String mode) {
if (mode.contains(SymmetricMode.AES)) {
return SymmetricMode.AES;
} else if (mode.contains(SymmetricMode.DES)) {
return SymmetricMode.DES;
} else {
throw new IllegalArgumentException("unknown mode");
}
}
static boolean contains(String mode) {
return CipherModes.contains(mode);
}
static boolean hasVector(String mode) {
return CipherModesWithVector.contains(mode);
}
}
```
加解密,填充模式有很多组合,不一一列举了。
四、实现加解密
加密分为带向量和不带向量的加密,代码如下。
``` /** * 对称加密,含有偏移量(expose) * * @param inputData 待加密的文本 * @param cipherMode 具体加密模式 * @param secretKey 密钥,格式为HexString * @param iv 偏移量,格式为HexString * @return 加密后的数据 */ static byte[] encryptionWithIv(String inputData, @NotNull String cipherMode, @NotNull String secretKey, @NotNull String iv) { final byte[] bytes = inputData.getBytes(StandardCharsets.UTF_8); final String symmetricMode = CipherMode.parseSymmetricMode(cipherMode); final SecretKeySpec key = generateKey(ArraysStringConverter.HexStringToByteArray(secretKey), symmetricMode); return doEncryption(bytes, cipherMode, key, ArraysStringConverter.HexStringToByteArray(iv)); }
/**
* 对称加密(expose)
*
* @param inputData 待加密的文本
* @param cipherMode 具体加密模式
* @param secretKey 密钥,格式为HexString
* @return 加密后的数据
*/
static byte[] encryption(String inputData,
@NotNull String cipherMode,
@NotNull String secretKey) {
final byte[] bytes = inputData.getBytes(StandardCharsets.UTF_8);
final String symmetricMode = CipherMode.parseSymmetricMode(cipherMode);
final SecretKeySpec key = generateKey(ArraysStringConverter.HexStringToByteArray(secretKey), symmetricMode);
return doEncryption(bytes, cipherMode, key, null);
}
@SneakyThrows
static byte[] doEncryption(byte[] inputData,
@NotNull String cipherMode,
@NotNull SecretKey secretKey,
byte[] vector) {
if (!CipherMode.contains(cipherMode) || inputData == null) {
throw new IllegalArgumentException();
}
final Cipher cipher = Cipher.getInstance(cipherMode);
if (CipherMode.hasVector(cipherMode)) {
final IvParameterSpec ivParameterSpec = new IvParameterSpec(vector);
cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivParameterSpec);
} else {
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
}
return cipher.doFinal(inputData);
}
```
哈哈,当然解密也分带向量和不带向量,代码如下。
``` /** * 解密,含有偏移量(expose) * * @param inputData 待加密的文本 数据格式为HexString * @param cipherMode 具体加密模式 * @param secretKey 密钥,格式为HexString * @return 解密后的数据 */ @SneakyThrows static String decryption(String inputData, @NotNull String cipherMode, @NotNull String secretKey) { final byte[] bytes = ArraysStringConverter.HexStringToByteArray(inputData); final String symmetricMode = CipherMode.parseSymmetricMode(cipherMode); final SecretKeySpec key = generateKey(ArraysStringConverter.HexStringToByteArray(secretKey), symmetricMode); return new String(doDecryption(bytes, cipherMode, key, null)); }
/**
* 解密,含有偏移量(expose)
*
* @param inputData 待加密的文本 数据格式为HexString
* @param cipherMode 具体加密模式
* @param secretKey 密钥,格式为HexString
* @param iv 偏移量,格式为HexString
* @return 解密后的数据
*/
@SneakyThrows
static String decryptionWithIv(String inputData,
@NotNull String cipherMode,
@NotNull String secretKey,
@NotNull String iv) {
final byte[] bytes = ArraysStringConverter.HexStringToByteArray(inputData);
final String symmetricMode = CipherMode.parseSymmetricMode(cipherMode);
final SecretKeySpec key = generateKey(ArraysStringConverter.HexStringToByteArray(secretKey), symmetricMode);
return new String(doDecryption(bytes, cipherMode, key, ArraysStringConverter.HexStringToByteArray(iv)));
}
@SneakyThrows
static byte[] doDecryption(byte[] inputData,
@NotNull String cipherMode,
@NotNull SecretKey secretKey,
byte[] vector) {
if (!CipherMode.contains(cipherMode)) {
throw new IllegalArgumentException();
}
final Cipher cipher = Cipher.getInstance(cipherMode);
if (CipherMode.hasVector(cipherMode)) {
final IvParameterSpec ivParameterSpec = new IvParameterSpec(vector);
cipher.init(Cipher.DECRYPT_MODE, secretKey, ivParameterSpec);
} else {
cipher.init(Cipher.DECRYPT_MODE, secretKey);
}
return cipher.doFinal(inputData);
}
```
加密的输出和解密的输入均为Hex String
,纯属个人偏好。当然也可以采取Base64
进行处理。测试过程见源码。
五、总结
附上源码地址training.java.crypto.encryption.symmetric,请不吝赐教。最后说点,对称加密请优先选择AES
,这是历史必然的选择。