欢迎使用 CipherBC API
接入准备
1 申请APP_ID
联系客服人员申请APP_ID,需要提供相关资质信息。 例如:公司名称,域名,IP, 第一负责人及其他商务需要信息。
2 提供需要支持的主链与合约币种清单
合约币需要合约地址、所属主链、精度、名称等基本信息给到业务对接人。
3 接口测试环境调试
接口测试环境对接,联调,演练。 接口采用 RSA2048位 双向签名验签形式确保通信可信性。 商户需要提前将公钥给到平台,用于平台对商户请求参数验签。 商户需要提前获取平台公钥,用于对平台响应结果验签。
支持公链
签名规则
<?php
$obj = new obj();
$obj->coin_list();
class obj
{
public $baseUrl = 'http://api.xxxxx.com/shopapi';
public $priKey = "-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC8d6RGCeQkrobx
+4xO1jXnHwiMU48UyOaRaNW2FMQrN9DcK/T2h58FyuA7kYN0CVNFEnaZQS9S8Nur
Tl0lOVJcbwKfCSnu9nmFYDvPWqlCTqQW3F83sKNi9IWrMFFny3CX35eUHpeJahE2
R9wma2oJIFJv3ux/I3+w8fQQ6zMPIqpDx4Tcu9sCVpot+/siWJDpyiQBlbXfcxuH
YOePUGx+tJ5HLXtJOUtPcgyhFTXRmW9r17DnnsjrT5Vc50IbN//JXbvr96eexQ/e
/IXHZP6MZWdthjuv9y2TaxS+JMnpKLrXfgazoQEsKmchAsTLEyX6tHG5tHq+3MaK
BSTFCoHHAgMBAAECggEAYodR1GPbZ+nuFpfhlF2ctIWe3G3awYz+LUrXX2ef3tBB
WrmISVJBqq+TZtD+Hi254MR0SE6xxPSygu5m98zt5/EJN8d8qrckdLwIyLCnCElA
s8rl5KFt6Y/YSdTq2g6bgGqePgt4QZi4At6yk0tYlgjV0DKFBTqzyPfVFvs/KEuZ
o8le0XjgJA+Rnti35Gi5UDL1Tn9LcmCU/JcdpksFgPvU/wPmlNb8azM68ougXcrf
hBebSRWbS0F2OXRJd7+hrTq+pwTNIH+IyzsUZHQCcH0tFe2Q304oDuVuwX5VxB3S
tYG8+LoECW1nKalJbolkmdRqVxZRXG/Ho8/aRCHBcQKBgQDBmoZzUaKU2F4Sa6P+
5vCQ5RAAiAATWao/WYfL9BBVnLzbkbzget3BNdJcJZKp1Avh6F/jeB0xUOD0aFgm
rJAZjVAFu77damKWwiEPxKfdgSbBedV/KWCMASzr4m4cdYKOcmlLFsh+Du2+oZW5
3Mn35hyfbTf1OyYxrvOVrhSu+wKBgQD5NVfxWTBsugAE++d4CCy8TUL7QCqb1I1v
EQ0FNdZsmXslPbCFlJ5UCFJXAF6s2x0MDKCl7y2ixlDo31H7DI6xlOP5kjYon7wr
mB0U2bclGtlJ3FvCwDBFkfZykYZqPyey8A3D+kf4pnvZvn/0BgKfWCVSYnqEB4mp
BI5WhywOpQKBgH4Mio8YTn8UZCMgG/UBDQ15ZbLC2THABj0aoHRkoiHrW3ala1DI
DgsWbLB78gJKQbZCofOqp28NVnkqWoJLhcJtI/Wnp/vmoOvA1t+6TQTFaqM7HIqf
OPHObh3iHYi/5VGzIeS2n7CbLaUY9S++lWymUGpoJK+wZAyi+IqyfVrzAoGAPKmI
g75zNDQ+pvfuy2AqC5g/Esv200H1P7EF21vUm1DtEg6nNo0L0WbJLFZxtWAM/Q7h
2CKVmEbwEQ+zdAoFfiLBL9Iwjsj7VB1ODvJES6ryc7FJQOnpljXGqPLVpm1DK6KV
pzv4YwWbplBh8zO3VbRuOVSxgAwZrWPnhhaXWeUCgYEAwVHYXN1qGTBS1uyrldDr
OMuXyfvE7lbfJlg2EfG2A7PtTooPkh1IbRjlJ8ACbiadM0pYhnGGPk0D1WSKvRxU
SmSc6PgW2weANQ8QCemo2V/zZ0DlWFZDdCV+7jMbAFBhQHcVh0qUr+uNyntjLTjj
OPdMSnhKLoKtleu+NiMIogc=
-----END PRIVATE KEY-----";
public $pubKey = "-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAk7K9s+r82lofBFaj3KWp
o/5E1ryDPDlLGZ4Rv68DcoCxV/bv0IvYX3cleC0WvTpwbdTpGtLjS6RHj89JREhi
svv5qmk5YTfjEqd3RmtYDmOVz2Ry6E76wLh9qi1pqpneMGr3shvEzZC6gkieXRCA
MOlEYbE7TSzxxDJxmovdeElowdRZcvwwPLvh/BLmCn+H1MXfdx01c/eFpTvu8BZG
1m7AqDOqnaPcR1ru/ekJoIwotF1jCPM2E5kAbQQutNaeSXhLbkfom4bxqF2/ni6k
Cwnz3fbBYcXP3Ww3/AdI1dn0iLr6450qn72yBpDtQY8OZ7+Krff4Fa5jOBY7OHEN
FwIDAQAB
-----END PUBLIC KEY-----";
public function coin_list()
{
$data = $this->get_public_data();
$data["chain"] = "eth";
$data["coin"] = "eth";
$data['sign'] = Signature::encryption($data, $this->priKey);
$res = self::curlByPost($this->baseUrl . '/coin/list', $data);
print_r($res);
// 验签
$resp = json_decode($res, true);
if (Signature::checkSignature($resp["sign"], $resp, $this->pubKey)) {
print_r("验签通过");
} else {
print_r("验签失败");
}
}
public static function curlByPost($url, $data = null, $timeout = 60)
{
$SSL = substr($url, 0, 8) == "https://" ? TRUE : FALSE;
$ch = curl_init();
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_URL, $url);
if ($SSL) {
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
}
$data = json_encode($data);
$headers = [
'Content-Type: application/json; charset=utf-8',
'Content-Length:' . strlen($data)
];
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_POST, TRUE);
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
$output = curl_exec($ch);
return $output;
}
public function get_public_data()
{
$data['app_id'] = "xxxxxx";
$data['version'] = '1.0';
$data['key_version'] = 'admin';
$data['time'] = time();
return $data;
}
}
class Signature
{
/**
* 私钥签名
*
* @param $data
* @param $private_key
*/
public static function encryption($data, $private_key)
{
if (is_array($data))
$signString = self::getSignString($data);
else
$signString = $data;
$privKeyId = openssl_pkey_get_private($private_key);
$signature = '';
openssl_sign($signString, $signature, $privKeyId, OPENSSL_ALGO_MD5);
openssl_free_key($privKeyId);
return base64_encode($signature);
}
/**
* 使用对方的公钥验签,并且判断签名是否匹配
*
* @param $sign
* @param $data
* @param $public_key
* @return bool
*/
public static function checkSignature($sign, $data, $public_key)
{
$toSign = self::getSignString($data);
$publicKeyId = openssl_pkey_get_public($public_key);
$result = openssl_verify($toSign, base64_decode($sign), $publicKeyId, OPENSSL_ALGO_MD5);
openssl_free_key($publicKeyId);
return $result === 1 ? true : false;
}
public static function getSignString($data)
{
unset($data['sign']);
ksort($data);
reset($data);
$pairs = array();
foreach ($data as $k => $v) {
if (is_array($v)) $v = self::arrayToString($v);
$pairs[] = "$k=$v";
}
return implode('&', $pairs);
}
private static function arrayToString($data)
{
$str = '';
foreach ($data as $list) {
if (is_array($list)) {
$str .= self::arrayToString($list);
} else {
$str .= $list;
}
}
return $str;
}
}
package main
import (
"crypto"
"crypto/md5"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/base64"
"encoding/json"
"encoding/pem"
"errors"
"fmt"
"github.com/imroc/req"
"github.com/tidwall/gjson"
"sort"
"time"
)
var (
BaseUrl = "http://api.xxxxx.com/shopapi"
signPrivateKey = -----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC8d6RGCeQkrobx
+4xO1jXnHwiMU48UyOaRaNW2FMQrN9DcK/T2h58FyuA7kYN0CVNFEnaZQS9S8Nur
Tl0lOVJcbwKfCSnu9nmFYDvPWqlCTqQW3F83sKNi9IWrMFFny3CX35eUHpeJahE2
R9wma2oJIFJv3ux/I3+w8fQQ6zMPIqpDx4Tcu9sCVpot+/siWJDpyiQBlbXfcxuH
YOePUGx+tJ5HLXtJOUtPcgyhFTXRmW9r17DnnsjrT5Vc50IbN//JXbvr96eexQ/e
/IXHZP6MZWdthjuv9y2TaxS+JMnpKLrXfgazoQEsKmchAsTLEyX6tHG5tHq+3MaK
BSTFCoHHAgMBAAECggEAYodR1GPbZ+nuFpfhlF2ctIWe3G3awYz+LUrXX2ef3tBB
WrmISVJBqq+TZtD+Hi254MR0SE6xxPSygu5m98zt5/EJN8d8qrckdLwIyLCnCElA
s8rl5KFt6Y/YSdTq2g6bgGqePgt4QZi4At6yk0tYlgjV0DKFBTqzyPfVFvs/KEuZ
o8le0XjgJA+Rnti35Gi5UDL1Tn9LcmCU/JcdpksFgPvU/wPmlNb8azM68ougXcrf
hBebSRWbS0F2OXRJd7+hrTq+pwTNIH+IyzsUZHQCcH0tFe2Q304oDuVuwX5VxB3S
tYG8+LoECW1nKalJbolkmdRqVxZRXG/Ho8/aRCHBcQKBgQDBmoZzUaKU2F4Sa6P+
5vCQ5RAAiAATWao/WYfL9BBVnLzbkbzget3BNdJcJZKp1Avh6F/jeB0xUOD0aFgm
rJAZjVAFu77damKWwiEPxKfdgSbBedV/KWCMASzr4m4cdYKOcmlLFsh+Du2+oZW5
3Mn35hyfbTf1OyYxrvOVrhSu+wKBgQD5NVfxWTBsugAE++d4CCy8TUL7QCqb1I1v
EQ0FNdZsmXslPbCFlJ5UCFJXAF6s2x0MDKCl7y2ixlDo31H7DI6xlOP5kjYon7wr
mB0U2bclGtlJ3FvCwDBFkfZykYZqPyey8A3D+kf4pnvZvn/0BgKfWCVSYnqEB4mp
BI5WhywOpQKBgH4Mio8YTn8UZCMgG/UBDQ15ZbLC2THABj0aoHRkoiHrW3ala1DI
DgsWbLB78gJKQbZCofOqp28NVnkqWoJLhcJtI/Wnp/vmoOvA1t+6TQTFaqM7HIqf
OPHObh3iHYi/5VGzIeS2n7CbLaUY9S++lWymUGpoJK+wZAyi+IqyfVrzAoGAPKmI
g75zNDQ+pvfuy2AqC5g/Esv200H1P7EF21vUm1DtEg6nNo0L0WbJLFZxtWAM/Q7h
2CKVmEbwEQ+zdAoFfiLBL9Iwjsj7VB1ODvJES6ryc7FJQOnpljXGqPLVpm1DK6KV
pzv4YwWbplBh8zO3VbRuOVSxgAwZrWPnhhaXWeUCgYEAwVHYXN1qGTBS1uyrldDr
OMuXyfvE7lbfJlg2EfG2A7PtTooPkh1IbRjlJ8ACbiadM0pYhnGGPk0D1WSKvRxU
SmSc6PgW2weANQ8QCemo2V/zZ0DlWFZDdCV+7jMbAFBhQHcVh0qUr+uNyntjLTjj
OPdMSnhKLoKtleu+NiMIogc=
-----END PRIVATE KEY-----
signPublicKey = -----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAk7K9s+r82lofBFaj3KWp
o/5E1ryDPDlLGZ4Rv68DcoCxV/bv0IvYX3cleC0WvTpwbdTpGtLjS6RHj89JREhi
svv5qmk5YTfjEqd3RmtYDmOVz2Ry6E76wLh9qi1pqpneMGr3shvEzZC6gkieXRCA
MOlEYbE7TSzxxDJxmovdeElowdRZcvwwPLvh/BLmCn+H1MXfdx01c/eFpTvu8BZG
1m7AqDOqnaPcR1ru/ekJoIwotF1jCPM2E5kAbQQutNaeSXhLbkfom4bxqF2/ni6k
Cwnz3fbBYcXP3Ww3/AdI1dn0iLr6450qn72yBpDtQY8OZ7+Krff4Fa5jOBY7OHEN
FwIDAQAB
-----END PUBLIC KEY-----
)
func main() {
pub := make(map[string]string)
// 公共参数
pub["app_id"] = "xxxxxxxxx"
pub["version"] = "1.0"
pub["key_version"] = "admin"
pub["time"] = fmt.Sprintf("%d", time.Now().Unix())
// 业务参数
pub["chain"] = "eth"
pub["coin"] = "eth"
pub["sign"] = getSign(pub)
url := fmt.Sprintf("%s/coin/list", BaseUrl)
r := req.New()
r.EnableInsecureTLS(true)
post, err := r.Post(url, req.BodyJSON(&pub))
if err != nil {
panic(err)
}
fmt.Println("返回", post.String())
// 验签
checkSign(post.String())
}
// 获取签名串
func getSign(data map[string]string) string {
err, signStr := ApiSign(data, signPrivateKey)
if err != nil {
panic(err)
}
fmt.Println("签名串:", signStr)
return signStr
}
// 验证签名
func checkSign(resp string) {
var res map[string]interface{}
err := json.Unmarshal([]byte(resp), &res)
if err != nil {
panic(err)
}
sign := res["sign"].(string)
data := ParseJsonToClassMap(resp)
err = ApiVerifySign(data, signPublicKey, sign)
if err != nil {
fmt.Println("验签失败")
return
}
fmt.Println("验签通过")
}
/**
* 本地私钥签名
* @param data 参数
* @param priKey 本地私钥
* @return error
* @return string
/
func ApiSign(data map[string]string, priKey string) (error, string) {
hashed := getSignString(data)
// rsa
block, _ := pem.Decode([]byte(priKey))
if block == nil {
return errors.New("private key error"), ""
}
privateKey, err := x509.ParsePKCS8PrivateKey(block.Bytes)
if err != nil {
return err, ""
}
signature, err := rsa.SignPKCS1v15(rand.Reader, privateKey.(rsa.PrivateKey), crypto.MD5, hashed)
if err != nil {
return err, ""
}
ciphertext := base64.StdEncoding.EncodeToString(signature)
return nil, ciphertext
}
/**
* 使用对方的公钥验签,并且判断签名是否匹配
* @param data 参数
* @param signed 签名体
* @param pubKey 公钥
* @return error
/
func ApiVerifySign(data map[string]string, pubKey string, signed string) error {
hashed := getSignString(data)
block, _ := pem.Decode([]byte(pubKey))
if block == nil {
return errors.New("public key error")
}
// 解析公钥
pubInterface, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
return err
}
pub := pubInterface.(rsa.PublicKey)
decodeString, err := base64.StdEncoding.DecodeString(signed)
if err != nil {
return err
}
err = rsa.VerifyPKCS1v15(pub, crypto.MD5, hashed, decodeString)
if err != nil {
return err
}
return nil
}
// 拼接参数
func getSignString(data map[string]string) []byte {
delete(data, "sign")
var keys []string
for k := range data {
keys = append(keys, k)
}
sort.Strings(keys)
var signStr string
for _, k := range keys {
if signStr != "" {
signStr = signStr + "&" + k + "=" + data[k]
} else {
signStr = k + "=" + data[k]
}
}
hashMd5 := md5.Sum([]byte(signStr))
hashed := hashMd5[:]
return hashed
}
func ParseJsonToClassMap(jsonStr string) (m map[string]string) {
m = make(map[string]string)
result := gjson.Parse(jsonStr)
result.ForEach(func(key, value gjson.Result) bool {
if value.Type == gjson.JSON && gjson.Valid(value.String()) {
m[key.String()] = JsonToString(value.String())
} else {
m[key.String()] = value.String()
}
return true
})
return
}
func JsonToString(jsonStr string) string {
str := ""
r := gjson.Parse(jsonStr)
r.ForEach(func(key, value gjson.Result) bool {
if value.Type == gjson.JSON && gjson.Valid(value.String()) {
str += JsonToString(value.String())
} else {
str += value.String()
}
return true
})
return str
}
var crypto = require('crypto');
var priKey = -----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC8d6RGCeQkrobx
+4xO1jXnHwiMU48UyOaRaNW2FMQrN9DcK/T2h58FyuA7kYN0CVNFEnaZQS9S8Nur
Tl0lOVJcbwKfCSnu9nmFYDvPWqlCTqQW3F83sKNi9IWrMFFny3CX35eUHpeJahE2
R9wma2oJIFJv3ux/I3+w8fQQ6zMPIqpDx4Tcu9sCVpot+/siWJDpyiQBlbXfcxuH
YOePUGx+tJ5HLXtJOUtPcgyhFTXRmW9r17DnnsjrT5Vc50IbN//JXbvr96eexQ/e
/IXHZP6MZWdthjuv9y2TaxS+JMnpKLrXfgazoQEsKmchAsTLEyX6tHG5tHq+3MaK
BSTFCoHHAgMBAAECggEAYodR1GPbZ+nuFpfhlF2ctIWe3G3awYz+LUrXX2ef3tBB
WrmISVJBqq+TZtD+Hi254MR0SE6xxPSygu5m98zt5/EJN8d8qrckdLwIyLCnCElA
s8rl5KFt6Y/YSdTq2g6bgGqePgt4QZi4At6yk0tYlgjV0DKFBTqzyPfVFvs/KEuZ
o8le0XjgJA+Rnti35Gi5UDL1Tn9LcmCU/JcdpksFgPvU/wPmlNb8azM68ougXcrf
hBebSRWbS0F2OXRJd7+hrTq+pwTNIH+IyzsUZHQCcH0tFe2Q304oDuVuwX5VxB3S
tYG8+LoECW1nKalJbolkmdRqVxZRXG/Ho8/aRCHBcQKBgQDBmoZzUaKU2F4Sa6P+
5vCQ5RAAiAATWao/WYfL9BBVnLzbkbzget3BNdJcJZKp1Avh6F/jeB0xUOD0aFgm
rJAZjVAFu77damKWwiEPxKfdgSbBedV/KWCMASzr4m4cdYKOcmlLFsh+Du2+oZW5
3Mn35hyfbTf1OyYxrvOVrhSu+wKBgQD5NVfxWTBsugAE++d4CCy8TUL7QCqb1I1v
EQ0FNdZsmXslPbCFlJ5UCFJXAF6s2x0MDKCl7y2ixlDo31H7DI6xlOP5kjYon7wr
mB0U2bclGtlJ3FvCwDBFkfZykYZqPyey8A3D+kf4pnvZvn/0BgKfWCVSYnqEB4mp
BI5WhywOpQKBgH4Mio8YTn8UZCMgG/UBDQ15ZbLC2THABj0aoHRkoiHrW3ala1DI
DgsWbLB78gJKQbZCofOqp28NVnkqWoJLhcJtI/Wnp/vmoOvA1t+6TQTFaqM7HIqf
OPHObh3iHYi/5VGzIeS2n7CbLaUY9S++lWymUGpoJK+wZAyi+IqyfVrzAoGAPKmI
g75zNDQ+pvfuy2AqC5g/Esv200H1P7EF21vUm1DtEg6nNo0L0WbJLFZxtWAM/Q7h
2CKVmEbwEQ+zdAoFfiLBL9Iwjsj7VB1ODvJES6ryc7FJQOnpljXGqPLVpm1DK6KV
pzv4YwWbplBh8zO3VbRuOVSxgAwZrWPnhhaXWeUCgYEAwVHYXN1qGTBS1uyrldDr
OMuXyfvE7lbfJlg2EfG2A7PtTooPkh1IbRjlJ8ACbiadM0pYhnGGPk0D1WSKvRxU
SmSc6PgW2weANQ8QCemo2V/zZ0DlWFZDdCV+7jMbAFBhQHcVh0qUr+uNyntjLTjj
OPdMSnhKLoKtleu+NiMIogc=
-----END PRIVATE KEY-----
var pubKey = -----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAk7K9s+r82lofBFaj3KWp
o/5E1ryDPDlLGZ4Rv68DcoCxV/bv0IvYX3cleC0WvTpwbdTpGtLjS6RHj89JREhi
svv5qmk5YTfjEqd3RmtYDmOVz2Ry6E76wLh9qi1pqpneMGr3shvEzZC6gkieXRCA
MOlEYbE7TSzxxDJxmovdeElowdRZcvwwPLvh/BLmCn+H1MXfdx01c/eFpTvu8BZG
1m7AqDOqnaPcR1ru/ekJoIwotF1jCPM2E5kAbQQutNaeSXhLbkfom4bxqF2/ni6k
Cwnz3fbBYcXP3Ww3/AdI1dn0iLr6450qn72yBpDtQY8OZ7+Krff4Fa5jOBY7OHEN
FwIDAQAB
-----END PUBLIC KEY-----
/**
* 私钥签名
*/
function encryption(data) {
let signString = getSignString(data)
let sign = crypto.createSign('md5WithRSAEncryption');
sign.update(signString, 'utf8');
sign.end();
let signature = sign.sign(priKey, 'base64');
return signature
}
/**
* 使用对方的公钥验签,并且判断签名是否匹配
* @param sign
* @param data
* @return bool
*/
function checkSignature(sign, data) {
let signString = getSignString(data)
let verify = crypto.createVerify('md5WithRSAEncryption');
verify.update(signString, 'utf8');
verify.end();
return verify.verify(pubKey, sign, 'base64');
}
// 拼接参数
function getSignString(data) {
delete(data["sign"])
var keys = Object.keys(data)
keys = keys.sort()
let signStr = ""
for(let k in keys) {
if(typeof(data[keys[k]]) == "object") {
signStr = keys[k] + "=" + arrayToString(data[keys[k]])
}else {
if (signStr != "") {
signStr = signStr + "&" + keys[k] + "=" + data[keys[k]]
} else {
signStr = keys[k] + "=" + data[keys[k]]
}
}
}
return signStr
}
function arrayToString(data) {
let str = '';
for(let list in data) {
if (typeof (data[list]) == "object") {
str += arrayToString(data[list]);
} else {
str += data[list];
}
}
return str;
}
//发送请求
function httpByPost(data) {
let HTTP = require('http')
let POST_OPTIONS = {
port: 8888,
host: "http://api.xxxxx.com/shopapi",
path: "/coin/list",
method: 'POST',
headers: {
"Content-Type": "application/json"
}
};
// 创建 http 连接
const REQUEST = HTTP.request(POST_OPTIONS, requestOnResponse)
// 设置超时
REQUEST.setTimeout(6000)
// 通过连接发送数据
REQUEST.write(JSON.stringify(data), 'utf8')
REQUEST.end()
}
// 接受返回的数据验签
function requestOnResponse(incomingMessage){
let data = []
incomingMessage.on('data', chunk => {
data.push(...chunk)
})
incomingMessage.on('end', () => {
let _date = JSON.parse( new TextDecoder().decode(new Uint8Array(data)))
let res = checkSignature(_date["sign"], _date)
console.log(res)
})
}
/**测试*****/
let data = {
'app_id' : 'xxxxxxxxx',
'version' : '1.0',
'key_version' : 'admin',
'time' : Math.floor(Date.now()/1000),
'chain' :'eth',
'coin' : 'eth',
}
data['sign'] = encryption(data)
httpByPost(data);
package com.junior.common;
import com.google.gson.*;
import java.io.;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.security.;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.*;
import java.util.stream.Collectors;
public class rsaSigner {
public static void main(String[] args) {
String baseUrl = "http://api.xxxxx.com/shopapi";
String priKey = "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCtdgvcfozY7fe/x6UlXR/Fff8+wX++CiG05YVRafDe90O8y5KJAMZ156TgajEUXSRyKZqASz7iVoMAcu5FqfK1KuOOdjTAg0LFe/twIZvKmAqHcCSS2pjUXk0fTpZEgQ5lmDCK12Mg07YXvr+BeHjF+kBpwRJMPMG/+DGBDYNFNeRSJqWyopt6yyF528soAykHIUQ7TTultJV5IPCrvjRQQrGdJ/QtCdE0vZg+tA09nFN7AqgraU99kIvzyS0sqysj+pP1SJRmTRSGtlVAO3mILfkXOekZl1KD60G7CF+7BIaaW83FPD55tZhAGhnbLMYyiq7OiIyU61LTRcNSaJ0tAgMBAAECggEBAIGgGKcSzxCBbMYdXLV6TPbZ/HeaRGrwyVWUu7cmc0E8CJu6iWvmb2jGzbiCwuCT5luF6ZZ8JKchvU4FlTfsE5r9TQY2IZ/Ht4s65qBaDUEts5iY3kv8HX9+ZSXDJZpV0ztqqsPmx4ZNj+NYwWXwcFiKdb5R8OmV8bgSsnPddD6wFhwewglUOBPRAjropHE4sqk1Vam6q1MlFq5kiekMn/CItyFYFFzST/ruqAtVTp7YIvazAMrfCKH+pvXWk6pYNHZmL/JZey0GglRyJ1nItiqL33X8Lji056FcbAclidq022h/KPLEJpZk6irWqYdqYZeF6YMLHtdZFDkTZ/hXY+UCgYEA5i794UTQ1Kd6K+iui/S8K5H8nGdiXb3+uZ5j5VKYfbRmzXhAdVYisYe8czeTECxhH+MeHgM4tLR4ifx9q60ylvZxl9Fav1p6EaE36YcvGVmpm7Gm3q6QDDBa4gB+X9u7CxCS4LpotV917YS1QvOIF9lnRonSj1zNzFtbXkywrN8CgYEAwOpx/mR40ZvgZxNCq9DsIdrSZYAFFKpw0xy0OtF1PpuwMaREoRabnlVw6Bb3Hofg50XA3JxemzAX6fB92ee+NfXfeL4UtC4vG1K9JyiXHtHq8p9Uw2W03ehIEeNa3xdoRRnLmD4VUpyfL19ViE2+7TM1Gd3TogjDT+AMOCSuq3MCgYBsurPH7gaq/LVT+mRAzgj4l8v4YUlwuGeTbIMJdvt7HXUWB4CDLH3U2CYnUpAQKrZyJok6ahEmIr1xiKggKP7lmmHL8eNo0icpHrtXfzi7Q8Q/PCpzs4dtioXTjaIkS5nNvzVyG/uL+RyuZmpsxrZ5dYM4KbAhchfwORMutxEZhwKBgQC5zpVw4jCEItBmNuTWO+nTScGvxTgfiXIVw+XLaQa2AJoZlhAL34yPWdffko79tv3lgweY9HsimZXO2rU8dbp8mo5c6ydhy8HPXUeWOcAkDSdv/ApWENW9jgYsRIC3swHY3Fl+Dv3Wjce8huQI3mjwaYvRmBhIToxfmHnscVhTBQKBgB9uciEHfz8hMI7ePrPo2PkyK/RbJDm3HnuWE2lFBp7MOUMlh53MXwv4dIRaYMbRrFuN1UIKEFxuKhWwTHSzZJufk/UENrha/81fpj2BoFsAKkEunKeETvaIh6fZSjec16h2+zxbzwkdS6b+yIYkSlaoAxmDYrMtae0C8G4e0YS9";
String pubKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2RZZISModRzproL3eTxRtvVK/nToDbeLmAVdmaVtHgNCwjXMeNCZvHySZKO7+t7XySzBd2PhhQjvAkDd8HRKB5Y0uvP6TFwvhxOMkMydTeBFcUobQWNeAI7ttRms1txSNrmyrZr0WpWfk79VIsO+yI/jFN/Pj2gxD+vh5UUKU1LD2IQq/VxgtEpf8Vs6w7SvafPwYkpdPGCKUFkRvN1FrY2+tq0j1MRVJbB2rIunWDnT8cDUh2sWLXf4yNaJVOaaTFB0zbAk/qcoicGQyW7bVG7dVW8WywKmOAPeQCCQa+SJ4BhfI1k8eEAwFVRuxz/f9Ecfog9hZJ+RgELkpS8qhQIDAQAB";
rsaSigner signer = new rsaSigner();
Map<String, Object> params = new HashMap<>();
params.put("app_id", "hxgmau9imt86qky9");
params.put("version", "1.0");
params.put("key_version", "admin");
params.put("time", Long.toString(System.currentTimeMillis() / 1000));
params.put("coin", "trx");
try {
params.put("sign", signer.genSign(params, priKey));
Gson gson = new GsonBuilder().create();
System.out.println("params = " + gson.toJson(params));
String res = doPost(baseUrl + "/address/getBatch", gson.toJson(params));
if (null == res) {
throw new RuntimeException("http error");
}
System.out.println("response = " + res);
JsonParser jp = new JsonParser();
JsonObject resEle = jp.parse(res).getAsJsonObject();
boolean retSignOK = signer.verifySign(resEle, resEle.get("sign").getAsString(), pubKey);
if (!retSignOK) {
throw new RuntimeException("verify response sign fail");
}
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("OK");
}
public String genSign(Map<String, Object> data, String priKey) throws NoSuchAlgorithmException, InvalidKeySpecException, SignatureException, InvalidKeyException {
Map<String, String> stringMap = toStringMap(new GsonBuilder().create().toJson(data));
return this.sign(this.genSignString(stringMap), priKey);
}
public String genSignString(Map<String, String> params) {
if (null == params || params.size() == 0) {
return "";
}
List<String> sortedKeys = new ArrayList<>(params.keySet());
sortedKeys.remove("sign");
sortedKeys.sort(null);
String sb = sortedKeys.stream().map(s -> s + "=" + params.get(s)).collect(Collectors.joining("&"));
System.out.println("genSignString = " + sb);
return sb;
}
public String sign(String str, String priKey) throws NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException, SignatureException {
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
byte[] decodedKey = Base64.getDecoder().decode(priKey.getBytes(StandardCharsets.UTF_8));
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(decodedKey);
PrivateKey pk = keyFactory.generatePrivate(keySpec);
Signature signature = Signature.getInstance("MD5withRSA");
signature.initSign(pk);
signature.update(str.getBytes(StandardCharsets.UTF_8));
return new String(Base64.getEncoder().encode(signature.sign()), StandardCharsets.UTF_8);
}
public boolean verifySign(JsonElement data, String sign, String pubKey) throws NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException, SignatureException {
Map<String, String> resMap = toStringMap(data);
String signString = this.genSignString(resMap);
byte[] keyBytes = Base64.getDecoder().decode(pubKey.getBytes(StandardCharsets.UTF_8));
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey key = keyFactory.generatePublic(keySpec);
Signature signature = Signature.getInstance("MD5withRSA");
signature.initVerify(key);
signature.update(signString.getBytes(StandardCharsets.UTF_8));
return signature.verify(Base64.getDecoder().decode(sign.getBytes(StandardCharsets.UTF_8)));
}
public static Map<String, String> toStringMap(String str) {
JsonParser jp = new JsonParser();
JsonElement jEle = jp.parse(str);
return toStringMap(jEle);
}
public static Map<String, String> toStringMap(JsonElement jEle) {
Map<String, String> resMap = new HashMap<>();
if (null != jEle && jEle.isJsonObject()) {
for (Map.Entry<String, JsonElement> entry : jEle.getAsJsonObject().entrySet()) {
resMap.put(entry.getKey(), valueAsString(entry.getValue()));
}
}
return resMap;
}
public static String valueAsString(JsonElement element) {
if (element.isJsonNull()) {
return "";
} else if (element.isJsonPrimitive()) {
return element.getAsJsonPrimitive().getAsString();
} else if (element.isJsonObject()) {
StringBuilder sb = new StringBuilder();
for (Map.Entry<String, JsonElement> entry : element.getAsJsonObject().entrySet()) {
sb.append(valueAsString(entry.getValue()));
}
return sb.toString();
} else if (element.isJsonArray()) {
StringBuilder sb = new StringBuilder();
for (JsonElement vv : element.getAsJsonArray()) {
sb.append(valueAsString(vv));
}
return sb.toString();
}
return "";
}
public static String doPost(String httpUrl, String param) {
//send http post request
}
}
创建私钥
openssl genrsa -out rsa_private_key.pem 2048
根据私钥创建公钥
openssl rsa -in rsa_private_key.pem -out rsa_public_key.pem -pubout
签名算法详解
- 1. 获取请求参数并进行格式化获得一个新的参数格式化后的字符串:
- 2. 对第一步data进行用RSA私钥签名并把签名结果保存到一个变量中:
生成签名字符串, 对于如下的参数数组:
user_id = 1
coin = eth
address = 0x038B8E7406dED2Be112B6c7E4681Df5316957cad
amount = 10.001
trade_id = 20220131012030274786
对数组里的每一个键从a到z的顺序排序,若遇到相同首字母,则看第二个字母,以此类推。排序完成之后,再把所有数组值以“&”字符连接起来,如$dataString:
address=0x038B8E7406dED2Be112B6c7E4681Df5316957cad&amount=10.001&coin=eth&trade_id=20220131012030274786&user_id=1
这串字符串便是拼接好的字符串。
公共信息说明
以下信息对所有接口均通用、后面每个接口不重复描述
生产环境
API接口地址请联系客服获得
测试环境
API接口地址请联系客服获得
请求公共参数
| 参数名 | 必选 | 类型 | 约束 | 案列 | 说明 |
|---|---|---|---|---|---|
| app_id | 是 | string | 系统生成 | A6vZLiqyRYxu | 商户号具体对接时候系统分配 |
| version | 是 | string | 固定 | 1.0 | 接口版本号-根据接入时候约定为准固定不变 |
| sign | 是 | string | 长度344位 | Dr7MnhPYh… | RSA-2048 位秘钥模式 |
| time | 是 | string | 长度 10 位 | 1578829811 | 时间戳-忽略时区以秒为单位的timestamp |
| key_version | 否 | string | 动态枚举型 | 默认:admin | 秘钥版本号:admin / read / arbitrary-可以针对任意1-n个接口授权 |
公共信息须知
| 名称 | 类型 | 案列 | 描述 |
|---|---|---|---|
| 时间1 | timeStamp | 1578829811 | UTC 时间统一时间不带时区 |
| 时间2 | DateTime | 2020-01-09 15:48:42 | 全局系统如果出现这种格式默认为北京时间 |
| 请求方式 | POST | — | 所有请求均为POST |
| 编码 | UTF-8 | — | 其他编码可能会产生乱码和错误 |
| 通信协议 | HTTPS | https | 目前仅支持HTTPS |
| 签名类型 | RSA-2048位 | Dr7MnhPYh… | 点击查看规则详情 |
| 请求与返回 | JSON | — | 所有请求和响应参数都是JSON格式 |
| 金额参数注意 | string | — | 小数点后超8位,小数点前超12位无法通过,最后无效0要删除 |