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:

Sign a request and validate the 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:

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 data to be signed

Construct the data to be signed with the following rules:

copy
<HTTP-Method> <Request-URL-Endpoint>
<Client-ID>.<Request-Time>.<Request-Body>

For example, given an API request message with the following <Request-Body>:

copy
{
  "removeBeneficiaryRequestId":"2022******",
  "beneficiaryToken":"ALIPAYqwertyuiopoiuytrewqwertyuiopoiuytrewq==",
  "customerId":"2120120003901867"
}

You need to generate the following data to be signed:

copy
POST /v1/business/account/removeBeneficiary
5Y60382Z2Y4S02622.2022-04-28T12:31:30+08:00.{"removeBeneficiaryRequestId":"2022******","beneficiaryToken":"ALIPAYqwertyuiopoiuytrewqwertyuiopoiuytrewq==","customerId":"2120120003901867"}

where,

  • <HTTP-Method> is POST.
  • <Request-URL-Endpoint> is /v1/business/account/removeBeneficiary
  • In this example, the full request URL is https://open-na.worldfirst.com/v1/business/account/removeBeneficiary.
  • <Client-ID> is 5Y60382Z2Y4S02622.
  • <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\":\"2022******\",\"beneficiaryToken\":\"ALIPAYqwertyuiopoiuytrewqwertyuiopoiuytrewq==\",\"customerId\":\"2120120003901867\"}.

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:

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

copy
KEhXthj4bJ801Hqw8kaLvEKc0Rii8KsNUazw7kZgjxyGSPuOZ48058UVJUkkR21iD9JkHBGRrWiHPae8ZRPuBagh2H3qu7fxY5GxVDWayJUhUYkr9m%2FOW4UQVmXaQ9yn%2Fw2dCtzwAW0htPHYrKMyrTpMk%2BfDDmRflA%2FAMJhQ71yeyhufIA2PCJV8%2FCMOa46303A0WHhH0YPJ9%2FI0UeLVMWlJ1XcBo3JrbRFvcowQwt0lP1XkoPmSLGpBevDE8%2FQ9WnxjPNDfrHnKgV2fp0hpMKVXNM%2BrLHNyMv3MkHg9iTMOD%2FFYDAwSd%2B6%2FEOFo9UbdlKcmodJwjKlQoxZZIzmF8w%3D%3D

Step 4. Sign the request

Assemble the signature field in the request header with the following syntax:

copy
'Signature: algorithm=<algorithm>, keyVersion=<key-version>, signature=<signature>'
  • algorithm: Specify the algorithm used to generate the signature. The default value is RSA256.
  • 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:

Sign a request and 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:

copy
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAhqe3BYkjEXdwAl7LWyFjcbYkEYLyFVqXpnwsBzfIsBDujqtQYrDPKm7+PbomkZ787aRj2lWDb6RYy7NwkLnu0Vw799WPGnSwLHnXyM8P/V+YVvzFm873PHUj30sYPDs5uJBWnrcIVpNGKTkGyebtQuF+IFAxP/0yqF/LG3bVIWQa5m6Mkv6pngs+Ln84rULkYBqmROgAzUCoEt8jlynmXUGNH0WeVPk5IA97ZzMoQsp71zC3u4gszN7Kl0qfGKT1Vz7vl/pLZ5SySACISlP0GzyDufBA823g5DDzkxeKKO5zNshKaQaI5t1k+ODgBT8yB9IW8lMyrebVnFO9L5f/8wIDAQAB

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:

copy
'Signature: algorithm=<algorithm>, keyVersion=<key-version>, signature=<signature>'
  • algorithm: Specify the algorithm used to generate the signature. The default value is RSA256.
  • 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:

copy
signature: algorithm=RSA256, keyVersion=2, signature=KEhXthj4bJ801Hqw8kaLvEKc0Rii8KsNUazw7kZgjxyGSPuOZ48058UVJUkkR21iD9JkHBGRrWiHPae8ZRPuBagh2H3qu7fxY5GxVDWayJUhUYkr9m%2FOW4UQVmXaQ9yn%2Fw2dCtzwAW0htPHYrKMyrTpMk%2BfDDmRflA%2FAMJhQ71yeyhufIA2PCJV8%2FCMOa46303A0WHhH0YPJ9%2FI0UeLVMWlJ1XcBo3JrbRFvcowQwt0lP1XkoPmSLGpBevDE8%2FQ9WnxjPNDfrHnKgV2fp0hpMKVXNM%2BrLHNyMv3MkHg9iTMOD%2FFYDAwSd%2B6%2FEOFo9UbdlKcmodJwjKlQoxZZIzmF8w%3D%3D

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:

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

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+6XS6VaxhOp4Kf2rQKQe94i1OAK";
        
        static string verifyPublicKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAo3aoZpZrMOjVljy7RawTClF++JUmbKYFpli+HDobKOrjohuXzFIl/1mZqI9MgQ9kyRTuZN+QpqF26YzOqIroW2bgoMYzcs4LaUHdKo5ja8Q3zM9yzU8acVO/pTr60QRN7yB55C9xBZeIuTvz4n8oaebPnKa/vcoTIRP7ylqOCsXSkG/abFLM4o3k/29Xgfcoegx1thqPTGNoQX+gEk9gmhfeqbIaXqEJS799AHFozQTCjXwf93tJxbEx+AIH6T1LgAVK/96ZHtZBqXqf1rn1ms8x+we9UCHpVXhRjb09MQin4HE8INaXM8ksA6K9byzSlMPCCKaT7Zu7p90lLcgMtQIDAQAB";
        static void Main(string[] args)
        {
            string httpMethod = "POST";
            string uriWithQueryString = "/amsin/api/v1/business/account/inquiryBalance";
            string clientId = "3J5Y606G2Y2TPL02022";
            string timeString = "2022-04-28T12:31:30+08:00";
            string content = "{\"customerId\":\"2120120021277065\"}";
            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/w2Vp9KCOg69thPAU23ocnrDe9ZlXw==";

    private static final String verifyPublicKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA03Phjc6EDiei5Dj1D7//N+884TJowbXz/z1i7Fop/vrjCV8IbA46mU1zLL1M+RBdNir8jqZ1U99ijkPWO2UpJx5v2vxmcsjOACOICiaHItXMKB5PKGhDDRxb9fIpgAycDksm9bYNaMq5Damnz+vg+VsByMu46mJ+vco2sRgYTGgbycwwfQD0N1JNWpWlTiYjkU5dwdjk29N1Q4tc0C5w43zREWWHj6Pg4Vk1p3t64IObDMfSe+YWciQyHoRpRig76TfkQ4cq+dunUKLqxOAR/AF1ipqb+y/zFLvtl2CrxvWAaQldFVNeB3ekvorwir5z9NbrB1w7qTYICfrmT+SZTQIDAQAB";

    public static void main(String[] args) throws Exception {
        String httpMethod = "POST";
        String uriWithQueryString = "/aps/api/business/fund/inquiryBalance";
        String clientId = "5J5Y307NSG5GB700810";
        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

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