签名&验签算法
# 密钥说明
- 密钥算法:
RSA
- RSA密钥格式:
pkcs#8
- RSA密钥长度:
2048 bit
# 密钥生成
- 在线生成工具:第三方在线密钥生成工具网址 (opens new window) (选2048位PKCS#8格式)
- 离线生成工具:openssl工具
# 密钥使用
- 商户私钥,对请求参数进行签名,签名值放入sign值中,请求时一起提交给CheezeePay
- 商户公钥,请查看商户公钥配置说明
# 签名算法说明
- 签名算法:
SHA256WithRSA
,Java对应算法枚举类型:SHA256
,PHP对应算法枚举类型:OPENSSL_ALGO_SHA256
# 加签&验签字符串获取步骤
- 获取请求参数内容,剔除
sign
字段,剔除值为空的参数; - 按照第一个字符的键值 ASCII 码递增排序(字母升序排序),如果遇到相同字符则按照第二个字符的键值 ASCII 码递增排序,以此类推;
- 将排序后的参数与其对应值,组合成 参数=参数值 的格式,并且把这些参数用
&
字符连接起来,此时生成的字符串为待签名字符串。
# 加签示例
# 参数示例值
{
"appId":"B2iwKFIM4tvo",
"merchantId":"CH10001165",
"mchOrderNo":"20240123181928",
"paymentMethod":"BANK_TH",
"payeeAccountInfos":"{\"bankCode\":\"001\",\"accountNumber\":\"1708099097\"}",
"amount":"200",
"name":"CheezeePayTest",
"phone":"9123456789",
"email":"[email protected]",
"notifyUrl":"https://yourdomain/notifyUrl",
"timestamp":1706005168776
}
# 拼接后的待签名字符串
amount=200&appId=B2iwKFIM4tvo&email=9123456789@qq.com&mchOrderNo=20240123181928&merchantId=CH10001165&name=CheezeePayTest¬ifyUrl=https://yourdomain/notifyUrl&payeeAccountInfos={"bankCode":"001","accountNumber":"1708099097"}&paymentMethod=BANK_TH&phone=9123456789×tamp=1706005168776
# 生成签名
使用您生成的商户私钥进行加签获取到最终的签名字符串。
# 验证签名
使用您在CheezeePay商户后台获取的平台公钥对sign进行验签。
# 签名工具Demo
package com.cheesepay;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.*;
public class CheeseTradeRSAUtil {
private static final String DATA = "data";
// 生成签名
public static String getSign(Map<String, Object> params, String privateKey) throws Exception {
params.remove("sign");
String textContent = getContent(params);
return sign(textContent, privateKey);
}
// 验证签名
public static boolean verifySign(Map<String, Object> params, String publickey) throws
Exception {
String platSign= (String)params.remove("sign");
String textContent = getContent(params);
return verify(textContent, platSign, publickey);
}
// 拼接待签名的字符串
private static String getContent(Map<String, Object> params) {
List<String> paramNameList = new ArrayList<>(params.keySet());
Collections.sort(paramNameList);
StringBuilder stringBuilder = new StringBuilder();
for (String name : paramNameList) {
if(ObjectUtil.isNotEmpty(params.get(name))) {
if (params.get(name) != null ) {
if (DATA.equals(name)){
String jsonString = getJsonStringByJackson(params.get(name));
stringBuilder.append(name).append("=").append(jsonString).append("&");
}else {
stringBuilder.append(name).append("=").append(params.get(name)).append("&");
}
}
}
}
String content = stringBuilder.toString();
content = content.substring(0, content.length() - 1);
return content;
}
// Jackson方式序列化对象
private static String getJsonStringByJackson(Object param) {
// 使用Jackson方式序列化对象
ObjectMapper objectMapper = new ObjectMapper();
try {
return objectMapper.writeValueAsString(param);
} catch (Exception ex) {
return null;
}
}
// 使用私钥生成签名
public static String sign(String message, String privateKeyString) throws Exception {
byte[] privateKeyBytes = java.util.Base64.getDecoder().decode(privateKeyString);
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(privateKeyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PrivateKey privateKey = keyFactory.generatePrivate(spec);
Signature signer = Signature.getInstance("SHA256withRSA");
signer.initSign(privateKey);
signer.update(message.getBytes());
byte[] signatureBytes = signer.sign();
return java.util.Base64.getEncoder().encodeToString(signatureBytes);
}
// 使用公钥验签
public static boolean verify(String message, String signatureString, String publicKeyString) throws Exception {
byte[] publicKeyBytes = java.util.Base64.getDecoder().decode(publicKeyString);
byte[] signatureBytes = java.util.Base64.getDecoder().decode(signatureString);
X509EncodedKeySpec spec = new X509EncodedKeySpec(publicKeyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey publicKey = keyFactory.generatePublic(spec);
Signature verifier = Signature.getInstance("SHA256withRSA");
verifier.initVerify(publicKey);
verifier.update(message.getBytes());
return verifier.verify(signatureBytes);
}
public static void main(String[] args) throws Exception {
// RSA公钥
String publicKeyString = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv8ICamtlh1E6ycHa7TooPJcLij2i+Otv9axeOafsYtubqBlBMojrQdDguhy3j1H5BmMz1Czx/ztMheRplpIUrs95Siv17V0nkXUCMAEO5WJYX3SY0y1Qu5sJA0WXhXj8G5wF+aQtIemMX9Im4wIJZFtneOQLHKLmbfdcSMYH9FSzWfXR3vEe4/ITyjhqTYvu9PU4ZkWUFR0CzfusPpqyA+yclgUm239m1VnO1AZRwpLncIxIlv6/egnn07pG9EooGktF6alhCmB3jVktAz/2uTlA81zIun6hxMzD2urjWGy6Tlta8TITIVPe1vKS2AW2tE/QSOxf8brKM8VQ1XkGwwIDAQAB";
// RSA私钥
String privateKeyString = "MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC/wgJqa2WHUTrJwdrtOig8lwuKPaL462/1rF45p+xi25uoGUEyiOtB0OC6HLePUfkGYzPULPH/O0yF5GmWkhSuz3lKK/XtXSeRdQIwAQ7lYlhfdJjTLVC7mwkDRZeFePwbnAX5pC0h6Yxf0ibjAglkW2d45AscouZt91xIxgf0VLNZ9dHe8R7j8hPKOGpNi+709ThmRZQVHQLN+6w+mrID7JyWBSbbf2bVWc7UBlHCkudwjEiW/r96CefTukb0SigaS0XpqWEKYHeNWS0DP/a5OUDzXMi6fqHEzMPa6uNYbLpOW1rxMhMhU97W8pLYBba0T9BI7F/xusozxVDVeQbDAgMBAAECggEAdZ4EaU3yemuCiZoUNIoFgBSNiX+A5PlUNPZC3U54mbJl6VeEPADre/Uowj82//uhqR9T/QKMdKbkqwONGEQF16t+k9YfBDatPHTuoI8lmeEWn4Ye7vjOmiPgBVe8NqwcxrqOl67x1+kupt955qerJxlBgE8v2aK5gB3HRwPggSYZgSZJ3X2zQMJ+XN4qe49Un+utqFuGtVRwPQvs/Kazp5GL7WlR0OnpQ2KZ+ur9T58VfTKmIeqBckey7nIrb/fdodJ7IqcykWjSLmy8p84W78iiRDfPn81Ujb231EWW+AbuV399aXBZ4oaR4q8wwkhe9xHUADWLvcfCnMrB40LkEQKBgQD2TEeBEB5Gwj/zpriq23xKJyzfo0hOxzGY/Ol2EykGeEYhny/YiTlMYtOMy37RiGKr4lN7G/pKl2fI5kRjgQM1QXgx6dxd11D0rORXj+SAy6+zAq3NVY4c3zDKmrQQ8x3f2QFffo+TmLtw/BH0Frp+a2lzKzIcXtZ4/akJdVIihwKBgQDHT7xvOaNyAPnnYwpROMI9eUVgFc9pMRAmQ18at8ggZ8M+Ww1nhr00loufzkovCb9MvGR4HSedWHfgmtDu1hUFtl7PItNItsB347yWPxR/lVLs44NVXt7jkFisoBoEBXaNVVt6uOSvBiqINmX3gm4FoisI2SRZsH7nMr07OXO85QKBgCkXdN6Nh9+aTP0jla+7yrK8cnolTc0G4rl5iKHQdInFyz2Ux3DDBDJAUrcsxE3FCWFP80mY01tag1A7SrGnNpfOAnWhZMirQbBwn/AT67WpRfkBu2mEmp8qhYNn6B7j0Nol5FrATMf9NViWVZB5skehOAik7klZcULiXy9ayYdFAoGAA+0bLim5gYZpZdh6nIz8ro9UAYeMdWsFAWv0VKdoJtNEclcC1ZY+5elNSNGcfn63qQBRDlisrhnPCrqiAQJwmZM1HOl3tgf7lKEE1wTZF4ZOguIcdTMQOvVd+dCRkqC07CXRToKT5qq9bb98lLqaBjxikqMMZ0PlzBIgzijckc0CgYAH16CziStEZVvKbiR8b0ftF9mf6V8I0KHianMgV8X6Y51aE0Ig1eQhIvqSjqtBac7gEppiu3OQVrp39uLdgyvDSVta1Hq9QYYTDNt8crt69aD/Th1h5esYvApYyZIhHbFDR5meE4Y3nHpfY7J8zhmfyaVW/6cuwPaToTtrEjQmWQ==";
// 请求接口参数
Map<String, Object> params = new HashMap<>();
params.put("a","A");
params.put("b","BB");
// 使用私钥生成签名
String platSign = getSign(params, privateKeyString);
System.out.println("sign: " + platSign);
params.put("sign",platSign);
// 使用公钥验证签名
boolean verified = verifySign( params, publicKeyString);
System.out.println("Signature verified: " + verified);
}
}