JAVA 做非对称加密(RSA)

目录

一、加密注意事项

二、openssl 生成密钥对(PEM文件)

三、代码实现

1、读取 PEM 密钥文件

2、加解密

3、生成签名(sign)


因公司业务需要和其他公司做接口对接,对方明确需要使用 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.jarUS_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 方法就是用于验签的。
在验签之前,场景需要明确:

  1. A 方通过约定的规则生成字符串,并使用 sha256 摘要后通过自己的私钥加密生成了一个 sign(byte数组 + base64)。
  2. B 方只能拿到 A 方的公钥,那么 B 只能使用公钥将 sign 解密出来,拿到一个 sha256 摘要后的数据(byte数组),而这个时候 B 要验签,也只能通过约定好的规则生成一个字符串,再通过 sha256 摘要后得到一个 data(我方生成的 sign,byte数组),进行对比验签。

明确了这个场景后,hutool 工具包的 Sign#verify 方法已经帮我们实现了以下工作:

  1. 使用公钥解密 sign 
  2. 对我们自己生成的数据 data 进行摘要
  3.  对比

所以我们自己只需要通过约定的规则生成出待签名的字符串,并转换为 byte 数组即可——data原数据,并和接收到的 sign 一起调用:sign.verify(data, sign) 即可完成验签。

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值