解决java.security.InvalidKeyException: Illegal key size or default parameters

前言

前面说到DES对称加解密算法的安全系数不是很高,企业项目中一般不会使用的。所以衍生了后续的3DES和AES算法,和DES一样,这两者都是对称加密算法。但是安全性比DES高很多,最近项目中集成的第三方兑换券就是用AES加密算法的,这里来和大家一起学习下AES加密算法。

AES加密算法Demo

直接上代码

import org.apache.tomcat.util.codec.binary.Base64;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * @author 蒋墨风
 * @title: AesTest
 * @projectName study
 * @description: AES加解密demo
 * @date 2019/12/15 21:19
 */
public class AesTest {


    /**
     * @version V1.0
     * @desc AES 加密工具类
     */
    private static final String KEY_ALGORITHM = "AES";
    private static final String DEFAULT_CIPHER_ALGORITHM = "AES/ECB/PKCS5Padding";//默认的加密算法

    /**
     * AES 加密操作
     *
     * @param content  待加密内容
     * @param password 加密密码
     * @return 返回Base64转码后的加密数据
     */
    public static String encrypt(String content, String password) {

        try {
            Cipher cipher = Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM);// 创建密码器
            byte[] byteContent = content.getBytes("utf-8");
            cipher.init(Cipher.ENCRYPT_MODE, getSecretKey(password));// 初始化为加密模式的密码器
            byte[] result = cipher.doFinal(byteContent);// 加密
            return Base64.encodeBase64String(result);//通过Base64转码返回
        } catch (Exception ex) {
            Logger.getLogger(AesTest.class.getName()).log(Level.SEVERE, null, ex);
        }
        return null;

    }

    /**
     * AES 解密操作
     *
     * @param content
     * @param password
     * @return
     */
    public static String decrypt(String content, String password) {
        try {
            //实例化
            Cipher cipher = Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM);
            //使用密钥初始化,设置为解密模式
            cipher.init(Cipher.DECRYPT_MODE, getSecretKey(password));
            //执行操作
            byte[] result = cipher.doFinal(Base64.decodeBase64(content));
            return new String(result, "utf-8");
        } catch (Exception ex) {
            Logger.getLogger(AesTest.class.getName()).log(Level.SEVERE, null, ex);
        }
        return null;

    }

    /**
     * 生成加密秘钥
     *
     * @return
     */
    private static SecretKeySpec getSecretKey(final String password) {
        //返回生成指定算法密钥生成器的 KeyGenerator 对象
        KeyGenerator kg = null;
        try {
            kg = KeyGenerator.getInstance(KEY_ALGORITHM);
            //AES 要求密钥长度为 128
            kg.init(128, new SecureRandom(password.getBytes()));
            //生成一个密钥
            SecretKey secretKey = kg.generateKey();
            return new SecretKeySpec(secretKey.getEncoded(), KEY_ALGORITHM);// 转换为AES专用密钥
        } catch (NoSuchAlgorithmException ex) {
            Logger.getLogger(AesTest.class.getName()).log(Level.SEVERE, null, ex);
        }
        return null;
    }


    public static void main(String[] args) {
        String s = "爱琴孩的博客";
        System.out.println("原始明文:" + s);
        String s1 = AesTest.encrypt(s, "1236541");
        System.out.println("加密之后的密文:" + s1);
        System.out.println("解密之后的明文:" + AesTest.decrypt(s1, "123654"));
    }
}

测试效果如下

密钥长度超过128

上面可以看到,我们代码密钥长度是128。如果想提高密钥长度为256,那么代码会报错的

kg.init(256, new SecureRandom(password.getBytes()));

报错信息如下

替换JCE类库

在Java的核心类库中有一个JCE(Java Cryptography Extension),JCE是一组包,它们提供用于加密、密钥生成和协商以及 Message Authentication Code(MAC)算法的框架和实现,所以这个是实现加密解密的重要类库。在我们安装的JRE目录下有这样一个文件夹:%JAVE_HOME%\jre\lib\security,其中包含有两个.jar文件:“local_policy.jar ”和“US_export_policy.jar”,也就是我们平时说的jar包,这两个jar包就是我们JCE中的核心类库了。JRE中自带的“local_policy.jar ”和“US_export_policy.jar”是支持128位密钥的加密算法,而当我们要使用256位密钥算法的时候,已经超出它的范围,无法支持,所以才会报上面的错误。

解决方案:去官方下载JCE无限制权限策略文件。下载后解压,可以看到local_policy.jar和US_export_policy.jar以及readme.txt。如果安装了JRE,将两个jar文件放到%JRE_HOME%\lib\security目录下覆盖原来的文件。如果安装了JDK,还要将两个jar文件也放到%JDK_HOME%\jre\lib\security目录下覆盖原来文件。目前项目用是jdk8,下面附上下载地址
JDK8的下载地址: http://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html

通过反射修改属性放开限制

    /**
     * 去除JCE限制
     */
    private static void removeJceLimit() {
        // 去除JCE加密限制,只限于Java1.8
        try {
            Field field = Class.forName("javax.crypto.JceSecurity").getDeclaredField("isRestricted");
            field.setAccessible(true);
            Field modifiersField = Field.class.getDeclaredField("modifiers");
            modifiersField.setAccessible(true);
            modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
            field.set(null, false);
        } catch (ClassNotFoundException | NoSuchFieldException | 
            SecurityException | IllegalArgumentException | IllegalAccessException ex) {
            LOGGER.error("removeJceLimit e:", ex);
        }
    }

在服务启动类中执行这个方法放开限制,这样就不会报错了。

山不转水转

上面两种方式都有一定的缺陷,第一种如果直接替换生产环境jdk服务器上的jar,这样无疑影响了该服务器上的所有服务,哪怕是没有用到AES加密的服务,谁能担保这样换jar包不会对其他服务有影响。当然如果公司是土豪一台服务器只部署一个服务,这样就另当别论了。对于第二种方式,在启动类中通过反射放开限制,这样的风险也是不可预估的。总之生产环境还是要谨慎点比较好。除非你可以拍胸脯说,我对jdk了如指掌,就是这么自信。

那么最保险的方式是什么呢,我们可以将需要用到AES256加密的服务,指定一个单独的jdk启动,只替换这个jdk的local_policy.jar和US_export_policy.jar,这个单独的jdk不要在环境变量中配置,服务启动的时候我们指定这个jdk的路径。比如说说nohup /usr/server/jdk1.8.0_111/bin/java -jar test.jar &。这样就避免了因为替换Jar包影响了其他服务。

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章