主题
签名与验签
签名算法
- 签名算法为SHA256WithRSA,密钥长度为2048位(bit)。
签名过程
- 首先,对接口请求参数中除了参数signature之外的所有非空数据元明文按照key的ascii顺序排序,并以&作为连接符拼接成待签名串(即key1=value1&key2=value2…);
- 然后,使用设备终端的 RSA 私钥对待签名串进行 SHA256 with RSA 签名;
- 最后,对签名做Base64编码,将编码后的签名串放在签名(signature)域里和其他报文域一起上送给系统。
注意:若请求参数中有需要加密的敏感信息,要先使用明文进行签名,再进行加密。
验签过程
- 首先,对接口返回参数中除了参数signature之外的所有非空数据元明文按照key的ascii顺序排序,并以&作为连接符拼接成待签名串(即key1=value1&key2=value2…);
- 然后,使用系统的 RSA 公钥对待签名串进行 SHA256 with RSA 签名验证。
注意:若返回报文中有加密的敏感信息存在,需先对敏感信息进行解密,得到敏感信息明文,再进行签名验证。
代码示例
- 参数拼接
Java 实现
java
/**
* 参数拼接
* @param paramMap 原数据
* @return 拼接后的字符串
*/
public static String sortMapToString(Map<String, String> paramMap) {
StringBuilder result = new StringBuilder();
Collection<String> keySet = paramMap.keySet();
List<String> list = new ArrayList<>(keySet);
Collections.sort(list);
for (String key : list) {
if ("signature".equals(key)) {
continue;
}
if (StringUtils.isNotBlank(paramMap.get(key))) {
result.append(key).append("=").append(paramMap.get(key)).append("&");
}
}
return result.substring(0, result.length() - 1);
}.NET 实现
.NET
public static string SortMapToString(IDictionary<string, string> paramMap)
{
if (paramMap == null || paramMap.Count == 0)
return string.Empty;
// 过滤掉signature参数和空值
var validParams = paramMap
.Where(kvp => !string.IsNullOrEmpty(kvp.Value) &&
!"signature".Equals(kvp.Key, StringComparison.OrdinalIgnoreCase))
.OrderBy(kvp => kvp.Key, StringComparer.Ordinal) // ASCII顺序排序
.Select(kvp => $"{kvp.Key}={kvp.Value}");
return string.Join("&", validParams);
}- 签名方法
Java 实现
java
/**
* 签名方法
* @param content 待签名内容
* @param privateKey 私钥
* @return 签名后数据(base64)
*/
public static String sign(String content, String privateKey) {
try {
PKCS8EncodedKeySpec priPKCS8 = new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKey.getBytes()));
KeyFactory factory = KeyFactory.getInstance("RSA");
PrivateKey priKey = factory.generatePrivate(priPKCS8);
Signature signature = Signature.getInstance("SHA256WithRSA");
signature.initSign(priKey);
signature.update(content.getBytes("UTF-8"));
byte[] signed = signature.sign();
return Base64.getEncoder().encodeToString(signed);
} catch (Exception e) {
logger.error("sign error", e);
}
return null;
}.NET 实现
.NET
public static string Sign(string content, string privateKey, Encoding encoding = null){
if (string.IsNullOrEmpty(content))
throw new ArgumentNullException(nameof(content));
if (string.IsNullOrEmpty(privateKey))
throw new ArgumentNullException(nameof(privateKey));
encoding ??= Encoding.UTF8;
try
{
byte[] privateKeyBytes = Convert.FromBase64String(privateKey);
byte[] dataBytes = encoding.GetBytes(content);
using var rsa = RSA.Create();
rsa.ImportPkcs8PrivateKey(privateKeyBytes, out _);
byte[] signatureBytes = rsa.SignData(dataBytes, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
return Convert.ToBase64String(signatureBytes);
}
catch (FormatException ex)
{
throw new ArgumentException("私钥格式不正确,请提供PKCS#8格式的Base64编码私钥", ex);
}
catch (Exception ex)
{
throw new InvalidOperationException($"签名失败: {ex.Message}", ex);
}
}- 验签方法
Java 实现
java
/**
* 验证签名
* @param content 待验签字符串
* @param sign 签名
* @param publicKey 公钥
* @return 验证结果
*/
public static boolean verify(String content, String sign, String publicKey) {
try {
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey pubKey = keyFactory.generatePublic(new X509EncodedKeySpec(Base64.getDecoder().decode(publicKey)));
Signature signature = Signature.getInstance("SHA256WithRSA");
signature.initVerify(pubKey);
signature.update(content.getBytes("UTF-8"));
return signature.verify(Base64.getDecoder().decode(sign));
} catch (Exception e) {
logger.error("sign error", e);
}
return false;
}.NET 实现
.NET
public static bool Verify(string content, string signature, string publicKey, Encoding encoding = null)
{
if (string.IsNullOrEmpty(content))
throw new ArgumentNullException(nameof(content));
if (string.IsNullOrEmpty(signature))
throw new ArgumentNullException(nameof(signature));
if (string.IsNullOrEmpty(publicKey))
throw new ArgumentNullException(nameof(publicKey));
encoding ??= Encoding.UTF8;
try
{
byte[] publicKeyBytes = Convert.FromBase64String(publicKey);
byte[] signatureBytes = Convert.FromBase64String(signature);
byte[] dataBytes = encoding.GetBytes(content);
using var rsa = RSA.Create();
rsa.ImportSubjectPublicKeyInfo(publicKeyBytes, out _);
return rsa.VerifyData(dataBytes, signatureBytes, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
}
catch (FormatException ex)
{
throw new ArgumentException("公钥或签名格式不正确", ex);
}
catch (Exception ex)
{
throw new InvalidOperationException($"验签失败: {ex.Message}", ex);
}
}