Sign a request and validate the signature
Sign a request
Before sending WorldFirst a request message, you need to sign the request message first.
The following figure illustrates the process to sign a request:
Figure 1. Message signing process
Step 1. Obtain the private key
The Partner needs to sign the request with a private key. Use the following method to generate an RSA2 key pair:
1. Generate a private key while using the private.pem file as the output file:
openssl genrsa -out private.pem 2048
2. Generate a public key with the following command, where private.pem is the output from the command above.
openssl rsa -in private.pem -pubout -out public.pem
3. If you need the PKCS8 format key, you can use the following commands to convert the RSA private key generated in the previous step to the PKCS8 format private key:
openssl pkcs8 -topk8 -inform PEM -in private.pem -out private_pkcs8.pem -nocrypt
Step 2. Construct the data to be signed
Construct the data to be signed with the following rules:
<HTTP-Method> <Request-URL-Endpoint>
<Client-ID>.<Request-Time>.<Request-Body>
For example, given an API request message with the following <Request-Body>:
{
"removeBeneficiaryRequestId":"*****",
"beneficiaryToken":"ALIPAYqwertyuiopoiuytrewqwertyuiopoiuytr*****",
"customerId":"*****"
}
You need to generate the following data to be signed:
POST /v1/business/account/removeBeneficiary
5Y60382Z2Y4S*****.2022-04-28T12:31:30+08:00.{"removeBeneficiaryRequestId":"******","beneficiaryToken":"ALIPAYqwertyuiopoiuytrewqwertyuiopoiuytr*****","customerId":"*****"}
where,
- <HTTP-Method> is
POST
. - <Request-URL-Endpoint> is
/v1/business/account/removeBeneficiary
- In this example, the full request URL is
https://{domain_name}.com/v1/business/account/removeBeneficiary
.
- <Client-ID> is
5Y60382Z2Y4S*****
. - <Request-Time> is
2022-04-28T12:31:30+08:00
. The format of this timestamp follows ISO 8601 standards and is accurate to seconds. - The request message is
{\"removeBeneficiaryRequestId\":\"*****\",\"beneficiaryToken\":\"ALIPAYqwertyuiopoiuytrewqwertyuiopoiuytr*****\",\"customerId\":\"*****\"}
.
Step 3. Generate the signature
Hash the data to be signed with the SHA256 method. Sign the hash with the private key, and subsequently encode with the Base64 method.
For example:
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
: the private key obtained in Step 1.data
: the data to be signed which is created in Step 2.
The following code sample describes the generated signature (signature
):
KEhXthj4bJ8*****
Step 4. Sign the request
Assemble the signature field in the request header with the following syntax:
'Signature: algorithm=<algorithm>, keyVersion=<key-version>, signature=<signature>'
algorithm
: Specify the algorithm used to generate the signature. The default value isRSA256
.keyVersion
: Specify the key version that is used to generate or validate the signature.signature
: the generated signature in Step 3.
Note: After properly signing the message, you can send the request message to WorldFirst. For more details about how to send a request, refer to the Getting started with WorldFirst APIs chapter. |
Validate the signature of a response
After you receive a response, you need to validate the signature of the response.
The following figure illustrates the process to validate the signature:
Figure 2. Signature validation process
Step 1. Obtain the platform public key
Contact the WorldFirst Support Team or log into the WorldFirst Developer Center to obtain a public key. For example:
MIIBIjANBgkqhkiG9*****
Step 2. Construct the data to be validated
The process in this step is functionally equivalent to the Construct the data to be signed step. Instead of signing the data and sending the message to WorldFirst, the Partner needs to process the message from WorldFirst and validate the data.
Step 3. Get the signature
The signature string is assembled with the following syntax:
'Signature: algorithm=<algorithm>, keyVersion=<key-version>, signature=<signature>'
algorithm
: Specify the algorithm used to generate the signature. The default value isRSA256
.keyVersion
: Specify the key version that is used to generate or validate the signature.signature
: the signature that is needed to be validated.
A sample signature string from a response header has the following structure:
signature: algorithm=RSA256, keyVersion=2, signature=KEhXthj4bJ8*****
Step 4. Validate the signature
Hash the data to be validated with the SHA256 method. Subsequently, decode the signature with the base64 algorithm, and decrypt the signature with the public key. Compare whether the result matches or not.
For example:
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
: the platform public key obtained in Step 1.data
: the data to be validated which is created in Step 2.signature
: the signature that is obtained in Step 3.
The sample code returns a boolean value. If the returned value is false
, it means the signature is not valid. The failure could be caused by either an unmatching pair of public and private keys or failure to construct valid data
in Step 2.
Demo code
Python Demo code
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# Demo code
using System.Text;
using System.Web;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Security;
namespace test_0505
{
class Program
{
static string signPrivateKey = "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCB*****";
static string verifyPublicKey = "MIIBIjANBgkq*****";
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 Demo code
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 = "MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAg*****";
private static final String verifyPublicKey = "MIIBIjANBg*****";
public static void main(String[] args) throws Exception {
String httpMethod = "POST";
String uriWithQueryString = "/aps/api/business/fund/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://open-na.alipay.com/ams/api/pay/query uriWithQueryString should be /ams/api/pay/query not https://open-na.alipay.com/ams/api/pay/query
* @param clientId clientId issued by Alipay e.g., 112233445566
* @param timeString "request-time" in request e.g., 2020-01-03T14:36:27+08:00
* @param reqBody json format request e.g., "{"paymentRequestId":"xxx","refundRequestId":"xxx","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 Alipay
*
* @param httpMethod http method e.g., POST, GET
* @param uriWithQueryString query string in url e.g., if your request url is https://open-na.alipay.com/ams/api/pay/query uriWithQueryString should be /ams/api/pay/query not https://open-na.alipay.com/ams/api/pay/query
* @param clientId clientId issued by Alipay e.g., 112233445566
* @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 alipayPublicKey public key from Alipay
*/
public static boolean verify(
String httpMethod,
String uriWithQueryString,
String clientId,
String timeString,
String rspBody,
String signature,
String alipayPublicKey) 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 Alipay's public key
return verifySignatureWithSHA256RSA(responseContent, decodedString, alipayPublicKey);
}
/**
* 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 Demo code
<!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>