skip to content
Wentao Zhang

RSA 接口签名认证实现

/ 4 min read

前因

在业务上需要暴露一些接口提供给第三方,经过讨论决定使用RSA1签名来做接口认证。

RSA 算法简介

RSA(Rivest-Shamir-Adleman)是一种非对称加密算法,广泛用于数据加密和数字签名。它的安全性基于大整数因子分解的困难性。

RSA 的基本原理可以用以下数学公式表示:

加密: C=MemodnC = M^e \mod n

解密: M=CdmodnM = C^d \mod n

其中:

  • MM 是明文消息
  • CC 是密文
  • eenn 组成公钥
  • ddnn 组成私钥

性能比较

下面是一个简单的RSA签名性能分析图表,展示了不同密钥长度对签名和验证时间的影响:

image

签名认证流程

1. 开始
2. 生成RSA密钥对
3. 分发公钥给客户端
4. 客户端准备请求数据
5. 客户端使用私钥对数据签名
6. 客户端发送请求数据和签名
7. 服务端接收请求
8. 服务端使用公钥验证签名
9. 验证是否通过
10. 接受请求并处理
11. 拒绝请求
12. 返回处理结果
13. 返回错误信息
14. 结束

Java实现

生成签名

String appSecret = "...private_key...";
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PKCS8EncodedKeySpec privateSpec = new PKCS8EncodedKeySpec(Base64.decode(appSecret));
PrivateKey privateKey = keyFactory.generatePrivate(privateSpec);
// 创建签名对象
Signature sign = Signature.getInstance("SHA256withRSA");
// 签名
sign.initSign(privateKey);
sign.update("Hello, World!".getBytes("UTF-8"));
byte[] signature = sign.sign();
System.out.println("签名: " + Base64.encode(signature));

验证签名

String appKey = "...public_key..."
// 解码密钥
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
X509EncodedKeySpec publicSpec = new X509EncodedKeySpec(Base64.decode(appKey));
PublicKey publicKey = keyFactory.generatePublic(publicSpec);
// 验证
Signature verifySig = Signature.getInstance("SHA256withRSA");
verifySig.initVerify(publicKey);
verifySig.update("Hello, World!".getBytes("UTF-8"));
boolean isCorrect = verifySig.verify(signature);
System.out.println("签名正确: " + isCorrect);

完整的工具方法

import cn.hutool.core.codec.Base64;
import cn.hutool.crypto.SecureUtil;
import java.nio.charset.StandardCharsets;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
/**
* 描述: RSA 工具
*/
public class RSAUtil {
/**
* 验证签名
* @param publicKey 公钥
* @param sign 签名
* @param data 数据
* @return 是否验证通过
* @throws Exception 异常
*/
public static boolean verify(String publicKey, String sign, String data) {
X509EncodedKeySpec publicSpec = new X509EncodedKeySpec(Base64.decode(publicKey));
PublicKey key = SecureUtil.generatePublicKey("RSA", publicSpec);
// 验证
try {
Signature verifySig = Signature.getInstance("SHA256withRSA");
verifySig.initVerify(key);
verifySig.update(data.getBytes(StandardCharsets.UTF_8));
return verifySig.verify(Base64.decode(sign));
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
/**
* 生成签名
* @param privateKey 私钥
* @param data 数据
* @return 签名
*/
public static String sign(String privateKey, String data) throws Exception {
PKCS8EncodedKeySpec privateSpec = new PKCS8EncodedKeySpec(Base64.decode(privateKey));
PrivateKey key = SecureUtil.generatePrivateKey("RSA", privateSpec);
// 创建签名对象
Signature sign = Signature.getInstance("SHA256withRSA");
// 签名
sign.initSign(key);
sign.update(data.getBytes(StandardCharsets.UTF_8));
byte[] signature = sign.sign();
return Base64.encode(signature);
}
}

Python生成签名、验证签名 (使用pycryptodome)

安装 pycryptodome

Terminal window
pip install pycryptodome

实现代码

import base64
import http.client
import json
import time
import Crypto
from Crypto.PublicKey import RSA
from Crypto.Signature import pkcs1_15
from Crypto.Hash import SHA256
appKey = "...public_key..."
app_secret = """-----BEGIN RSA PRIVATE KEY-----
...private_key...
-----END RSA PRIVATE KEY-----"""
def sign(data, private_key):
key = RSA.import_key(private_key)
h = SHA256.new(data.encode('utf-8'))
signature = pkcs1_15.new(key).sign(h)
return base64.b64encode(signature).decode('utf-8')
def verify(public_key, sign, data):
public_key = RSA.import_key(public_key)
sign_bytes = base64.b64decode(sign)
hash = SHA256.new(data.encode('utf-8'))
try:
pkcs1_15.new(public_key).verify(hash, sign_bytes)
return True
except (ValueError, TypeError):
return False

注意事项

注意: 最初使用 hutool 工具实现签名认证时,发现 Python 中生成的签名无法在 Java 端验证通过。因此改用了 Java 基础库来实现,以确保跨语言兼容性。

Footnotes

  1. RSA维基百科