百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程字典 > 正文

Java开发中的加密、解密、签名、验签,密钥,证书(上篇)

toyiye 2024-08-10 21:25 6 浏览 0 评论

OpenSSL和keytool

先说一下两个重要的工具

  • OpenSSL:OpenSSL整个软件包大概可以分成三个主要的功能部分:SSL协议库libssl、应用程序命令工具以及密码算法库libcrypto。它使用标准的文件格式(PEM/CER/CRT/PKCS等)存储密钥和证书信息。
  • keytool:是密钥和证书管理工具。它出自于Java体系,它使用KeyStore来管理密钥和证书。

两者都是可以用来生成加密密钥的工具,keytool出自Java体系,它可以直接操作KeyStore,而OpenSSL不支持直接操作KeyStore。实际情况有可能是这样的,使用OpenSSL生成了密钥或证书,然后使用keytool将其导入到KeyStore以便在Java环境中使用。

当然OpenSSL还具备其他功能比如作为SSL的客户端和服务器,这是keytool所不具备的。

对称加密

采用单钥密码系统的加密方法,同一个密钥可以同时用作信息的加密和解密,这种加密方法称为对称加密,也称为单密钥加密。——百度百科

对称加密算法的特点

  • 加密和解密使用同样的密钥
  • 计算速度快,适用于对大量数据加密处理
  • 安全性取决于算法,也取决于密钥的管理,一旦密钥泄漏,数据则暴露无遗

对称加密算法的使用场景

基于上述的特点,在一些需要高效实时传输的加密通讯场景中,比如使用VPN或者代理进行通讯时,可以使用对称加密。另外在同一个系统内部不同模块,比如前后端,从前端输入的敏感信息,可以使用对称加密算法进行加密后将密文传到后端,避免传输过程中明文被截获,因为同系统内部之间密钥管理相对容易,而对于共享密钥有泄漏风险的其他任何场景,则不适合使用对称加密算法进行加密。

常见的对称加密算法

算法 描述 DES(Data Encryption Standard) 数据加密标准,速度较快,适用于加密大量数据 3DES(Triple DES) 基于DES,对一块数据用三个不同的密钥进行三次加密,强度更高 AES(Advanced Encryption Standard) 高级加密标准,速度快,安全级别高,支持128、192、256、512位密钥的加密 Blowfish 速度快且安全,而且没有专利和商业限制。了解更多>>

OpenSSL实现对称加密

OpenSSL> enc --help
usage: enc -ciphername [-AadePp] [-base64] [-bufsize number] [-debug]
    [-in file] [-iv IV] [-K key] [-k password]
    [-kfile file] [-md digest] [-none] [-nopad] [-nosalt]
    [-out file] [-pass arg] [-S salt] [-salt]

 -A                 Process base64 data on one line (requires -a)
 -a                 Perform base64 encoding/decoding (alias -base64)
 -bufsize size      Specify the buffer size to use for I/O
 -d                 Decrypt the input data
 -debug             Print debugging information
 -e                 Encrypt the input data (default)
 -in file           Input file to read from (default stdin)
 -iv IV             IV to use, specified as a hexadecimal string
 -K key             Key to use, specified as a hexadecimal string
 -md digest         Digest to use to create a key from the passphrase
 -none              Use NULL cipher (no encryption or decryption)
 -nopad             Disable standard block padding
 -out file          Output file to write to (default stdout)
 -P                 Print out the salt, key and IV used, then exit
                      (no encryption or decryption is performed)
 -p                 Print out the salt, key and IV used
 -pass source       Password source
 -S salt            Salt to use, specified as a hexadecimal string
 -salt              Use a salt in the key derivation routines (default)
 -v                 Verbose

命令选项 描述 -in file 被加密文件的全路径 -out file 加密后内容输出的文件路径 -salt 自动插入一个随机数作为文件内容加密,默认选项 -e 加密模式,默认 -d 解密模式,需要与加密算法一致 -a 使用-base64位编码格式,也可使用-base64 -pass source 指定密码的输入方式,共有五种方式:命令行输入(stdin)、文件输入(file)、环境变量输入(var)、文件描述符输入(fd)、标准输入(stdin)。默认是标准输入即从键盘输入

只对文件进行base64编码,而不使用加解密

/*对文件进行base64编码*/
openssl enc -base64 -in plain.txt -out base64.txt
/*对base64格式文件进行解密操作*/
openssl enc -base64 -d -in base64.txt -out plain2.txt
/*使用diff命令查看可知解码前后明文一样*/
diff plain.txt plain2.txt

不同方式的密码输入方式

/*命令行输入,密码123456*/
openssl enc -aes-128-cbc -in plain.txt -out out.txt -pass pass:123456
/*文件输入,密码123456*/
echo 123456 > passwd.txt
openssl enc -aes-128-cbc -in plain.txt -out out.txt -pass file:passwd.txt
/*环境变量输入,密码123456*/
passwd=123456
export passwd
openssl enc -aes-128-cbc -in plain.txt -out out.txt -pass env:passwd
/*从文件描述输入*/ 
openssl enc -aes-128-cbc -in plain.txt -out out.txt -pass fd:1  
/*从标准输入输入*/ 
openssl enc -aes-128-cbc -in plain.txt -out out.txt -pass stdin 

Java实现对称加密

DES

/**
 * 生成 DES 算法密钥
 * @return byte[]
 * @throws Exception
 */
public static byte[] generateDESKey() throws Exception {
    KeyGenerator keyGenerator = KeyGenerator.getInstance("DES");
    // must be equal to 56
    keyGenerator.init(56);
    SecretKey secretKey = keyGenerator.generateKey();
    byte[] encodedKey = secretKey.getEncoded();
    return encodedKey;
}

/**
 * DES加密
 * @param encodedKey generateDESKey生成的密钥
 * @param dataBytes byte[]形式的待加密数据
 * @return byte[]
 * @throws Exception
 */
public static byte[] encryptByDES(byte[] encodedKey, byte[] dataBytes) throws Exception {
    SecretKey secretKey = new SecretKeySpec(encodedKey, "DES");
    Cipher cipher = Cipher.getInstance("DES");
    cipher.init(Cipher.ENCRYPT_MODE, secretKey);
    byte[] encryptedData = cipher.doFinal(dataBytes);
    return encryptedData;
}

/**
 * DES解密
 * @param encodedKey generateDESKey生成的密钥
 * @param encryptedData byte[]形式的待解密数据
 * @return byte[]
 * @throws Exception
 */
public static byte[] decryptByDES(byte[] encodedKey, byte[] encryptedData) throws Exception {
    SecretKey secretKey = new SecretKeySpec(encodedKey, "DES");
    Cipher cipher = Cipher.getInstance("DES");
    cipher.init(Cipher.DECRYPT_MODE, secretKey);
    byte[] decryptedData = cipher.doFinal(encryptedData);
    return decryptedData;
}

基础版本使用方法如下:

@Test
public void testDES_1() throws Exception {
    byte[] encodedKey = SecurityUtil.generateDESKey();
    String data = "this is a good boy";
    byte[] encryptedData = SecurityUtil.encryptByDES(encodedKey, data.getBytes());
    byte[] decryptedData = SecurityUtil.decryptByDES(encodedKey, encryptedData);
    Assert.assertEquals(data, new String(decryptedData));
}

可以看到,以上的方法使用起来并不友好,参数、返回等大量存在byte[],不便于理解,中间结果不便于查看和传输,比如如果需要将encryptedData返回给下游系统,那么还得使用Base64进行处理,基于此,我对在上述接口基础上进一步进行封装,使其使用起来更贴近日常使用场景。

优化版本:

/**
 * 生成 DES 算法密钥
 * @return 经过Base64编码的字符串密钥
 * @throws Exception
 */
public static String generateDESKeyStr() throws Exception {
    return Base64.encodeBase64String(generateDESKey());
}

/**
 * DES加密
 * @param key 经过Base64编码的字符串密钥
 * @param data String形式的待加密数据
 * @return 经过Base64编码的加密数据
 * @throws Exception
 */
public static String encryptByDES(String key, String data) throws Exception {
    byte[] encodedKey = Base64.decodeBase64(key);
    byte[] dataBytes = data.getBytes();
    byte[] encryptedData = encryptByDES(encodedKey, dataBytes);
    return Base64.encodeBase64String(encryptedData);
}

/**
 * DES解密
 * @param key 经过Base64编码的字符串密钥
 * @param data String形式的待解密数据
 * @return 原始数据
 * @throws Exception
 */
public static String decryptByDES(String key, String data) throws Exception {
    byte[] encodedKey = Base64.decodeBase64(key);
    byte[] dataBytes = Base64.decodeBase64(data);
    byte[] decryptedData = decryptByDES(encodedKey, dataBytes);
    return new String(decryptedData);
}

优化版本使用方法如下:

@Test
public void testDES_2() throws Exception {
    String key = SecurityUtil.generateDESKeyStr();
    String data = "this is a good boy";
    String encryptedData = SecurityUtil.encryptByDES(key, data);
    String decryptedData = SecurityUtil.decryptByDES(key, encryptedData);
    Assert.assertEquals(data, decryptedData);
}

这里补充一下,在实际项目开发过程中,还真遇见不少同学对Base64理解有误的情况,对于以上处理和转换过程理解有难度的同学,可以戳一下这里

3DES

/**
 * 生成 3DES 算法密钥
 * @return byte[]
 * @throws Exception
 */
public static byte[] generate3DESKey() throws Exception {
    KeyGenerator keyGenerator = KeyGenerator.getInstance("DESede");
    // must be equal to 112 or 168
    keyGenerator.init(168);
    SecretKey secretKey = keyGenerator.generateKey();
    byte[] encodedKey = secretKey.getEncoded();
    return encodedKey;
}

/**
 * 3DES加密
 * @param encodedKey generate3DESKey生成的密钥
 * @param dataBytes byte[]形式的待加密数据
 * @return byte[]
 * @throws Exception
 */
public static byte[] encryptBy3DES(byte[] encodedKey, byte[] dataBytes) throws Exception {
    SecretKey secretKey = new SecretKeySpec(encodedKey, "DESede");
    Cipher cipher = Cipher.getInstance("DESede");
    cipher.init(Cipher.ENCRYPT_MODE, secretKey);
    byte[] encryptedData = cipher.doFinal(dataBytes);
    return encryptedData;
}

/**
 * 3DES解密
 * @param encodedKey generate3DESKey生成的密钥
 * @param encryptedData byte[]形式的待解密数据
 * @return byte[]
 * @throws Exception
 */
public static byte[] decryptBy3DES(byte[] encodedKey, byte[] encryptedData) throws Exception {
    SecretKey secretKey = new SecretKeySpec(encodedKey, "DESede");
    Cipher cipher = Cipher.getInstance("DESede");
    cipher.init(Cipher.DECRYPT_MODE, secretKey);
    byte[] decryptedData = cipher.doFinal(encryptedData);
    return decryptedData;
}

使用方法如下:

@Test
public void test3DES() throws Exception {
    byte[] encodedKey = SecurityUtil.generate3DESKey();
    String data = "this is a good boy";
    byte[] encryptedData = SecurityUtil.encryptBy3DES(encodedKey, data.getBytes());
    byte[] decryptedData = SecurityUtil.decryptBy3DES(encodedKey, encryptedData);
    Assert.assertEquals(data, new String(decryptedData));
}

AES

/**
 * 生成 AES 算法密钥
 * @return byte[]
 * @throws Exception
 */
public static byte[] generateAESKey() throws Exception {
    KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
    // must be equal to 128, 192 or 256
    // 但是当你使用 192/256 时,会收到:
    // java.security.InvalidKeyException: Illegal key size or default parameters
    keyGenerator.init(128);
    SecretKey secretKey = keyGenerator.generateKey();
    byte[] encodedKey = secretKey.getEncoded();
    return encodedKey;
}

/**
 * AES加密
 * @param encodedKey generateAESKey生成的密钥
 * @param dataBytes byte[]形式的待加密数据
 * @return byte[]
 * @throws Exception
 */
public static byte[] encryptByAES(byte[] encodedKey, byte[] dataBytes) throws Exception {
    SecretKey secretKey = new SecretKeySpec(encodedKey, "AES");
    Cipher cipher = Cipher.getInstance("AES");
    cipher.init(Cipher.ENCRYPT_MODE, secretKey);
    byte[] encryptedData = cipher.doFinal(dataBytes);
    return encryptedData;
}

/**
 * AES密
 * @param encodedKey generateAESSKey生成的密钥
 * @param encryptedData byte[]形式的待解密数据
 * @return byte[]
 * @throws Exception
 */
public static byte[] decryptByAES(byte[] encodedKey, byte[] encryptedData) throws Exception {
    SecretKey secretKey = new SecretKeySpec(encodedKey, "AES");
    Cipher cipher = Cipher.getInstance("AES");
    cipher.init(Cipher.DECRYPT_MODE, secretKey);
    byte[] decryptedData = cipher.doFinal(encryptedData);
    return decryptedData;
}

使用方法如下:

@Test
public void testAES() throws Exception {
    byte[] encodedKey = SecurityUtil.generateAESKey();
    String data = "this is a good boy";
    byte[] encryptedData = SecurityUtil.encryptByAES(encodedKey, data.getBytes());
    byte[] decryptedData = SecurityUtil.decryptByAES(encodedKey, encryptedData);
    Assert.assertEquals(data, new String(decryptedData));
}

虽然AES支持128、192或 256的密钥长度,但是当我们使用192或256位长度的密钥时,会收到这个异常:java.security.InvalidKeyException: Illegal key size or default parameters

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

 at javax.crypto.Cipher.checkCryptoPerm(Cipher.java:1026)
 at javax.crypto.Cipher.implInit(Cipher.java:801)
 at javax.crypto.Cipher.chooseProvider(Cipher.java:864)
 at javax.crypto.Cipher.init(Cipher.java:1249)
 at javax.crypto.Cipher.init(Cipher.java:1186)
 at com.example.architecture.util.SecurityUtil.encryptByAES(SecurityUtil.java:161)
 at com.example.architecture.util.SecurityUtilTest.testAES(SecurityUtilTest.java:97)
 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
 at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
 at java.lang.reflect.Method.invoke(Method.java:498)
 at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
 at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
 at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
 at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)

原因是JRE中自带的local_policy.jarUS_export_policy.jar是支持128位密钥的加密算法,而当我们要使用192或256位密钥算法的时候,已经超出它支持的范围。

解决方案:去官方下载JCE无限制权限策略文件。

JDK5 | JDK6 | JDK7| JDK8

下载后解压,可以看到local_policy.jar和US_export_policy.jar以及readme.txt

  • 如果安装了JRE,将两个jar文件放到%JRE_HOME%\lib\security目录下覆盖原来的文件。
  • 如果安装了JDK,还要将两个jar文件也放到%JDK_HOME%\jre\lib\security目录下覆盖原来文件。

AES128和AES256主要区别是密钥长度不同(分别是128bits,256bits)、加密处理轮数不同(分别是10轮,14轮),后者强度高于前者,当前AES是公认的较为安全的对称加密算法。

至此,上篇结束,更多精彩内容,欢迎继续阅读下篇。

参考资料:

https://baike.baidu.com/item/Blowfish/1677776

https://baike.baidu.com/item/对称加密/2152944

https://www.cnblogs.com/gordon0918/p/5317701.html

https://blog.csdn.net/u011414629/article/details/102645600

https://baike.baidu.com/item/非对称加密算法/1208652

https://www.jianshu.com/p/78886e480bef

https://blog.csdn.net/makenothing/article/details/88429511

https://blog.csdn.net/zlfing/article/details/77648444

https://blog.csdn.net/w47_csdn/article/details/87564029

相关推荐

为何越来越多的编程语言使用JSON(为什么编程)

JSON是JavascriptObjectNotation的缩写,意思是Javascript对象表示法,是一种易于人类阅读和对编程友好的文本数据传递方法,是JavaScript语言规范定义的一个子...

何时在数据库中使用 JSON(数据库用json格式存储)

在本文中,您将了解何时应考虑将JSON数据类型添加到表中以及何时应避免使用它们。每天?分享?最新?软件?开发?,Devops,敏捷?,测试?以及?项目?管理?最新?,最热门?的?文章?,每天?花?...

MySQL 从零开始:05 数据类型(mysql数据类型有哪些,并举例)

前面的讲解中已经接触到了表的创建,表的创建是对字段的声明,比如:上述语句声明了字段的名称、类型、所占空间、默认值和是否可以为空等信息。其中的int、varchar、char和decimal都...

JSON对象花样进阶(json格式对象)

一、引言在现代Web开发中,JSON(JavaScriptObjectNotation)已经成为数据交换的标准格式。无论是从前端向后端发送数据,还是从后端接收数据,JSON都是不可或缺的一部分。...

深入理解 JSON 和 Form-data(json和formdata提交区别)

在讨论现代网络开发与API设计的语境下,理解客户端和服务器间如何有效且可靠地交换数据变得尤为关键。这里,特别值得关注的是两种主流数据格式:...

JSON 语法(json 语法 priority)

JSON语法是JavaScript语法的子集。JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔花括号保存对象方括号保存数组JS...

JSON语法详解(json的语法规则)

JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔大括号保存对象中括号保存数组注意:json的key是字符串,且必须是双引号,不能是单引号...

MySQL JSON数据类型操作(mysql的json)

概述mysql自5.7.8版本开始,就支持了json结构的数据存储和查询,这表明了mysql也在不断的学习和增加nosql数据库的有点。但mysql毕竟是关系型数据库,在处理json这种非结构化的数据...

JSON的数据模式(json数据格式示例)

像XML模式一样,JSON数据格式也有Schema,这是一个基于JSON格式的规范。JSON模式也以JSON格式编写。它用于验证JSON数据。JSON模式示例以下代码显示了基本的JSON模式。{"...

前端学习——JSON格式详解(后端json格式)

JSON(JavaScriptObjectNotation)是一种轻量级的数据交换格式。易于人阅读和编写。同时也易于机器解析和生成。它基于JavaScriptProgrammingLa...

什么是 JSON:详解 JSON 及其优势(什么叫json)

现在程序员还有谁不知道JSON吗?无论对于前端还是后端,JSON都是一种常见的数据格式。那么JSON到底是什么呢?JSON的定义...

PostgreSQL JSON 类型:处理结构化数据

PostgreSQL提供JSON类型,以存储结构化数据。JSON是一种开放的数据格式,可用于存储各种类型的值。什么是JSON类型?JSON类型表示JSON(JavaScriptO...

JavaScript:JSON、三种包装类(javascript 包)

JOSN:我们希望可以将一个对象在不同的语言中进行传递,以达到通信的目的,最佳方式就是将一个对象转换为字符串的形式JSON(JavaScriptObjectNotation)-JS的对象表示法...

Python数据分析 只要1分钟 教你玩转JSON 全程干货

Json简介:Json,全名JavaScriptObjectNotation,JSON(JavaScriptObjectNotation(记号、标记))是一种轻量级的数据交换格式。它基于J...

比较一下JSON与XML两种数据格式?(json和xml哪个好)

JSON(JavaScriptObjectNotation)和XML(eXtensibleMarkupLanguage)是在日常开发中比较常用的两种数据格式,它们主要的作用就是用来进行数据的传...

取消回复欢迎 发表评论:

请填写验证码