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:

yuque_diagram (5).jpg

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:

copy
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.

copy
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:

copy
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/pay
  • Client-Id: TEST_5X00000000000000
  • Request-Time: 2019-05-28T12:12:12+08:00
  • Request-Body: The following code sample describes the body format. For example:
copy
{
 "order":{
    "orderId":"*****",
    "orderDescription":"sample_order",
    "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:

copy
POST /aps/api/v1/payments/pay
TEST_5X00000000000000.2019-05-28T12:12:12+08:00.{
"order":{
    "orderId":"*****",
    "orderDescription":"sample_order",
    "orderAmount":{
       "value":"100",
       "currency":"JPY"
    },
 },
 "paymentAmount":{
    "value":"100",
    "currency":"JPY"
 },
 "paymentFactor": {
     "isInStorePayment": "true"
 } 
}

Syntax of Content_To_Be_Signed

copy
<HTTP-METHOD> <Request-URI>
<Client-Id>.<Request-Time>.<Request-Body>
  • HTTP-METHOD: POST
  • Request-URI: The {endpoint} part of the whole URL https://{host}{endpoint}. For example:
  • If the HTTP URL is https://www.example.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, TEST_5X00000000000000.
  • 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.

copy
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):

copy
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:

copy
'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:

copy
'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:

copy
curl -X POST \
  https://example.com/aps/api/v1/payments/pay \
  -H 'Content-Type: application/json' \
  -H 'Client-Id: TEST_5X00000000000000' \
  -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":"OrderID_0101010101",
          "orderDescription":"sample_order",
          "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
copy
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
copy
{
"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:

yuque_diagram (6).jpg

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:

copy
POST /aps/api/v1/payments/pay
TEST_5X00000000000000.2019-05-28T12:12:14+08:00.{
 "result": {
    "resultCode":"SUCCESS",
    "resultStatus":"S",
    "resultMessage":"success"
 },
 "paymentTime": "2019-05-28T12:12:13+08:00",
 "paymentId":"1234567"
}

Syntax of Content_To_Be_Validated

copy
POST /aps/api/v1/payments/pay
TEST_5X00000000000000.2019-05-28T12:12:14+08:00.{
 "result": {
    "resultCode":"SUCCESS",
    "resultStatus":"S",
    "resultMessage":"success"
 },
 "paymentTime": "2019-05-28T12:12:13+08:00",
 "paymentId":"1234567"
}
  • HTTP-METHOD: POST
  • Response-URI: The URI of an HTTP link. For example, if the URL is https://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 TEST_5X00000000000000.
  • 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.

copy
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:

copy
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, or Content_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

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# Demo code

copy
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 = "3J5Y606G2Y2TPL*****";
            string timeString = "2022-04-28T12:31:30+08:00";
            string content = "{\"customerId\":\"21201200212*****\"}";
            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

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 =  "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 = "5J5Y307NSG5GB7*****";
        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://worldfirst.com/ams/api/pay/query uriWithQueryString should be /ams/api/pay/query not https://worldfirst.com/ams/api/pay/query
     * @param clientId           clientId issued by WorldFirst  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 WorldFirst
     *
     * @param httpMethod         http method                  e.g., POST, GET
     * @param uriWithQueryString query string in url          e.g., if your request url is https://worldfirst.com/ams/api/pay/query uriWithQueryString should be /ams/api/pay/query not https://worldfirst.com/ams/api/pay/query
     * @param clientId           clientId issued by WorldFirst    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 publicKey    public key from WorldFirst
     */
    public static boolean verify(
            String httpMethod,
            String uriWithQueryString,
            String clientId,
            String timeString,
            String rspBody,
            String signature,
            String publicKey) 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, publicKey);

    }


    /**
     * 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

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>

@2024 WorldFirst