上一篇文章回顾了对称加密的加解密模式、填充模式、向量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去生成指定的密钥。SecureRandomRandom类似,如果种子一样,产生的随机数也一样。

 /** * 获取加密密钥 * * @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,产生的密钥是随机的。

SecureRandomKeyGenerator还有很多有意思的用法,请移步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生成指定长度的随机数,以产生一个初始化向量。

三、加解密模式配置

对于DESAES有很多的加解密模式,需要有一个地方统一管理起来。

配置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,这是历史必然的选择。