生成签名及加验签
生成签名并对请求加签
在发送请求给万里汇前,集成商需要对信息进行加签。
下图为生成签名的流程:
步骤一:生成密钥对
在加签前,集成商需要生成一对公钥和私钥。生成一对RSA2密钥的示例方法如下:
- 执行以下指令,private.pem文件作为输出文件生成私钥:
copy
openssl genrsa -out private.pem 2048
- 执行以下指令生成公钥,其中private.pem为上一步骤中生成的私钥:
copy
openssl rsa -in private.pem -pubout -out public.pem
- 若需要PKCS8格式的密钥,可以执行以下操作将上述生成的RSA私钥转为PKCS8格式私钥:
copy
openssl pkcs8 -topk8 -inform PEM -in private.pem -out private_pkcs8.pem -nocrypt
步骤二:生成待加签数据
按照以下规则生成待加签数据::
copy
<HTTP-Method> <Request-URL-Endpoint>
<Client-ID>.<Request-Time>.<Request-Body>
例如,当请求信息的请求体(<Request-Body>)为以下内容时:
copy
{
"removeBeneficiaryRequestId":"*****",
"beneficiaryToken":"ALIPAYqwertyuiopoiuytrewqwertyuiopoiuytr*****",
"customerId":"*****"
}
集成商需要生成下述格式的数据:
copy
POST /v1/business/account/removeBeneficiary
5Y60382Z2Y4S*****.2022-04-28T12:31:30+08:00.{"removeBeneficiaryRequestId":"*****","beneficiaryToken":"*****","customerId":"*****"}
其中:
- HTTP调用方法(<HTTP-Method>) 为
POST
。 - 请求URLEndpoint(<Request-URL-Endpoint>) 为
/v1/business/account/removeBeneficiary
- Client ID(<Client-ID>) 为
5Y60382Z2Y4S*****
。 - 请求时间(<Request-Time>) 为
2022-04-28T12:31:30+08:00
,时间遵循ISO 8601标准。 - 请求信息为
{\"removeBeneficiaryRequestId\":\"*****\",\"beneficiaryToken\":\"*****\",\"customerId\":\"*****\"}
.
步骤三:生成签名
对步骤二生成的待加签数据,使用SHA256算法生成Hash值。然后使用Base64算法对hash值进行编码。
使用SHA256方法处理待加签数据,生成hash值。然后使用Base64对hash值进行编码。
copy
def RSA_sign(data,privateKey):
private_keyBytes = base64.b64decode(privateKey)
priKey = RSA.importKey(private_keyBytes)
signer = PKCS1_v1_5.new(priKey)
hash_obj = SHA256.new(data.encode('utf-8'))
signature = base64.b64encode(signer.sign(hash_obj))
return signature
privateKey
:步骤一中生成的私钥data
:步骤二中生成的待加签数据
生成的签名 (signature
)范例:
copy
KEhXthj4bJ8...*****
步骤四:加签
按照以下规则生成请求头中的完整签名部分:
copy
'Signature: algorithm=<algorithm>, keyVersion=<key-version>, signature=<signature>'
algorithm
: 生成签名使用的算法,默认值为RSA256
。keyVersion
: 加验签使用的密钥版本signature
: 步骤三中生成的签名内容。
注:对信息进行加签后,集成商可将请求信息发送至万里汇。请求发送方法详见快速上手页面。
对响应信息验签
集成商收到响应后,需要对完成响应信息的验签,具体流程如下图所示:
步骤一:获取万里汇公钥
联系万里汇技术支持获取公钥,或登录开发者中心下载公钥。
copy
MIIBIjANBgkqhkiG9...*****
步骤二:生成待验证数据
本步骤与加签的“生成待加签数据”步骤规则相同,集成商通过本步骤处理返回体,生成待验证数据。并准备对数据进行验证。
步骤三:获取签名
万里汇按照下述格式生成字符串类型的签名:
copy
'Signature: algorithm=<algorithm>, keyVersion=<key-version>, signature=<signature>'
algorithm
: 生成签名使用的算法,默认值为RSA256
。keyVersion
: 加验签使用的密钥版本signature
: 需要验证的签名内容
返回头中包含的字符串类型签名完整格式如下:
copy
signature: algorithm=RSA256, keyVersion=2, signature=KEhXthj4b...*****
步骤四:验证签名
对步骤二中准备的待验证数据,使用SHA256
算法处理待验证数据,生成hash值。
用公钥对步骤三中获取的签名进行解密。签名需要用Base64
算法进行解码。
例:
copy
def verify(httpMethod, uriWithQueryString, clientId, timeString, content,signature,publicKey):
public_keyBytes = base64.b64decode(publicKey)
pubKey = RSA.importKey(public_keyBytes)
data = sign(httpMethod, uriWithQueryString, clientId, timeString, content)
ssda =data.encode('utf-8')
h = SHA256.new(ssda)
verifier = PKCS1_v1_5.new(pubKey)
return verifier.verify(h, base64.b64decode(signature))
publicKey
: 步骤一中从万里汇获取的公钥。data
: 步骤二中生成的待验证数据。signature
: 步骤三中截取的签名内容
示例代码中,验证完成后返回布尔值。若返回值为false
,则签名与数据不一致。验证失败可能因为:密钥对不匹配,或步骤二中生成的待验证数据(data
)有误。
示例代码
Python 示例代码
copy
import base64
import warnings
import urllib.parse
import flask
from Crypto.Hash import SHA256
from Crypto.PublicKey import RSA
from Crypto.Signature import PKCS1_v1_5
warnings.filterwarnings("ignore")
NAME_VALUE_SEPARATOR = "="
COMMA = ","
ALGORITHM = "algorithm"
SIGNATURE = "signature"
KEY_VERSION = "keyVersion"
RSA_256 = "RSA256"
server = flask.Flask(__name__) # __name__ represents the current python file.
#Generate signature and sign a request
def RSA_sign(data,privateKey):
private_keyBytes = base64.b64decode(privateKey)
priKey = RSA.importKey(private_keyBytes)
signer = PKCS1_v1_5.new(priKey)
hash_obj = SHA256.new(data.encode('utf-8'))
signature = base64.b64encode(signer.sign(hash_obj))
return signature
def sign(httpMethod, uriWithQueryString, clientId, timeString, reqBody):
reqContent = httpMethod + " " + uriWithQueryString + "\n" + clientId + "." + timeString + "." + reqBody
return reqContent
#Validate the signature of a response
def verify(httpMethod, uriWithQueryString, clientId, timeString, content,signature,publicKey):
public_keyBytes = base64.b64decode(publicKey)
pubKey = RSA.importKey(public_keyBytes)
data = sign(httpMethod, uriWithQueryString, clientId, timeString, content)
ssda =data.encode('utf-8')
h = SHA256.new(ssda)
verifier = PKCS1_v1_5.new(pubKey)
return verifier.verify(h, base64.b64decode(signature))
@server.route('/signature/add', methods=['post'])
def signatureAdd():
content = str(flask.request.get_data())
content = content[2:-1]
httpMethod = flask.request.headers['httpMethods']
uriWithQueryString =flask.request.headers['uriWithQueryString']
clientId = flask.request.headers['clientId']
timeString = flask.request.headers['timeString']
privateKey = flask.request.headers['privateKey']
data = sign(httpMethod, uriWithQueryString, clientId, timeString, content)
res_sign1 = RSA_sign(data,privateKey)
signature = res_sign1.decode('utf-8')
values = {}
values["signature"] = signature
data = urllib.parse.urlencode(values)
res = ALGORITHM + NAME_VALUE_SEPARATOR + RSA_256 + COMMA + KEY_VERSION + NAME_VALUE_SEPARATOR + "1" + COMMA + data
return res
@server.route('/signature/verification', methods=['post'])
def verification():
content = str(flask.request.get_data())
content = content[2:-1]
httpMethod = flask.request.headers['httpMethods']
uriWithQueryString = flask.request.headers['uriWithQueryString']
clientId = flask.request.headers['clientId']
timeString = flask.request.headers['timeString']
publicKey = flask.request.headers['publicKey']
signature = flask.request.headers['signature']
databoolea = verify(httpMethod, uriWithQueryString, clientId, timeString, content,urllib.parse.unquote(signature),publicKey)
return str(databoolea)
if __name__ == '__main__':
server.run(port=5001, debug=True,host='0.0.0.0') #Default port is 5000.
C# 示例代码
copy
using System.Text;
using System.Web;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Security;
namespace test_0505
{
class Program
{
static string signPrivateKey = "MIIEvgIBADANBgkqhkiG9w0B...*****";
static string verifyPublicKey = "MIIBIjA...*****";
static void Main(string[] args)
{
string httpMethod = "POST";
string uriWithQueryString = "/amsin/api/v1/business/account/inquiryBalance";
string clientId = "*****";
string timeString = "2022-04-28T12:31:30+08:00";
string content = "{\"customerId\":\"*****\"}";
string reqContent = httpMethod + " " + uriWithQueryString + "\n" + clientId + "." + timeString + "." + content;
Console.WriteLine(reqContent);
string signature = Sign(reqContent, signPrivateKey);
Console.WriteLine(signature);
bool result = ValidationPublicKey(reqContent, signature, verifyPublicKey);
if(result){
Console.WriteLine("verify success");
}
}
static string Sign(string contentForSign, string privateKey)
{
AsymmetricKeyParameter priKey = GetPrivateKeyParameter(privateKey);
byte[] byteData = System.Text.Encoding.UTF8.GetBytes(contentForSign);
ISigner normalSig = SignerUtilities.GetSigner("SHA256WithRSA");
normalSig.Init(true, priKey);
normalSig.BlockUpdate(byteData, 0, contentForSign.Length);
byte[] normalResult = normalSig.GenerateSignature();
var arr = Convert.ToBase64String(normalResult);
Console.WriteLine(arr);
return UrlEncode(arr, Encoding.UTF8);
}
static AsymmetricKeyParameter GetPrivateKeyParameter(string s)
{
s = s.Replace("\r", "").Replace("\n", "").Replace(" ", "");
byte[] privateInfoByte = Convert.FromBase64String(s);
AsymmetricKeyParameter priKey = PrivateKeyFactory.CreateKey(privateInfoByte);
return priKey;
}
static string UrlEncode(string temp, Encoding encoding)
{
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < temp.Length; i++)
{
string t = temp[i].ToString();
string k = HttpUtility.UrlEncode(t, encoding);
if (t == k)
{
stringBuilder.Append(t);
}
else
{
stringBuilder.Append(k.ToUpper());
}
}
return stringBuilder.ToString();
}
static bool ValidationPublicKey(string plainData, string sign, string key)
{
AsymmetricKeyParameter pubKey = GetPublicKeyParameter(key);
string newSign = HttpUtility.UrlDecode(sign, Encoding.UTF8);
Console.WriteLine(newSign);
byte[] plainBytes = Encoding.UTF8.GetBytes(plainData);
ISigner verifier = SignerUtilities.GetSigner("SHA256WithRSA");
verifier.Init(false, pubKey);
verifier.BlockUpdate(plainBytes, 0, plainBytes.Length);
byte[] signBytes = Convert.FromBase64String(newSign);
return verifier.VerifySignature(signBytes);
}
static AsymmetricKeyParameter GetPublicKeyParameter(string s)
{
s = s.Replace("\r", "").Replace("\n", "").Replace(" ", "");
byte[] publicInfoByte = Convert.FromBase64String(s);
AsymmetricKeyParameter pubKey = PublicKeyFactory.CreateKey(publicInfoByte);
return pubKey;
}
}
}
Java 示例代码
copy
package com.alibaba.test;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
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 java.util.Base64;
public class Demo {
/**
* name and value separator
*/
private static final String NAME_VALUE_SEPARATOR = "=";
/**
* comma
*/
private static final String COMMA = ",";
/**
* algorithm
*/
private static final String ALGORITHM = "algorithm";
/**
* signature
*/
private static final String SIGNATURE = "signature";
/**
* keyVersion
*/
private static final String KEY_VERSION = "keyVersion";
/**
* RSA256
*/
private static final String RSA_256 = "RSA256";
private static final String signPrivateKey = "MIIEvwIBADANBgkqhkiG...*****";
private static final String verifyPublicKey = "MIIBIjANBg...*****";
public static void main(String[] args) throws Exception {
String httpMethod = "POST";
String uriWithQueryString = "/amsin/api/v1/business/account/inquiryBalance";
String clientId = "*****";
String timeString = "2022-03-02T15:03:30+08:00";
String content = "{\"transferFactor\":{\"transferFundType\":\"GLOBAL_WORLDFIRST\"},\"currency\":\"USD\"}";
String signature = sign(httpMethod, uriWithQueryString, clientId, timeString, content, signPrivateKey);
String signatureHeaderPayload = ALGORITHM +
NAME_VALUE_SEPARATOR +
RSA_256 +
COMMA +
KEY_VERSION +
NAME_VALUE_SEPARATOR +
"1" +
COMMA +
SIGNATURE +
NAME_VALUE_SEPARATOR +
signature;
System.out.println("signatureHeaderPayload:\n" + signatureHeaderPayload);
boolean res = verify(httpMethod, uriWithQueryString, clientId, timeString, content, signature, verifyPublicKey);
if (res) {
System.out.println("verify success.");
}
}
/**
* Sign the contents of the merchant request
*
* @param httpMethod http method e.g., POST, GET
* @param uriWithQueryString query string in url e.g., if your request url is https://{domain_name}.com/ams/api/pay/query uriWithQueryString should be /ams/api/pay/query not https://{domain_name}.com/ams/api/pay/query
* @param clientId clientId issued by WorldFirst e.g., *****
* @param timeString "request-time" in request e.g., 2020-01-03T14:36:27+08:00
* @param reqBody json format request e.g., "{"paymentRequestId":"*****","refundRequestId":"*****","refundAmount":{"currency":"USD","value":"123"},"extendInfo":{"":""}}"
* @param merchantPrivateKey your private key
*/
public static String sign(
String httpMethod,
String uriWithQueryString,
String clientId,
String timeString,
String reqBody,
String merchantPrivateKey) throws Exception {
// 1. construct the request content
String reqContent = httpMethod + " " + uriWithQueryString + "\n" + clientId + "." + timeString + "." + reqBody;
System.out.println("reqContent is " + "\n" + reqContent);
// 2. sign with your private key
String originalString = signWithSHA256RSA(reqContent, merchantPrivateKey);
// System.out.println("originalString is " + originalString);
// 4. return the encoded String
return URLEncoder.encode(originalString, "UTF-8");
}
/**
* Check the response of WorldFirst
*
* @param httpMethod http method e.g., POST, GET
* @param uriWithQueryString query string in url e.g., if your request url is https://{domain_name}.com/ams/api/pay/query uriWithQueryString should be /ams/api/pay/query not https://{domain_name}.com/ams/api/pay/query
* @param clientId clientId issued by WorldFirst e.g., *****
* @param timeString "response-time" in response e.g., 2020-01-02T22:36:32-08:00
* @param rspBody json format response e.g., "{"acquirerId":"xxx","refundAmount":{"currency":"CNY","value":"123"},"refundFromAmount":{"currency":"JPY","value":"234"},"refundId":"xxx","refundTime":"2020-01-03T14:36:32+08:00","result":{"resultCode":"SUCCESS","resultMessage":"success","resultStatus":"S"}}"
* @param worldfirstPublicKey public key from WorldFirst
*/
public static boolean verify(
String httpMethod,
String uriWithQueryString,
String clientId,
String timeString,
String rspBody,
String signature,
String worldfirstPublicKey) throws Exception {
// 1. construct the response content
String responseContent = httpMethod + " " + uriWithQueryString + "\n" + clientId + "." + timeString + "." + rspBody;
// 2. decode the signature string
String decodedString = URLDecoder.decode(signature, "UTF-8");
// 3. verify the response with WorldFirst's public key
return verifySignatureWithSHA256RSA(responseContent, decodedString, worldfirstPublicKey);
}
/**
* Generate base64 encoded signature using the sender's private key
*
* @param reqContent: the original content to be signed by the sender
* @param strPrivateKey: the private key which should be base64 encoded
*/
private static String signWithSHA256RSA(String reqContent, String strPrivateKey) throws Exception {
Signature privateSignature = Signature.getInstance("SHA256withRSA");
privateSignature.initSign(getPrivateKeyFromBase64String(strPrivateKey));
privateSignature.update(reqContent.getBytes(StandardCharsets.UTF_8));
byte[] bytes = privateSignature.sign();
return Base64.getEncoder().encodeToString(bytes);
}
private static PrivateKey getPrivateKeyFromBase64String(String privateKeyString) throws Exception {
byte[] b1 = Base64.getDecoder().decode(privateKeyString);
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(b1);
KeyFactory kf = KeyFactory.getInstance("RSA");
return kf.generatePrivate(spec);
}
/**
* Verify if the received signature is correctly generated with the sender's public key
*
* @param rspContent: the original content signed by the sender and to be verified by the receiver.
* @param signature: the signature generated by the sender
* @param strPk: the public key string-base64 encoded
*/
private static boolean verifySignatureWithSHA256RSA(String rspContent, String signature, String strPk) throws Exception {
PublicKey publicKey = getPublicKeyFromBase64String(strPk);
Signature publicSignature = Signature.getInstance("SHA256withRSA");
publicSignature.initVerify(publicKey);
publicSignature.update(rspContent.getBytes(StandardCharsets.UTF_8));
byte[] signatureBytes = Base64.getDecoder().decode(signature);
return publicSignature.verify(signatureBytes);
}
private static PublicKey getPublicKeyFromBase64String(String publicKeyString) throws Exception {
byte[] b1 = Base64.getDecoder().decode(publicKeyString);
X509EncodedKeySpec X509publicKey = new X509EncodedKeySpec(b1);
KeyFactory kf = KeyFactory.getInstance("RSA");
return kf.generatePublic(X509publicKey);
}
}
JavaScript 示例代码
copy
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title></title>
<script src="https://cdn.bootcss.com/crypto-js/3.1.9-1/crypto-js.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/jsencrypt/3.2.1/jsencrypt.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/jsrsasign/10.5.13/jsrsasign-all-min.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/2.2.4/jquery.min.js"></script>
</head>
<body>
<div>
<div>
<div>Signature:</div>
<div>client-id</div>
<div>
<input id="clientId" type="text" />
</div>
<div>request-time</div>
<div>
<input id="requestTime" type="text"></input>
</div>
<div>httpMethod</div>
<div>
<input id="httpMethod" type="text" />
</div>
<div>api</div>
<div>
<input id="api" type="text" />
</div>
<div>body</div>
<div>
<input id="bodyData" type="text" />
</div>
<div>Private Key:</div>
<div>
<input id="privkey" type="text" />
</div>
</div>
<button onclick="getUrlceshi();">Generate the signature</button>
</div>
</body>
<script>
function getUrlceshi() {
var cid = $("#clientId").val();
var requestTime = $("#requestTime").val();
var httpMethod = $("#httpMethod").val();
if (httpMethod == '' || httpMethod == undefined) {
httpMethod = "POST"
}
var urlApi = $("#api").val();
var bodyData = $("#bodyData").val();
//Private Key
var privkey = $("#privkey").val();
console.log(privkey);
//Assemble the data to be signed
var signatureBody = httpMethod + " " + urlApi + "\n" + cid + "." + requestTime + "." + bodyData;
console.log(signatureBody);
var signatureData = RsaSign(privkey, signatureBody)
console.log(signatureData);
}
//Signature
function RsaSign(privKey, plainText) {
var signStr = new JSEncrypt();
//Set the Private Key
// signStr.setPrivateKey('-----BEGIN RSA PRIVATE KEY-----'+privKey+'-----END RSA PRIVATE KEY-----');
signStr.setPrivateKey(privKey)
//Sign the request with the signature
var signature = signStr.sign(plainText, CryptoJS.SHA256, "sha256");
return encodeURIComponent(signature);
}
//Validate the signature
function RsaVerify(plainText, signature) {
var verify = new JSEncrypt();
//Set the Public Key
// verify.setPublicKey('-----BEGIN PUBLIC KEY-----' + pubKey + '-----END PUBLIC KEY-----');
verify.setPublicKey(pubKey)
//Validate the signature
var verified = verify.verify(plainText, signature, CryptoJS.SHA1);
return verified;
}
</script>
</html>