因公司业务需要和其他公司做接口对接,对方明确需要使用 RSA + AES 保障数据安全,AES 对称加密用于对请求参数加密,RSA 主要做签名和核心数据的加解密。所以使用本博客记录下要点。
一、加密注意事项
在使用 AES 的时候遇到的,加解密时报错:
java.security.InvalidKeyException: Illegal key size or default parameters
经过度娘的帮助原来是缺少了 OracleJDK 的权限文件,所以需要去官网下载,下载地址:
http://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html (JDK8版本的)
下载后将 local_policy.jar 和**US_export_policy.jar **两个文件放到 **${JAVA_HOME}/jre/lib/security **目录下即可。
二、openssl 生成密钥对(PEM文件)
- 生成私钥
openssl genrsa -out rsa_private_key.pem 1024
生成一个 1024 位的私钥,并保存到 rsa_private_key.pem 文件中。这个时候密钥是 pkcs1 格式的,无法被 java 程序使用,需要转换为 pkcs8 格式的
- 转换私钥为 pkcs8
openssl pkcs8 -topk8 -inform PEM -in rsa_private_key.pem -outform PEM -nocrypt -out private_pkcs8.pem
pkcs8 转换的参数详解见博客:openssl pkcs8_艾-小小雨的博客-CSDN博客_openssl pkcs8
- 生成公钥
openssl rsa -in private_pkcs8.pem -pubout -out public_pkcs8.pem
使用 rsa 库利用私钥生成对应的公钥,rsa 生成的公钥就是 pkcs8 格式的,所以无需转换。
三、代码实现
直接使用 hutool 工具包,开箱即用。
<!-- 引入所有模块 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>${hutool.version}</version>
</dependency>
<!-- 或者:只引入加解密模块:提供对称、非对称和摘要算法封装 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-crypto</artifactId>
<version>${hutool.version}</version>
</dependency>
1、读取 PEM 密钥文件
/**
* 读取 pem 文件的测试(pkcs8 格式的文件)
*/
@Test
public void readKeyFromPem() throws IOException {
// 通过 hutool 封装的工具:读取公钥
String publicKey;
try (InputStream is = getClass().getClassLoader().getResourceAsStream("public_pkcs8.pem")) {
byte[] bytes = BCUtil.readKeyBytes(is);
PublicKey pk = KeyUtil.generateRSAPublicKey(bytes);
publicKey = Base64Encoder.encode(pk.getEncoded());
}
// 读取私钥
String privateKey;
try (InputStream is = getClass().getClassLoader().getResourceAsStream("private_pkcs8.pem")) {
PrivateKey pk = BCUtil.readPrivateKey(is);
privateKey = Base64Encoder.encode(pk.getEncoded());
}
System.out.println(publicKey);
System.out.println(privateKey);
}
这样就把 PEM 文件读取并转换为代码中可用的公钥和私钥了。
那为什么公钥和私钥的读取 API 会有差异呢?
通过查看源码**
BCUtil#readPrivateKey
**方法关键的地方是:
// KeyUtil 工具类
public static PrivateKey generatePrivateKey(String algorithm, byte[] key) {
return null == key ? null : generatePrivateKey(algorithm, (KeySpec)(new PKCS8EncodedKeySpec(key)));
}
加载私钥是使用的 **
PKCS8EncodedKeySpec
**编码器,而生成的私钥也是 pkcs8 格式,所以能够正常读取。
而 **
BCUtil#readPublicKey
**读取公钥的源码如下:
// BCUtils 工具类
public static PublicKey readPublicKey(InputStream pemStream) {
Certificate certificate = KeyUtil.readX509Certificate(pemStream);
return null == certificate ? null : certificate.getPublicKey();
}
直接就使用
readX509Certificate
编码器,那么读取的就是 X509 格式的公钥文件,但是 openssl 生成的公钥是 pkcs8 格式的,格式不一致会读取不到公钥,所以不能直接使用这个 API,就只能使用上面测试用例的公钥读取方式。
2、加解密
/**
* 加解密测试:RSA/ECB/PKCS1Padding 算法实现
*/
@Test
public void rsa() throws Exception {
// 使用上面的测试用例的方式拿到了公钥和私钥了
String publicKey = ...;
String privateKey = ...;
String body = "hello hutool";
// 公钥加密
String encrypt = SecureUtil.rsa(null, publicKey)
.encryptBase64(body.getBytes(), KeyType.PublicKey);
System.out.println("密文 = " + encrypt);
// 私钥解密
String content = SecureUtil.rsa(privateKey, null)
.decryptStr(encrypt, KeyType.PrivateKey);
System.out.println("解密 = " + content);
System.out.println("原文 = " + body);
}
RSA 只是非对称加密的一个大类总称,其实现有不同的算法——通过不同的填充规则实现的,加大破解难度。所以实现的算法(填充)如下:
- RSA:常规的算法
- RSA/ECB/PKCS1Padding:使用 pkcs1_padding 算法填充(推荐的)
- RSA/None/NoPadding:不使用填充
- EC
通过 SecureUtil.rsa(privateKey, publicKey)创建的 Rsa 对象,默认的就是 RSA/ECB/PKCS1Padding 算法,AsymmetricAlgorithm枚举中定义了这些算法类型,所以要使用其他实现直接使用 **public RSA(String rsaAlgorithm, String privateKeyStr, String publicKeyStr)**这个构造方法创建即可。
3、生成签名(sign)
最开始我简单的认为就是使用私钥简单的将数据加密,就形成了签名(sign),结果大错特错,这样生成的签名,别人验证不通过,因为行业规定的签名是需要加入摘要算法的,让签名不可逆,也就无法破解签名中的内容。而 hutool 已经实现了签名工具类了,直接使用即可。
首先需要依据提前约定好的规则对数据生成待签名的字符串(一般都是参数key的字典排序 + md5 摘要形成的字符串),然后对字符串生成签名,如下:
/**
* 签名测试:sha256 摘要签名
*/
@Test
public void signTest() throws IOException {
// 假设 signMd5 字符串是通过约定好的规则并通过 MD5 摘要后的一个待签名的字符串
String signMd5 = "5A5A5A5A5A5A5A5A5A5A5A5A5A5A5A";
// sha256 加密签名
Sign signObj = SecureUtil.sign(SignAlgorithm.SHA256withRSA, privateKey, publicKey);
byte[] signBytes = signObj.sign(signMd5.getBytes());
System.out.println(Base64.encode(signBytes));
}
**SignAlgorithm 中定义了常用的 RSA 签名的摘要算法,这样生成出来的签名才是正确的签名。通过查看源码,签名是使用的私钥进行签名,所以验签要用公钥解密,Sign 对象中已经封装了验签方法:Sign#verify **方法就是用于验签的。
在验签之前,场景需要明确:
- A 方通过约定的规则生成字符串,并使用 sha256 摘要后通过自己的私钥加密生成了一个 sign(byte数组 + base64)。
- B 方只能拿到 A 方的公钥,那么 B 只能使用公钥将 sign 解密出来,拿到一个 sha256 摘要后的数据(byte数组),而这个时候 B 要验签,也只能通过约定好的规则生成一个字符串,再通过 sha256 摘要后得到一个 data(我方生成的 sign,byte数组),进行对比验签。
明确了这个场景后,hutool 工具包的 Sign#verify 方法已经帮我们实现了以下工作:
- 使用公钥解密 sign
- 对我们自己生成的数据 data 进行摘要
- 对比
所以我们自己只需要通过约定的规则生成出待签名的字符串,并转换为 byte 数组即可——data原数据,并和接收到的 sign 一起调用:**sign.verify(data, sign) **即可完成验签。
版权归原作者 网上搬砖 所有, 如有侵权,请联系我们删除。