术 | 对称加密的实现(二)

1,042 阅读4分钟

这是我参与更文挑战的第17天,活动详情查看: 更文挑战

上一篇文章回顾了对称加密的加解密模式、填充模式、向量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,这是历史必然的选择。