JAVA加密--AES加密算法JAVA实现及使用中的各种坑,超实用_invalid aes key length: 18 bytes-程序员宅基地

技术标签: 加密解密  加密算法  JAVA基础  Illegal key  crypto.policy  AES  

1. AES

1.1. 概念

密码学中的高级加密标准(Advanced Encryption Standard,AES),又称Rijndael加密法,是美国联邦政府采用的一种区块加密标准。
这个标准用来替代原先的DES(Data Encryption Standard),已经被多方分析且广为全世界所使用,已然成为对称密钥加密中最流行的算法之一。详见 百科 高级加密标准 AES

1.2. JAVA实现AES加解密

import lombok.extern.slf4j.Slf4j;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.security.SecureRandom;
import java.util.Arrays;

@Slf4j
class AESCryptoUtil {
    
    public static void main(String[] args) {
    
        String content = "美好的世界,美好的中国,美好的未来!!!";

        byte[] encryptResult = encrypt(content);
        byte[] decryptResult = decrypt(encryptResult);


        String encryptStr = parseByte2HexStr(encryptResult);
        printMsg("========\n加密前:%s\n加密后:%s\n解密后:%s", content, encryptStr, new String(decryptResult));

        //解密时,不能new String(encryptResult,"utf-8").getBytes("utf-8")
        decryptResult = decrypt(parseHexStr2Byte(encryptStr));
        printMsg("========\n加密前:%s\n加密后:%s\n解密后:%s", content, encryptStr,new String(decryptResult));


        //采用AES/ECB/NoPadding时,要求密钥长度16、24、32中的一个、待加密内容的byte[]长度必须是16的倍数
        encryptResult = encrypt2(content);
        decryptResult = decrypt2(encryptResult);
        encryptStr = parseByte2HexStr(encryptResult);
        printMsg("========\n加密前:%s\n加密后:%s\n解密后:%s", content, encryptStr, new String(decryptResult));

    }

    private static void printMsg(String template, Object... args) {
    
        System.out.println(String.format(template, args));
    }

    private static String password = "科技兴国@!##";
    private static SecretKeySpec key;

    static {
    
        init();
    }

    private static void init() {
    
        try {
    
            KeyGenerator kgen = KeyGenerator.getInstance("AES");
            kgen.init(256, new SecureRandom(password.getBytes()));
            SecretKey secretKey = kgen.generateKey();
            byte[] enCodeFormat = secretKey.getEncoded();
            key = new SecretKeySpec(enCodeFormat, "AES");
        } catch (Exception e) {
    
            log.error("初始化AES算法失败" + e.getMessage(), e);
        }
    }

    /**
     * 加密
     *
     * @param content 需要加密的内容
     * @return
     */
    public static byte[] encrypt(String content) {
    
        try {
    
            Cipher cipher = Cipher.getInstance("AES");
            byte[] byteContent = content.getBytes("utf-8");
            cipher.init(Cipher.ENCRYPT_MODE, key);
            byte[] result = cipher.doFinal(byteContent);
            return result;
        } catch (Exception e) {
    
            log.error("AES加密失败" + e.getMessage(), e);
        }
        return null;
    }

    public static byte[] decrypt(byte[] content) {
    
        try {
    
            Cipher cipher = Cipher.getInstance("AES");
            cipher.init(Cipher.DECRYPT_MODE, key);
            byte[] result = cipher.doFinal(content);
            return result;
        } catch (Exception e) {
    
            log.error("AES解密失败" + e.getMessage(), e);
        }
        return null;
    }

    /**
     * 加密
     *
     * @param content 需要加密的内容
     * @return
     */
    public static byte[] encrypt2(String content) {
    
        try {
    
            byte[] bytePassword = password.getBytes();
            bytePassword = checkByteLength(bytePassword);
            SecretKeySpec key = new SecretKeySpec(bytePassword, "AES");
            Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding");
            byte[] byteContent = content.getBytes("utf-8");
            byteContent = checkByteLength(byteContent);
            cipher.init(Cipher.ENCRYPT_MODE, key);
            byte[] result = cipher.doFinal(byteContent);
            return result;
        } catch (Exception e) {
    
            log.error("AES加密失败" + e.getMessage(), e);
        }
        return null;
    }

    public static byte[] decrypt2(byte[] content) {
    
        try {
    
            byte[] bytePassword = password.getBytes();
            bytePassword = checkByteLength(bytePassword);
            SecretKeySpec key = new SecretKeySpec(bytePassword, "AES");
            Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding");
            cipher.init(Cipher.DECRYPT_MODE, key);
            byte[] result = cipher.doFinal(content);
            return result;
        } catch (Exception e) {
    
            log.error("AES解密失败" + e.getMessage(), e);
        }
        return null;
    }

    private static byte[] checkByteLength(byte[] byteContent) {
    
        int length = byteContent.length;
        int remainder = length % 16;
        if(remainder == 0){
    
            return byteContent;
        }else{
    
            return Arrays.copyOf(byteContent,length+(16-remainder));
        }
    }

    /**将二进制转换成16进制
     * @param buf
     * @return
     */
    public static String parseByte2HexStr(byte buf[]) {
    
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < buf.length; i++) {
    
            String hex = Integer.toHexString(buf[i] & 0xFF);
            if (hex.length() == 1) {
    
                hex = '0' + hex;
            }
            sb.append(hex.toUpperCase());
        }
        return sb.toString();
    }

    public static byte[] parseHexStr2Byte(String hexStr) {
    
        if (hexStr.length() < 1) {
    
            return null;
        }
        byte[] result = new byte[hexStr.length()/2];
        for (int i = 0;i< hexStr.length()/2; i++) {
    
            int high = Integer.parseInt(hexStr.substring(i*2, i*2+1), 16);
            int low = Integer.parseInt(hexStr.substring(i*2+1, i*2+2), 16);
            result[i] = (byte) (high * 16 + low);
        }
        return result;
    }
}

2. 使用中的各种坑

2.1. 加密后的byte数组不能强制转换成字符串

加密后返回byte[],此byte数组强制转换为字符串是乱码,然后再用转换后的字符串转换为byte数组去解码(见下面的代码)会报错:Input length must be multiple of 16 when decrypting with padded cipher

在这种情况下: 字符串和byte数组不是互逆的;要避免这种情况,需要做一些修订,可以考虑将二进制数据转换成十六进制表示。详见parseByte2HexStr、parseHexStr2Byte两个方法

decryptResult = decrypt(new String(encryptResult,"utf-8").getBytes("utf-8"));

2021-07-02 11:27:39.067 [main] ERROR pattern.chain.AESCryptoUtil:decrypt:87 - AES解密失败Input length must be multiple of 16 when decrypting with padded cipher
javax.crypto.IllegalBlockSizeException: Input length must be multiple of 16 when decrypting with padded cipher
at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:936) ~[sunjce_provider.jar:1.8.0_171]
at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:847) ~[sunjce_provider.jar:1.8.0_171]
at com.sun.crypto.provider.AESCipher.engineDoFinal(AESCipher.java:446) ~[sunjce_provider.jar:1.8.0_171]
at javax.crypto.Cipher.doFinal(Cipher.java:2164) ~[?:1.8.0_171]
at org.apache.design.pattern.chain.AESCryptoUtil.decrypt(AESCryptoUtil.java:84) [classes/:?]
at org.apache.design.pattern.chain.AESCryptoUtil.main(AESCryptoUtil.java:26) [classes/:?]

2.2. 采用AES/ECB/NoPadding加密的限制

当采用如下方式获取Cipher实例时,则要求密钥长度16、24、32中的一个、待加密内容的byte[]长度必须是16的倍数,否则报如下错误:

Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding");

2021-07-02 11:37:15.432 [main] ERROR pattern.chain.AESCryptoUtil:encrypt2:111 - AES加密失败Input length not multiple of 16 bytes
javax.crypto.IllegalBlockSizeException: Input length not multiple of 16 bytes
at com.sun.crypto.provider.CipherCore.finalNoPadding(CipherCore.java:1042) ~[sunjce_provider.jar:1.8.0_171]
at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:1010) ~[sunjce_provider.jar:1.8.0_171]
at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:847) ~[sunjce_provider.jar:1.8.0_171]
at com.sun.crypto.provider.AESCipher.engineDoFinal(AESCipher.java:446) ~[sunjce_provider.jar:1.8.0_171]
at javax.crypto.Cipher.doFinal(Cipher.java:2164) ~[?:1.8.0_171]


2021-07-02 11:36:39.064 [main] ERROR pattern.chain.AESCryptoUtil:encrypt2:111 - AES加密失败Invalid AES key length: 18 bytes
java.security.InvalidKeyException: Invalid AES key length: 18 bytes
at com.sun.crypto.provider.AESCrypt.init(AESCrypt.java:87) ~[sunjce_provider.jar:1.8.0_171]
at com.sun.crypto.provider.ElectronicCodeBook.init(ElectronicCodeBook.java:94) ~[sunjce_provider.jar:1.8.0_171]
at com.sun.crypto.provider.CipherCore.init(CipherCore.java:592) ~[sunjce_provider.jar:1.8.0_171]
at com.sun.crypto.provider.CipherCore.init(CipherCore.java:468) ~[sunjce_provider.jar:1.8.0_171]
at com.sun.crypto.provider.AESCipher.engineInit(AESCipher.java:313) ~[sunjce_provider.jar:1.8.0_171]
at javax.crypto.Cipher.implInit(Cipher.java:801) ~[?:1.8.0_171]
at javax.crypto.Cipher.chooseProvider(Cipher.java:863) ~[?:1.8.0_171]

2.3 美国的出口管制限制引起的坑

2.3.1. 背景

因为美国的出口管制限制,Java发布的运行环境包中的加解密有一定的限制。
比如默认不允许256位密钥的AES加解密,如果使用,会报如下错误:
2021-07-02 11:40:34.048 [main] ERROR pattern.chain.AESCryptoUtil:encrypt:75 - AES加密失败Illegal key size or default parameters
java.security.InvalidKeyException: Illegal key size or default parameters
at javax.crypto.Cipher.checkCryptoPerm(Cipher.java:1025) ~[?:1.8.0_171]
at javax.crypto.Cipher.implInit(Cipher.java:800) ~[?:1.8.0_171]
at javax.crypto.Cipher.chooseProvider(Cipher.java:863) ~[?:1.8.0_171]
at javax.crypto.Cipher.init(Cipher.java:1248) ~[?:1.8.0_171]
at javax.crypto.Cipher.init(Cipher.java:1185) ~[?:1.8.0_171]

从Java 1.8.0_151

2.3.2. 解决方案

方案1
将%JDK_HOME%\jre\lib\security\java.security, 找到定义java安全性属性crypto.policy的行,将值修改为unlimited。
默认情况下,您应该能找到一条注释掉的行:
#crypto.policy=unlimited
您可以通过取消注释该行来启用无限制,删除#:

方案2
如果%JDK_HOME%\jre\lib\security目录下有jar包 且 crypto.policy 未配置,则使用jar包内置的规则
将%JDK_HOME%\jre\lib\security\policy\unlimited下的2个JAR【local_policy.jar、US_export_policy.jar】复制到%JDK_HOME%\jre\lib\security目录下,如果有,直接覆盖。

方案3
如果%JDK_HOME%\jre\lib\security目录下没有jar包 且 crypto.policy 未配置,则默认启用无限制的。

升级版本
https://www.oracle.com/technetwork/java/javase/8u161-relnotes-4021379.html
在官方文档写到,
security-libs/javax.crypto
Unlimited cryptography enabled by default
The JDK uses the Java Cryptography Extension (JCE) Jurisdiction Policy files to configure cryptographic algorithm restrictions. Previously, the Policy files in the JDK placed limits on various algorithms. This release ships with both the limited and unlimited jurisdiction policy files, with unlimited being the default. The behavior can be controlled via the new ‘crypto.policy’ Security property found in the /lib/java.security file. Please refer to that file for more information on this property.
也就是从 1.8.0_161-b12 版本后,默认将采用无限制的加密算法,也就是使用 unlimited 下的jar包。我们也可以通过 设置 java.security 文件的 crypto.policy的值来改变这个默认的值。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/penriver/article/details/118408239

智能推荐

React学习记录-程序员宅基地

文章浏览阅读936次,点赞22次,收藏26次。React核心基础

Linux查磁盘大小命令,linux系统查看磁盘空间的命令是什么-程序员宅基地

文章浏览阅读2k次。linux系统查看磁盘空间的命令是【df -hl】,该命令可以查看磁盘剩余空间大小。如果要查看每个根路径的分区大小,可以使用【df -h】命令。df命令以磁盘分区为单位查看文件系统。本文操作环境:red hat enterprise linux 6.1系统、thinkpad t480电脑。(学习视频分享:linux视频教程)Linux 查看磁盘空间可以使用 df 和 du 命令。df命令df 以磁..._df -hl

Office & delphi_range[char(96 + acolumn) + inttostr(65536)].end[xl-程序员宅基地

文章浏览阅读923次。uses ComObj;var ExcelApp: OleVariant;implementationprocedure TForm1.Button1Click(Sender: TObject);const // SheetType xlChart = -4109; xlWorksheet = -4167; // WBATemplate xlWBATWorksheet = -4167_range[char(96 + acolumn) + inttostr(65536)].end[xlup]

若依 quartz 定时任务中 service mapper无法注入解决办法_ruoyi-quartz无法引入ruoyi-admin的service-程序员宅基地

文章浏览阅读2.3k次。上图为任务代码,在任务具体执行的方法中使用,一定要写在方法内使用SpringContextUtil.getBean()方法实例化Spring service类下边是ruoyi-quartz模块中util/SpringContextUtil.java(已改写)import org.springframework.beans.BeansException;import org.springframework.context.ApplicationContext;import org.s..._ruoyi-quartz无法引入ruoyi-admin的service

CentOS7配置yum源-程序员宅基地

文章浏览阅读2w次,点赞10次,收藏77次。yum,全称“Yellow dog Updater, Modified”,是一个专门为了解决包的依赖关系而存在的软件包管理器。可以这么说,yum 是改进型的 RPM 软件管理器,它很好的解决了 RPM 所面临的软件包依赖问题。yum 在服务器端存有所有的 RPM 包,并将各个包之间的依赖关系记录在文件中,当管理员使用 yum 安装 RPM 包时,yum 会先从服务器端下载包的依赖性文件,通过分析此文件从服务器端一次性下载所有相关的 RPM 包并进行安装。_centos7配置yum源

智能科学毕设分享(算法) 基于深度学习的抽烟行为检测算法实现(源码分享)-程序员宅基地

文章浏览阅读828次,点赞21次,收藏8次。今天学长向大家分享一个毕业设计项目毕业设计 基于深度学习的抽烟行为检测算法实现(源码分享)毕业设计 深度学习的抽烟行为检测算法实现通过目前应用比较广泛的 Web 开发平台,将模型训练完成的算法模型部署,部署于 Web 平台。并且利用目前流行的前后端技术在该平台进行整合实现运营车辆驾驶员吸烟行为检测系统,方便用户使用。本系统是一种运营车辆驾驶员吸烟行为检测系统,为了降低误检率,对驾驶员视频中的吸烟烟雾和香烟目标分别进行检测,若同时检测到则判定该驾驶员存在吸烟行为。进行流程化处理,以满足用户的需要。

随便推点

STM32单片机示例:多个定时器同步触发启动_stm32 定时器同步-程序员宅基地

文章浏览阅读3.7k次,点赞3次,收藏14次。多个定时器同步触发启动是一种比较实用的功能,这里将对此做个示例说明。_stm32 定时器同步

android launcher分析和修改10,Android Launcher分析和修改9——Launcher启动APP流程(转载)...-程序员宅基地

文章浏览阅读348次。出处 : http://www.cnblogs.com/mythou/p/3187881.html本来想分析AppsCustomizePagedView类,不过今天突然接到一个临时任务。客户反馈说机器界面的图标很难点击启动程序,经常点击了没有反应,Boss说要优先解决这问题。没办法,只能看看是怎么回事。今天分析一下Launcher启动APP的过程。从用户点击到程序启动的流程,下面针对WorkSpa..._回调bubbletextview

Ubuntu 12 最快的两个源 个人感觉 163与cn99最快 ubuntu安装源下包过慢_un.12.cc-程序员宅基地

文章浏览阅读6.2k次。Ubuntu 12 最快的两个源 个人感觉 163与cn99最快 ubuntu下包过慢 1、首先备份Ubuntu 12.04源列表 sudo cp /etc/apt/sources.list /etc/apt/sources.list.backup (备份下当前的源列表,有备无患嘛) 2、修改更新源 sudo gedit /etc/apt/sources.list (打开Ubuntu 12_un.12.cc

vue动态路由(权限设置)_vue动态路由权限-程序员宅基地

文章浏览阅读5.8k次,点赞6次,收藏86次。1.思路(1)动态添加路由肯定用的是addRouter,在哪用?(2)vuex当中获取到菜单,怎样展示到界面2.不管其他先试一下addRouter找到router/index.js文件,内容如下,这是我自己先配置的登录路由现在先不管请求到的菜单是什么样,先写一个固定的菜单通过addRouter添加添加以前注意:addRoutes()添加的是数组在export defult router的上一行图中17行写下以下代码var addRoute=[ { path:"/", name:"_vue动态路由权限

JSTL 之变量赋值标签-程序员宅基地

文章浏览阅读8.9k次。 关键词: JSTL 之变量赋值标签 /* * Author Yachun Miao * Created 11-Dec-06 */关于JSP核心库的set标签赋值变量,有两种方式: 1.日期" />2. 有种需求要把ApplicationResources_zh_CN.prope

VGA带音频转HDMI转换芯片|VGA转HDMI 转换器方案|VGA转HDMI1.4转换器芯片介绍_vga转hdmi带音频转换器,转接头拆解-程序员宅基地

文章浏览阅读3.1k次,点赞3次,收藏2次。1.1ZY5621概述ZY5621是VGA音频到HDMI转换器芯片,它符合HDMI1.4 DV1.0规范。ZY5621也是一款先进的高速转换器,集成了MCU和VGA EDID芯片。它还包含VGA输入指示和仅音频到HDMI功能。进一步降低系统制造成本,简化系统板上的布线。ZY5621方案设计简单,且可以完美还原输入端口的信号,此方案设计广泛应用于投影仪、教育多媒体、视频会议、视频展台、工业级主板显示、手持便携设备、转换盒、转换线材等产品设计上面。1.2 ZY5621 特性内置MCU嵌入式VGA_vga转hdmi带音频转换器,转接头拆解

推荐文章

热门文章

相关标签