API签名生成
# 签名规则
所有参数名和参数值参与签名,Key(参数名) 进行排序
- 将所有参数的 Key(参数名) 按照
ASCII排序 - 按 Key(参数名) 的顺序,逐个取参数名和参数值进行拼接后,采用
RSA算法对字符串计算,算出签名字符串
# 示例说明
- 示例参数
| 参数名 | 参数值 |
|---|---|
| merchantCode | S820190712000002 |
| orderNum | T1231511321515 |
| orderAmount | 999.56 |
| callback | https://xxx/yyy |
| timestamp | 1745377181 |
- 参数格式
格式化规则说明:
- 提取传入键值对参数集合(Map)中的所有键,转换为字符串数组并按字典序升序排列。
- 遍历排序后的键数组,将每个键及其对应的值以 “键=值&” 的形式依次拼接到字符串中。
- 移除拼接后字符串末尾多余的 “&” 符号,最终得到格式化后的参数字符串。
- 上述规则已封装到TopPaySignUtil#paramFormat,调用即可得到如下source
source = callback=https://xxx/yyy&merchantCode=S820190712000002&orderAmount=999.56&orderNum=T1231511321515×tamp=1745377181
- 计算签名
- 使用您在 TopPay商户后台 配置的密钥对
- 用您的 私钥(Private Key) 和参数格式化后得到的source,作为入参调用TopPaySignUtil#sign,获取到最终的签名字符串
Sign = Jv0AMYiVSL/V8NxuBf8ZfHn7UyHO8TsU8Xkh2sqa0hbpKH1HSPampNXxzBn5PvJoytb8zPkHuQAMveTuBV5Ye8Qu+n8aw69 ...
# 代码示例
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.lang.RuntimeException;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Arrays;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
public class TopPaySignUtil {
private static final String RSA = "RSA";
private static final int MAX_ENCRYPT_BLOCK = 245; // 加密分块大小:ml-citation{ref="4" data="citationList"}
private static final int MAX_DECRYPT_BLOCK = 256; // 解密分块大小:ml-citation{ref="4" data="citationList"}
private static final String RSA_ECB_PKCS1_PADDING = "RSA/ECB/PKCS1Padding";
public static void main(String[] args) throws NoSuchAlgorithmException {
Map<String,String> params = new HashMap<>() ;
params.put("merchantCode", "S820190712000002") ;
params.put("orderAmount", "999.56") ;
params.put("orderNum", "T1231511321515") ;
params.put("callback", "https://xxx/yyy") ;
params.put("timestamp", "1745377181") ;
String source = paramFormat(params);
System.out.println("source: " + source);
KeyPairGenerator keyGen = KeyPairGenerator.getInstance(RSA);
keyGen.initialize(2048);
KeyPair pair = keyGen.generateKeyPair();
String pubKeyBase64 = Base64.getEncoder().encodeToString(pair.getPublic().getEncoded());
String priKeyBase64 = Base64.getEncoder().encodeToString(pair.getPrivate().getEncoded());
System.out.println("public key: " + pubKeyBase64);
System.out.println("private key: " + priKeyBase64);
String sign = sign(priKeyBase64, source);
System.out.println("sign: " + sign);
boolean verify = verify(pubKeyBase64, source, sign);
System.out.println("verify: " + verify);
}
/**
* 这段代码仅供参考,实际使用需要考虑
* 1.HttpClient 初始化参与
* 2.HttpResponse用完需要及时关闭
*/
public static String doPost(String url, String json) throws IOException {
HttpClient client = new DefaultHttpClient();
HttpPost post = new HttpPost(url);
StringEntity s = new StringEntity(json);
s.setContentEncoding("UTF-8");
s.setContentType("application/json");
post.setEntity(s);
HttpResponse res = client.execute(post);
if (res.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
return EntityUtils.toString(res.getEntity());// 返回json格式:
}
return null;
}
//参数格式化
private static String paramFormat(Map<String, String> param) {
String[] keys = param.keySet().toArray(new String[0]);
Arrays.sort(keys); //一定要先排序
StringBuilder builder = new StringBuilder();
for (String key : keys) {
String value = param.get(key);
if (value != null && !value.trim().isEmpty()) { //需要过滤null和空字符串
builder.append(key).append("=").append(value).append("&");
}
}
builder.deleteCharAt(builder.length()-1) ;
return builder.toString();
}
/**
* 计算签名
* @param priKeyBase64 base64编码的私钥
* @param source 用于计算签名的源数据
* @return rsa签名字符串
*/
public static String sign(String priKeyBase64, String source) {
return encrypt(source, stringToPrivateKey(priKeyBase64)) ;
}
/**
* 验证签名
* @param pubKeyBase64 base64编码的公钥
* @param source 用于计算签名的源数据
* @param sign 需要验证的签名
* @return rsa签名字符串
*/
public static boolean verify(String pubKeyBase64, String source, String sign) {
return source.equals(decrypt(sign, stringToPublicKey(pubKeyBase64)));
}
// RSA私钥加密
private static String encrypt(String plainData, PrivateKey privateKey) {
Cipher cipher = null; // 填充模式:ml-citation{ref="1,4" data="citationList"}
try {
cipher = Cipher.getInstance(RSA_ECB_PKCS1_PADDING);
cipher.init(Cipher.ENCRYPT_MODE, privateKey);
byte[] dataBytes = plainData.getBytes(StandardCharsets.UTF_8);
int inputLen = dataBytes.length;
ByteArrayOutputStream out = new ByteArrayOutputStream();
int offSet = 0;
// 分块加密处理:ml-citation{ref="4" data="citationList"}
while (inputLen - offSet > 0) {
int blockSize = Math.min(inputLen - offSet, MAX_ENCRYPT_BLOCK);
byte[] encryptedBlock = cipher.doFinal(dataBytes, offSet, blockSize);
out.write(encryptedBlock);
offSet += blockSize;
}
return Base64.getEncoder().encodeToString(out.toByteArray());
} catch (NoSuchAlgorithmException | NoSuchPaddingException | IOException | InvalidKeyException |
IllegalBlockSizeException | BadPaddingException e) {
throw new RuntimeException("encrypt error.");
}
}
// RSA公钥解密
private static String decrypt(String encryptedData, PublicKey publicKey) {
Cipher cipher = null;
try {
cipher = Cipher.getInstance(RSA_ECB_PKCS1_PADDING);
cipher.init(Cipher.DECRYPT_MODE, publicKey);
byte[] dataBytes = Base64.getDecoder().decode(encryptedData);
int inputLen = dataBytes.length;
ByteArrayOutputStream out = new ByteArrayOutputStream();
int offSet = 0;
// 分块解密处理:ml-citation{ref="4" data="citationList"}
while (inputLen - offSet > 0) {
int blockSize = Math.min(inputLen - offSet, MAX_DECRYPT_BLOCK);
byte[] decryptedBlock = cipher.doFinal(dataBytes, offSet, blockSize);
out.write(decryptedBlock);
offSet += blockSize;
}
return new String(out.toByteArray(), StandardCharsets.UTF_8);
} catch (NoSuchAlgorithmException | NoSuchPaddingException | IllegalBlockSizeException | BadPaddingException |
IOException | InvalidKeyException e) {
throw new RuntimeException("decrypt error.");
}
}
// 从Base64字符串恢复PublicKey
public static PublicKey stringToPublicKey(String publicKeyStr) {
byte[] keyBytes = Base64.getDecoder().decode(publicKeyStr);
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
try {
KeyFactory keyFactory = KeyFactory.getInstance(RSA);
return keyFactory.generatePublic(keySpec);
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
throw new RuntimeException("generatePublicKey error.");
}
}
// 从Base64字符串恢复PrivateKey
public static PrivateKey stringToPrivateKey(String privateKeyStr) {
byte[] keyBytes = Base64.getDecoder().decode(privateKeyStr);
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
try {
KeyFactory keyFactory = KeyFactory.getInstance(RSA);
return keyFactory.generatePrivate(keySpec);
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
throw new RuntimeException("generatePrivateKey error.");
}
}
}