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 create a signature:
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 content to be signed
Take a request as an example, which includes the following properties:
Request-URI
: aps/api/v1/payments/payClient-Id
: *****Request-Time
: 2019-05-28T12:12:12+08:00Request-Body
: The following code sample describes the body format. For example:
{
"order":{
"orderId":"*****",
"orderDescription":"*****",
"orderAmount":{
"value":"100",
"currency":"JPY"
},
},
"paymentAmount":{
"value":"100",
"currency":"JPY"
},
"paymentFactor": {
"isInStorePayment": "true"
}
}
By complying with the Syntax of Content_To_Be_Signed, the content to be signed (Content_To_Be_Signed
) is created as follows:
POST /aps/api/v1/payments/pay
*****.2019-05-28T12:12:12+08:00.{
"order":{
"orderId":"*****",
"orderDescription":"*****",
"orderAmount":{
"value":"100",
"currency":"JPY"
},
},
"paymentAmount":{
"value":"100",
"currency":"JPY"
},
"paymentFactor": {
"isInStorePayment": "true"
}
}
Syntax of Content_To_Be_Signed
<HTTP-METHOD> <Request-URI>
<Client-Id>.<Request-Time>.<Request-Body>
HTTP-METHOD
: POSTRequest-URI
: The{endpoint}
part of the whole URLhttps://{host}{endpoint}
. For example:- If the HTTP URL is
https://{domain_name}.com/aps/api/v1/payments/pay
, this property is/aps/api/v1/payments/pay
. Client-Id
: A unique ID that is assigned by WorldFirst to identify a Partner. You can get this field from the request header. For example,*****
.Request-Time
: Specifies the time when a request is sent, as defined by ISO 8601.
Note: This field must be accurate to seconds. For example, 2019-05-28T12:12:12+08:00
and 2021-04-21T01:47:04Z
. You can get this field from the request header.
Request-Body
: The body of the request.
Step 3. Calculate and generate the signature
Use the base64UrlEncode
and sha256withrsa
methods that involve the proper algorithm and private key to calculate and generate the signature.
generatedSignature=base64UrlEncode(sha256withrsa(<Content_To_Be_Signed>, <privateKey>))
Methods that are used:
sha256withrsa
: The method to generate a digital signature for the content provided.base64UrlEncode
: The method to encode the generated digital signature.
Input parameters:
Content_To_Be_Signed
: The content to be signed that is obtained in Step 2.privateKey
: The private key value that is obtained in Step 1.
The following code sample describes the generated signature (represented by generatedSignature
):
KrwDE9tAPJYBb4cUZU6ALJxGIZgwDXn5UkFPMip09n%2FkYKPhEIII%2Fki2rYY2lPtuKVgMNz%2BtuCU%2FjzRpohDbrOd8zYriiukpGAxBQDIVbatGI7WYOcc9YVQwdCR6ROuRQvr%2FD1AfdhHd6waAASu5Xugow9w1OW7Ti93LTd0tcyEWQYd2S7c3A73sHOJNYl8DC1PjasiBozZ%2FADgb7ONsqHo%2B8fKHsLygX9cuMkQYTGIRBQsvfgICnJhh%2BzXV8AQoecJBTrv6p*****
Step 4. Configure signature in the request header
Assemble a signature string by using the following syntax:
'Signature: algorithm=<algorithm>, keyVersion=<key-version>, signature=<generatedSignature>'
algorithm
,keyVersion
: See the header of the Message structure chapter.generatedSignature
: The signature that is generated in Step 3.
The following sample describes an assembled signature string:
'Signature: algorithm=RSA256, keyVersion=0, signature=KrwDE9tAPJYBb4cUZU6ALJxGIZgwDXn5UkFPMip09n%2FkYKPhEIII%2Fki2rYY2lPtuKVgMNz%2BtuCU%2FjzRpohDbrOd8zYriiukpGAxBQDIVbatGI7WYOcc9YVQwdCR6ROuRQvr%2FD1AfdhHd6waAASu5Xugow9w1OW7Ti93LTd0tcyEWQYd2S7c3A73sHOJNYl8DC1PjasiBozZ%2FADgb7ONsqHo%2B8fKHsLygX9cuMkQYTGIRBQsvfgICnJhh%2BzXV8AQoecJBTrv6p*****'
Subsequently, add the signature string to the request header.
Send a request
Construct a request by adding the Client-Id
, Request-Time
, and Signature
fields to the request header. After a request is constructed, you can use common tools, such as cURL or Postman, to send the request. In the following example, cURL is used:
curl -X POST \
https://{domain_name}.com/aps/api/v1/payments/pay \
-H 'Content-Type: application/json' \
-H 'Client-Id: *****' \
-H 'Request-Time: 2019-05-28T12:12:12+08:00' \
-H 'Signature: algorithm=RSA256, keyVersion=0, signature=KrwDE9tAPJYBb4cUZU6ALJxGIZgwDXn5UkFPMip09n%2FkYKPhEIII%2Fki2rYY2lPtuKVgMNz%2BtuCU%2FjzRpohDbrOd8zYriiukpGAxBQDIVbatGI7WYOcc9YVQwdCR6ROuRQvr%2FD1AfdhHd6waAASu5Xugow9w1OW7Ti93LTd0tcyEWQYd2S7c3A73sHOJNYl8DC1PjasiBozZ%2FADgb7ONsqHo%2B8fKHsLygX9cuMkQYTGIRBQsvfgICnJhh%2BzXV8AQoecJBTrv6p*****' \
-d '{
"order":{
"orderId":"*****",
"orderDescription":"*****",
"orderAmount":{
"value":"100",
"currency":"JPY"
}
},
"paymentAmount":{
"value":"100",
"currency":"JPY"
},
"paymentFactor": {
"isInStorePayment": "true"
}
}'
Validate the signature of a response
After you receive a response, you need to validate the signature of the response. A response consists of a response header and a response body. The following examples show the header and body of a response:
- The response header sample
Client-Id: 5X00000000000000
Response-Time: 2019-05-28T12:12:14+08:00
Signature: algorithm=RSA256, keyVersion=0, signature=p9T2hXxIjek0UOLw3fwlthNsV6ATaioIvu8X1uFx8a9tE87d2XEhqylnf0KjifJ3WhCoMoklGwwlDS3tsSenwnL0Ha6BsXbJvUHRC5qcVlNy5Oq%2FpNqx2%2BKdwbw4eY7tZBDQhMKoaMVSbqbCb3eR*****
- The response body sample
{
"result": {
"resultCode":"SUCCESS",
"resultStatus":"S",
"resultMessage":"success"
},
"paymentTime": "2019-05-28T12:12:13+08:00",
"paymentId":"*****"
}
The following steps demonstrate how to validate a signature of a response by using the example above.
The following figure illustrates the process to validate the signature:
Figure 2. Signature validation process
Step 1. Obtain a WorldFirst public key
To get the WorldFirst public key, visit WorldFirst Developer Center and navigate to your application page. The WorldFirst public key is then generated automatically and can be obtained from the Integration Setting tab.
Notes:
- Your public key in the sandbox environment must be different from the one in the production environment. Therefore, you must get the WorldFirst public keys for the sandbox environment and the production environment separately.
Step 2. Construct the content to be validated
Given the response body sample above, construct the content to be validated (Content_To_Be_Validated
) by complying with the Syntax of Content_To_Be_Validated as follows:
POST /aps/api/v1/payments/pay
*****.2019-05-28T12:12:14+08:00.{
"result": {
"resultCode":"SUCCESS",
"resultStatus":"S",
"resultMessage":"success"
},
"paymentTime": "2019-05-28T12:12:13+08:00",
"paymentId":"*****"
}
Syntax of Content_To_Be_Validated
POST /aps/api/v1/payments/pay
*****.2019-05-28T12:12:14+08:00.{
"result": {
"resultCode":"SUCCESS",
"resultStatus":"S",
"resultMessage":"success"
},
"paymentTime": "2019-05-28T12:12:13+08:00",
"paymentId":"*****"
}
HTTP-METHOD
: POSTResponse-URI
: The URI of an HTTP link. For example, if the URL ishttps://example.com/aps/api/v1/payments/pay
, this field is/aps/api/v1/payments/pay
.Client-Id
: A unique ID that is assigned by WorldFirst to identify a Partner. You can get this field from the response header. For example*****
.Response-Time
: Indicates the time when a response is returned, as defined by ISO 8601. Note: This field must be accurate to seconds. You can get this field from the response header.Response-Body
: Indicates the body of the response.
Step 3. Get the signature from the response header
The target signature string ( target_signature
) is extracted from the Signature
header of the response. For details about the response header, see the chapter Message structure.
Signature: algorithm=RSA256, keyVersion=0, signature=<target_signature>
Step 4. Validate the signature
Use the sha256withrsa_verify
method to validate the signature of the response.
The syntax of the sha256withrsa_verify
method is as follows:
IS_SIGNATURE_VALID=sha256withrsa_verify(base64UrlDecode(<target_signature>), <Content_To_Be_Validated>, <serverPublicKey>)
Methods:
base64UrlDecode
: The method to decode the signature.sha256withrsa_verify
: The method to verify the signature.
Input parameters:
target_signature
: The target signature that is obtained in Step 3.Content_To_Be_Validated
: The content to be validated, which is created in Step 2.serverPublicKey
: The platform public key that is obtained in Step 1.
Output parameter:
IS_SIGNATURE_VALID
: A Boolean value that specifies whether the signature is valid.
true
: The signature is valid.false
: The signature is not valid. The causes can be that the private key and the public key do not match, orContent_To_Be_Validated
is not correctly constructed.
Demo code
This page demonstrates demo codes for the Sign a request and validate the signature processes.
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 = "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCjdqhmlmsw6NWWPLtFrBMKUX74lSZspgWmWL4cOhso6uOiG5fMUiX/WZmoj0yBD2TJFO5k35CmoXbpjM6oiuhbZuCgxjNyzgtpQd0qjmNrxDfMz3LNTxpxU7+lOvrRBE3vIHnkL3EFl4i5O/Pifyhp5s+cpr+9yhMhE/vKWo4KxdKQb9psUszijeT/b1eB9yh6DHW2Go9MY2hBf6AST2CaF96pshpeoQlLv30AcWjNBMKNfB/3e0nFsTH4AgfpPUuABUr/3pke1kGpep/WufWazzH7B71QIelVeFGNvT0xCKfgcTwg1pczySwDor1vLNKUw8IIppPtm7un3SUtyAy1AgMBAAECggEAFed6nXSenIk2xdERjz6FtqZBC3KBGaINs8LEhufKZ+tB4aEs8DY53UPWcV3ydIDpkoB5iY7jxVBxrM4viypCBK1+gEzO7B1sLgijaGZ4MCK1D37k9VoaQwFj9vI2VffrsZV6f/dTf9pUee3suncrpTVGsMkvfHEtqwsQfIcx7YNp5NSNEFSMnuSWehi+PaFGIh2elGkLWuAAASx2V5bj+10YV857tSdXBxTdfHM/AyXeU1Q+IKTyB7dY/kdoE78zib05K5Yg6uHQz67cupcmXTqvL0Giksnp4MyZWd6I8CPtc+aB0Z4Gaei0rVtsfVq4ttnAE8HBOaDWimi33EnvqQKBgQDQFw4cGLmFMhSWbMBk/J9hvAWAOHiS0c+JrOSicNXSX93sU/N2KleeYmTXZCl78Pet278/a5kEtqwfMNNoadWT9I+FrKACYd4P2wkK/RK9W187tjzriHKCem6sxDf/bM0bZq9rLPQgLpjD5n3s3O6wzwKkQqNuMhqVtlH9GDRttwKBgQDJGUnmwwX72x1xW9hF4WoW+3vVExHBmNrL1OXdCfkDo22rEJNWf99rxhXG3K6q2e00m73iU/Ed61uSSxsU8NJe12d15UA04ScxtFWVvZ0qAuPpJxfXErRWgrKYh8Kb2ENQjBCW0qhWfX1nwx+/O7BIjh8g/pG5IrIO0ehfRBtY8wKBgQCNdz8GZuCo5pUMdr6NaRjJjtOGid2eorie9kUxEdirfrUWlzQqnzqV2HqJ2/Q/6F/ZeE0aRoHurGlzgvQWRe0G+voffG4DcJlQ2i3ZpJb62IVzyL69uTNv9OAM4BDZuxPtju+uww7sWgFNHQN65e/3j6RSG2OT5MgiA6LVl4D9HQKBgCW7qgMzqsbEKXUgv6b1Edv4Ghcz+cvyq3M9olfkTCYjnytO8+ZyHfDx/RoQRHHobDrH0kxKZd6wT8FWeGFogoCrOE2F05NYf+sypDBxoLDGVA9md3Gyn8XvxiUarChj3tWZQm+aXV3FNCepFCbkw0IJ8MtXGO69FN31Av4FIICDAoGBAM4V634q6d8YtLyycxGhNPTTd/SYkeI4R3jUqK++zpw7d2d+FvXKOwBNmbeo8cgAsW4EgiJW2lqDXdcq3YT8BTxWs+Hf2sjqifxAE5nz727kDpg2ipQX/f1aq0bpWtPLXScC5nE6oNdOS5a49+6XS6VaxhOp4Kf2rQKQe94*****";
static string verifyPublicKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAo3aoZpZrMOjVljy7RawTClF++JUmbKYFpli+HDobKOrjohuXzFIl/1mZqI9MgQ9kyRTuZN+QpqF26YzOqIroW2bgoMYzcs4LaUHdKo5ja8Q3zM9yzU8acVO/pTr60QRN7yB55C9xBZeIuTvz4n8oaebPnKa/vcoTIRP7ylqOCsXSkG/abFLM4o3k/29Xgfcoegx1thqPTGNoQX+gEk9gmhfeqbIaXqEJS799AHFozQTCjXwf93tJxbEx+AIH6T1LgAVK/96ZHtZBqXqf1rn1ms8x+we9UCHpVXhRjb09MQin4HE8INaXM8ksA6K9byzSlMPCCKaT7Zu7p90lLcgMtQI*****";
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 = "MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDTc+GNzoQOJ6LkOPUPv/837zzhMmjBtfP/PWLsWin++uMJXwhsDjqZTXMsvUz5EF02KvyOpnVT32KOQ9Y7ZSknHm/a/GZyyM4AI4gKJoci1cwoHk8oaEMNHFv18imADJwOSyb1tg1oyrkNqafP6+D5WwHIy7jqYn69yjaxGBhMaBvJzDB9APQ3Uk1alaVOJiORTl3B2OTb03VDi1zQLnDjfNERZYePo+DhWTWne3rgg5sMx9J75hZyJDIehGlGKDvpN+RDhyr526dQourE4BH8AXWKmpv7L/MUu+2XYKvG9YBpCV0VU14Hd6S+ivCKvnP01usHXDupNggJ+uZP5JlNAgMBAAECggEBAI86p0XO5VRRNN0yV4zbmJziyHIiGyNbhHMXubIvQNMeTjtCzAmlebvt4l4ju8fZdcoDRB+8US0G21NSUALbewysaUgEP8Dwk9k0OCnn/xFxkGB8Z1IPjZuL4h6ucb3yzKJB9ZnqPxsEdmTyzmDgfftp0cOZeyAIp6EXIATFNXBKuIG8lpwjO0TaAW+bMdgr9zxcZETrb0sAYs08m6gfMQwuWCq9bUmABSdhpqcxXtMbq71o64c5LKRh1xVgWe8leLlw1HNLV7yC7Nk1r3KoacxTRxwwy+I7qrAnfZqmnZBydyqsj64jtNitNrXtg576JeoONDfdTG4xtQ93XAOZgwECgYEA/LcGA1xiiFHVaA75QdDqF5palCIA0PuZKVkNqYNqW2svLyk8cWMZgFF9wnmOEqAuvBQRiDY/wO3/YF6oZ/1yLOI/0l97WsZTBbEBBPBvGv51HjNkRPfwD6PviOl5wuGZoWE4oR0EyDp+ljyHX3XW3NU5gG2SApmOOqqQiBcidtUCgYEA1jOL3D/jKkr0BlF9SSCEV8L751PQ8tpHOhNemcKffhVZL+cFYN8wSM5gVBR2BdIaGjOw7uEA36gjL0S61pzRUyyPQ70Ega5rpqysgUE6IaMwDW+1zxVKbMLESVr5XL6yss3ZWQrJj/ZjxOfDG6TDwwTs6n8dkCSQq4yl3IVPRJkCgYA09oqtE6SW20e1ekXk9ErLTY8kMognREOSNda2KxOUOz91S9geD13d1bZclqse3jFNO4t9F5l+7qIx6US0HpraK0Si613n5V6q97C1/0nZx3B2NuERz0ChloLyF7RsEmnnN3/tzC4fZJr5E5BvgjvYpltZvhz2rIXxZ6PI0choTQKBgQCwJ2P1lXSz60ATkiB+awdrRSb0brF/hpLc5+D8glm3zsax5kM+D04eEdqWSt1knAxrT+dKDDAzvopw7QzaQczDofmPs3ppS4+sWoTjJ0kvMIzr/9p8mv3Bw8q4qOA5rXo4IaGE4KIyimYyIIcLzMxRLVPI5RDi4a835Urht6AgwQKBgQDvANr+qUXRVR2U6Zq4H+4z7qq0RNeLdID2wtyPOvW83j7tGGjuX6KrFi8Cayii5vXn3rp6scol1TijoRm1W8NG/BCjmil/elGc9+GZHBvDLZIGLNzrjQ4Cg630LOCVjzD2ktK8cVc1fFxF/w2Vp9KCOg69thPAU23ocnrDe9Z*****";
private static final String verifyPublicKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA03Phjc6EDiei5Dj1D7//N+884TJowbXz/z1i7Fop/vrjCV8IbA46mU1zLL1M+RBdNir8jqZ1U99ijkPWO2UpJx5v2vxmcsjOACOICiaHItXMKB5PKGhDDRxb9fIpgAycDksm9bYNaMq5Damnz+vg+VsByMu46mJ+vco2sRgYTGgbycwwfQD0N1JNWpWlTiYjkU5dwdjk29N1Q4tc0C5w43zREWWHj6Pg4Vk1p3t64IObDMfSe+YWciQyHoRpRig76TfkQ4cq+dunUKLqxOAR/AF1ipqb+y/zFLvtl2CrxvWAaQldFVNeB3ekvorwir5z9NbrB1w7qTYICfrmT+SZTQI*****";
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://{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 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>