Signature Algorithms

Ksher have the process to verify every transactions comes from our authorized merchant to call API. You need attach "sign" every transaction.

Prepare data

Before start to make your owner signature, you have to have data following this

1. Private key

You need to access your merchant portal for resetting/downloading your private KEY. Please get it from https://merchant.ksher.net

For How to get it Please see How to download Key page.

2. data to signature

You can check from our API like Gateway pay API Integration Guide to see example request data.

Signature generation process

Create Signature by use SDK

You can use handy step but it’s make you annoying with algorithm we will use, verified signature, value type, etc. We recommend to use SDK. Check the SDK page.

Handy process

  • Sort all request parameters (including appid/time_stamp parameters, but except the "sign") according to the parameter name in ASCII table. For example:

Before sort:

{
    "appid": "mch30000",
    "time_stamp": "202208311556",
    "nonce_str": "653adfc3c99ffc35fd7711bdb29eeec4",
    "channel_list": "card,truemoney",
    "total_fee": "2000",
    "fee_type": "THB",
    "mch_order_no": "202208311556",
    "mch_code": "202208311556",
    "mch_notify_url": "https://website.com/api/gateway_pay/notify_url/",
    "mch_redirect_url": "https://website.com/api/gateway_pay/success",
    "mch_redirect_url_fail": "https://website.com/api/gateway_pay/fail",
    "color": "#FE6200",
    "payment_color": "#FFFFF",
    "background": "#FE6200",
    "product_name": "202208311556",
    "refer_url": "https://website.com"
}

After sort:

{
    "appid": "mch30000",
    "background": "#FE6200",
    "channel_list": "card,truemoney",
    "color": "#FE6200",
    "fee_type": "THB",
    "mch_code": "202208311556",
    "mch_notify_url": "https://website.com/api/gateway_pay/notify_url/",
    "mch_order_no": "202208311556",
    "mch_redirect_url": "https://website.com/api/gateway_pay/success",
    "mch_redirect_url_fail": "https://website.com/api/gateway_pay/fail",
    "nonce_str": "653adfc3c99ffc35fd7711bdb29eeec4",
    "payment_color": "#FFFFF",
    "product_name": "202208311556",
    "refer_url": "https://website.com",
    "time_stamp": "202208311556",
    "total_fee": "2000"
}
  • Concact all the key:value pairs into a string like '=' ':' ','

  • (Only outside the variable. Other values like "chanel":"alipay,wechat" keep "alipay,wechat". )

  • Concatenate the sorted parameters and their values into a string.

For example:

appid=mch30000background=#FE6200channel_list=card,truemoneycolor=#FE6200fee_type=THBmch_code=202208311556mch_notify_url=https://website.com/api/gateway_pay/notify_url/mch_order_no=202208311556mch_redirect_url=https://website.com/api/gateway_pay/successmch_redirect_url_fail=https://website.com/api/gateway_pay/failnonce_str=653adfc3c99ffc35fd7711bdb29eeec4payment_color=#FFFFFproduct_name=202208311556refer_url=https://website.comtime_stamp=202208311556total_fee=2000
  • Encode the concatenated string in UTF-8 format and make a digest by the "RSA-MD5" signature algorithm.

  • Convert the digest to hexadecimal.

For example:

const privateKey = ```
-----BEGIN RSA PRIVATE KEY-----
MIICYAIBAAKBgQCOoa1/VcyvU8EzsWxmJBUIjFevAjjpyFHxI7Z0Y55+q9XBsgSi
BxWauLZ9TNy6f32pCKC9QrYi1wF1sUljCsMq2kuW/EXa+UExMI3WnI76yUAlCRsk
PdORCvVV5uE/Hu3okfJ+ZJR1iztapAYPk/W6jnbPxbMj5ahBTQj6+1UN6QIDAQAB
AoGABK1OiB9jH9iqPCy0NkE2o1oewfFbtmbIMRUPtY9SsiqmTryspDeBQNqPuVoc
3syxbSqIQsx+NnRAawCONO+9padQmGbwoM+OQWSv8w3pkRlIUIifGh6gUzhx8QYL
SkNtKtYBSWjAfQ21wMJgnLHiyrqe+q6NsOq9YdoP1ZCM9RUCRQDmcX91En4h8Dbc
sVUb4SvuIgN85LUd/fuZFNJXNaj6Mvok3FOGL153+6kjoLr//4sI8IGMKW2BLwe6
g2onsti3qJPxawI9AJ5zHu9swTmssb622j/DJlPAUobIkpGiG4EYRNCslFuhJhoo
y1BKbQsKJyX2PM6KzWw4IjyZm+TIOJqO+wJENA+0MejJojU4z8coaTIH0LbSfubU
nEADFWSE2LsAv/XAWY+FNy2AdC7g2XG4jZlX+d8MBXReju7nGhYSZ4GaQHPPaJ0C
PAyNgj3ll8lB7TL2uYOjqj2oVuFmsnXnKdaoXYtKoNZBhgs8gB70Rn9BZqiTQW37
gRq5t7ylTrxpQFK+UQJFANY2pFIbSdU6HkoDFJcigVzD+dcuYH8f4gCDRZRiC+k6
flmwf/L6W35o0r6Cm96U8r3+0Sy+c4BgbjpI8pAcjJscmkBB
-----END RSA PRIVATE KEY-----
```
// This is example private key. your owner private key, please download at merchant platform.

const keyvalue = "appid=mch30000background=#FE6200channel_list=card,truemoneycolor=#FE6200fee_type=THBmch_code=202208311556mch_notify_url=https://website.com/api/gateway_pay/notify_url/mch_order_no=202208311556mch_redirect_url=https://website.com/api/gateway_pay/successmch_redirect_url_fail=https://website.com/api/gateway_pay/failnonce_str=653adfc3c99ffc35fd7711bdb29eeec4payment_color=#FFFFFproduct_name=202208311556refer_url=https://website.comtime_stamp=202208311556total_fee=2000"

const signer = crypto.createSign("RSA-MD5");
signer.write(keyvalue);
signer.end();
const signature = signer.sign(this.privateKey, "hex");
  • you will got signature

645fc8cb0b1a48f0e86959f7c0d6c28153e4f08832d5e166cd15e57e656af4f7cf7ddceba0960794d713b577b7698c767b828108f7864bfb510872a03f4b575daf3a834fddd397d1f47f3befcd8a880ff613e80620fae0f14a00a67f56a50e82cb18bf4ef11581f2d71be64553974df418be39cc175219db344469b46b599f5f
  • assign the signed result got in step above to the sign field in the post parameters dict.

The final result of post data is like below:

{
    "appid": "mch30000",
    "background": "#FE6200",
    "channel_list": "card,truemoney",
    "color": "#FE6200",
    "fee_type": "THB",
    "mch_code": "202208311556",
    "mch_notify_url": "https://website.com/api/gateway_pay/notify_url/",
    "mch_order_no": "202208311556",
    "mch_redirect_url": "https://website.com/api/gateway_pay/success",
    "mch_redirect_url_fail": "https://website.com/api/gateway_pay/fail",
    "nonce_str": "653adfc3c99ffc35fd7711bdb29eeec4",
    "payment_color": "#FFFFF",
    "product_name": "202208311556",
    "refer_url": "https://website.com",
    "time_stamp": "202208311556",
    "total_fee": "2000",
    "sign": "645fc8cb0b1a48f0e86959f7c0d6c28153e4f08832d5e166cd15e57e656af4f7cf7ddceba0960794d713b577b7698c767b828108f7864bfb510872a03f4b575daf3a834fddd397d1f47f3befcd8a880ff613e80620fae0f14a00a67f56a50e82cb18bf4ef11581f2d71be64553974df418be39cc175219db344469b46b599f5f"
}

Exception on create signature

order_query if you have "operator_id" in request data, not calculate in signature.

def _request(self, url, data, m=""):
        if url.find("/order_query") and ("operator_id" in data):
            dataWithoutOperator_id = data
            dataWithoutOperator_id.pop("operator_id")
            sign = self.ksher_sign(dataWithoutOperator_id)
        else:
            sign = self.ksher_sign(data)
        data.update({'sign': sign.decode()})

Signature generation online testing tool

You can generate a signature from this URL. Please get it from https://gateway.ksher.com/demo_sign.html

signtool01
  • Enter your request at "*原始参数/Original parameter(json format {"param name": "param value", …​}):"

{
    "appid": "mch35005",
    "channel": "promptpay",
    "fee_type": "THB",
    "mch_order_no": "20230711163201",
    "nonce_str": "90c8d5ad3d4aa1a538f610d259c35c97",
    "time_stamp": "2023071717532828S",
    "total_fee": "100"
}
  • your private key at "*私钥/Private(str PKCS1 format, include: -----BEGIN RSA PRIVATE KEY-----):"

-----BEGIN RSA PRIVATE KEY-----
MIICYAIBAAKBgQCOoa1/VcyvU8EzsWxmJBUIjFevAjjpyFHxI7Z0Y55+q9XBsgSi
BxWauLZ9TNy6f32pCKC9QrYi1wF1sUljCsMq2kuW/EXa+UExMI3WnI76yUAlCRsk
PdORCvVV5uE/Hu3okfJ+ZJR1iztapAYPk/W6jnbPxbMj5ahBTQj6+1UN6QIDAQAB
AoGABK1OiB9jH9iqPCy0NkE2o1oewfFbtmbIMRUPtY9SsiqmTryspDeBQNqPuVoc
3syxbSqIQsx+NnRAawCONO+9padQmGbwoM+OQWSv8w3pkRlIUIifGh6gUzhx8QYL
SkNtKtYBSWjAfQ21wMJgnLHiyrqe+q6NsOq9YdoP1ZCM9RUCRQDmcX91En4h8Dbc
sVUb4SvuIgN85LUd/fuZFNJXNaj6Mvok3FOGL153+6kjoLr//4sI8IGMKW2BLwe6
g2onsti3qJPxawI9AJ5zHu9swTmssb622j/DJlPAUobIkpGiG4EYRNCslFuhJhoo
y1BKbQsKJyX2PM6KzWw4IjyZm+TIOJqO+wJENA+0MejJojU4z8coaTIH0LbSfubU
nEADFWSE2LsAv/XAWY+FNy2AdC7g2XG4jZlX+d8MBXReju7nGhYSZ4GaQHPPaJ0C
PAyNgj3ll8lB7TL2uYOjqj2oVuFmsnXnKdaoXYtKoNZBhgs8gB70Rn9BZqiTQW37
gRq5t7ylTrxpQFK+UQJFANY2pFIbSdU6HkoDFJcigVzD+dcuYH8f4gCDRZRiC+k6
flmwf/L6W35o0r6Cm96U8r3+0Sy+c4BgbjpI8pAcjJscmkBB
-----END RSA PRIVATE KEY-----
  • click at "提交数据/Submit"

signtool02
  • "待签名数据/parameter to be signed:" is the text to create sign request

  • copy "签名/sign:" to use when request API

Verify Signature

Verify Signature will use when API response and response from "mch_notify_url" or "notify_url" For merchant can make sure response send from ksher.

Verify Signature will use Public Key data to make verify. The public key is

-----BEGIN RSA PUBLIC KEY-----
MEgCQQC+/eeTgrjeCPHmDS/5osWViFyIAryFRIr5canaYhz3Di3UNkT0sf6TkabF
LvxPcM9JmEtj2O4TXNpgYATkE/sFAgMBAAE=
-----END RSA PUBLIC KEY-----

The process will use the same like make signature, but use Public Key to verify.

Example verify response from API

{
  "code": 0,
  "msg": "操作成功",
  "data": {
    "channel": "airpay",
    "openid": "",
    "channel_order_no": "1254233130",
    "cash_fee_type": "",
    "ksher_order_no": "90020210527111737185024",
    "nonce_str": "orvGFiv6qOJsoNgg3fZcIcJJRmGYV2Wr",
    "time_end": "2021-05-27 10:18:07",
    "fee_type": "THB",
    "attach": "",
    "rate": "1.000000",
    "result": "SUCCESS",
    "total_fee": 100,
    "appid": "mch35005",
    "cash_fee": "",
    "mch_order_no": "20210519",
    "pay_mch_order_no": "2105271017222548"
  },
  "sign": "56e563680f4ae5383c652bba161382e692991c7f3cc5e5d593032344baa96333469863d8eb6e5481341ec80039f9b7f658189456f5ace288b3e33fb7e8b7ec75",
  "message": "操作成功"
}
  • Use parameters inside “data” to create text for make verification

{
    "channel": "airpay",
    "openid": "",
    "channel_order_no": "1254233130",
    "cash_fee_type": "",
    "ksher_order_no": "90020210527111737185024",
    "nonce_str": "orvGFiv6qOJsoNgg3fZcIcJJRmGYV2Wr",
    "time_end": "2021-05-27 10:18:07",
    "fee_type": "THB",
    "attach": "",
    "rate": "1.000000",
    "result": "SUCCESS",
    "total_fee": 100,
    "appid": "mch35005",
    "cash_fee": "",
    "mch_order_no": "20210519",
    "pay_mch_order_no": "2105271017222548"
}
  • Concact all the key:value pairs into a string like '=' ':' ','

channel=airpay
openid=
channel_order_no=1254233130
cash_fee_type=
ksher_order_no=90020210527111737185024
nonce_str=orvGFiv6qOJsoNgg3fZcIcJJRmGYV2Wr
time_end=2021-05-27 10:18:07
fee_type=THB
attach=
rate=1.000000
result=SUCCESS
total_fee=100
appid=mch35005
cash_fee=
mch_order_no=20210519
pay_mch_order_no=2105271017222548
  • Sort all parameters (including appid/time_stamp parameters, but except the "sign") according to the parameter name in ASCII table. For example:

appid=mch35005
attach=
cash_fee=
cash_fee_type=
channel=airpay
channel_order_no=1254233130
fee_type=THB
ksher_order_no=90020210527111737185024
mch_order_no=20210519
nonce_str=orvGFiv6qOJsoNgg3fZcIcJJRmGYV2Wr
openid=
pay_mch_order_no=2105271017222548
rate=1.000000
result=SUCCESS
time_end=2021-05-27 10:18:07
total_fee=100
  • Concatenate the sorted parameters and their values into a string.

appid=mch35005attach=cash_fee=cash_fee_type=channel=airpaychannel_order_no=1254233130fee_type=THBksher_order_no=90020210527111737185024mch_order_no=20210519nonce_str=orvGFiv6qOJsoNgg3fZcIcJJRmGYV2Wropenid=pay_mch_order_no=2105271017222548rate=1.000000result=SUCCESStime_end=2021-05-27 10:18:07total_fee=100

Example verify response from API (array inside "data" parameters)

{
    "code": 0,
    "data": {
        "appid": "mch29217",
        "cash_fee": "48",
        "cash_fee_type": "CNY",
        "channel_order_no": "4200001823202304219631687916",
        "fee_type": "THB",
        "ksher_order_no": "70020230421121618509855",
        "mch_order_no": "2023042111163700",
        "nonce_str": "cffa8e4b4aa98b197cf3ae73968d9c87",
        "refund_count": "2",
        "refund_fee": "2",
        "refund_orders": [
            {
                "mch_refund_fee": 20,
                "ksher_refund_no": "70020230421122235500857",
                "channel_refund_no": "50202405672023042133267207073",
                "mch_refund_no": "refund1_2023042111163700",
                "refund_state": "REFUNDSUCCESS",
                "refund_time": "2023-04-21 11:22:45"
            },
            {
                "mch_refund_fee": 20,
                "ksher_refund_no": "70020230421122418652074",
                "channel_refund_no": "50202405672023042133267207108",
                "mch_refund_no": "refund2_2023042111163700",
                "refund_state": "REFUNDSUCCESS",
                "refund_time": "2023-04-21 11:24:28"
            }
        ],
        "result": "SUCCESS",
        "time_end": "2023-04-21 11:22:45",
        "total_fee": 200
    },
    "msg": "ok",
    "sign": "9b8042dc417cc245c83f56e8c34b719cc88ab4420eb1eec351256a31b5f856c948069120600339d89f4252eaf076c265e6be633be1c65cea5e80d49830cc89e5",
    "status_code": "",
    "status_msg": "",
    "time_stamp": "2023-04-21T12:26:56.375672+08:00",
    "version": "3.0.0"
}
  • String after sorting a-z and Concact json

appid=mch29217
cash_fee=48
cash_fee_type=CNY
channel_order_no=4200001823202304219631687916
fee_type=THB
ksher_order_no=70020230421121618509855
mch_order_no=2023042111163700
nonce_str=cffa8e4b4aa98b197cf3ae73968d9c87
refund_count=2
refund_fee=2
refund_orders=[{"channel_refund_no":"50202405672023042133267207073","ksher_refund_no":"70020230421122235500857","mch_refund_fee":20,"mch_refund_no":"refund1_2023042111163700","refund_state":"REFUNDSUCCESS","refund_time":"2023-04-21 11:22:45"},{"channel_refund_no":"50202405672023042133267207108","ksher_refund_no":"70020230421122418652074","mch_refund_fee":20,"mch_refund_no":"refund2_2023042111163700","refund_state":"REFUNDSUCCESS","refund_time":"2023-04-21 11:24:28"}]
result=SUCCESS
time_end=2023-04-21 11:22:45
total_fee=200

text make verification

appid=mch29217cash_fee=48cash_fee_type=CNYchannel_order_no=4200001823202304219631687916fee_type=THBksher_order_no=70020230421121618509855mch_order_no=2023042111163700nonce_str=cffa8e4b4aa98b197cf3ae73968d9c87refund_count=2refund_fee=2refund_orders=[{"channel_refund_no":"50202405672023042133267207073","ksher_refund_no":"70020230421122235500857","mch_refund_fee":20,"mch_refund_no":"refund1_2023042111163700","refund_state":"REFUNDSUCCESS","refund_time":"2023-04-21 11:22:45"},{"channel_refund_no":"50202405672023042133267207108","ksher_refund_no":"70020230421122418652074","mch_refund_fee":20,"mch_refund_no":"refund2_2023042111163700","refund_state":"REFUNDSUCCESS","refund_time":"2023-04-21 11:24:28"}]result=SUCCESStime_end=2023-04-21 11:22:45total_fee=200

Example Code Verify Signature

Python

def verify_ksher_sign(self, signature, message):
        """
        Verify signature with public key object
        :param message:
        :param signature:
        :param mch_pubkey: pass in the publickey object
        :return:
        """
        alist = []
        for key, value in message.items():
            strval = ""
            keystr = key
            if isinstance(value,str):
                strval = value
            elif isinstance(value,bytes):
                strval = value.encode('utf-8')
            else:
                strval = json.dumps(value, ensure_ascii=False, separators=(',',':'), sort_keys=True)
                if isinstance(strval,bytes):
                    strval = strval.encode('utf-8')
            if isinstance(key, bytes):
                keystr = key.encode('utf-8')
            print(keystr + "=" + strval)
            alist.append(keystr + "=" + strval)

        alist.sort()
        predata = "".join(alist)
        logging.debug(str(predata))

        predata = "".join(alist).encode('utf-8')
        decodesign = binascii.unhexlify(signature)

        print("text make sign:",predata)
        pubkey = ''
        try:
            with open(self.pubkey) as f:
                pubkey = rsa.PublicKey.load_pkcs1(f.read())
            decodesign = binascii.unhexlify(signature)
            if rsa.verify(predata, decodesign, pubkey):
                print('Sign verification Passed...')
                return True
        except Exception as e:
            # if e.message == 'Verification failed':
            print('Sign verification failed...')
            return False

Exception on Verify Signature

merchant_info API will calculate Verify Signature only 5 parameters

  • "mobile"

  • "mch_id"

  • "account_type"

  • "business_mode"

  • "nonce_str"

that mean code will change to

Example code in Python

def verify_ksher_sign(self, signature, message):
        """
        Verify signature with public key object
        :param message:
        :param signature:
        :param mch_pubkey: pass in the publickey object
        :return:
        """
        # verify for https://api.mch.ksher.net/KsherPay/merchant_info
        if ("mobile" in message) and ("mch_id" in message) and ("account_type" in message) and ("business_mode" in message) and ("nonce_str" in message):
            merchant_info_verify_message = {
            "mobile": message.get("mobile",""),
            "mch_id": message.get("mch_id",""),
            "account_type": message.get("account_type",""),
            "business_mode": message.get("business_mode",""),
            "nonce_str": message.get("nonce_str","")
            }
            message = merchant_info_verify_message

        alist = []
        for key, value in message.items():
            strval = ""
            keystr = key
            if isinstance(value,str):
                strval = value
            elif isinstance(value,bytes):
                strval = value.encode('utf-8')
            else:
                strval = json.dumps(value, ensure_ascii=False, separators=(',',':'), sort_keys=True)
                if isinstance(strval,bytes):
                    strval = strval.encode('utf-8')
            if isinstance(key, bytes):
                keystr = key.encode('utf-8')
            print(keystr + "=" + strval)
            alist.append(keystr + "=" + strval)

        alist.sort()
        predata = "".join(alist)
        logging.debug(str(predata))

        predata = "".join(alist).encode('utf-8')
        decodesign = binascii.unhexlify(signature)

        print("text make sign:",predata)
        pubkey = ''
        try:
            with open(self.pubkey) as f:
                pubkey = rsa.PublicKey.load_pkcs1(f.read())
            decodesign = binascii.unhexlify(signature)
            if rsa.verify(predata, decodesign, pubkey):
                print('Sign verification Passed...')
                return True
        except Exception as e:
            # if e.message == 'Verification failed':
            print('Sign verification failed...')
            return False