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
-
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"
-
"待签名数据/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