# 10.其他

# 10.1 服务域名

域名用途 公网(外部研发联调、联运游戏上线使用)
中台域名 https://open.kuaishou.com (opens new window)
支付域名 https://allin.kuaishoupay.com (opens new window)

# 10.2 支付签名示例

# PHP

# 签名

<?php
$data = array(
	"app_id" => "ks68xxxxxxxxxx756",
    "channel_id" => "ks",
    "role_id" => "10xxxxxxxx22",
    "role_name" => "神仙",
    "role_level" => 1,
    "server_id" => "6",
    "server_name" => "测试服务",
    "product_id" => "11001",
    "product_name" => "一零计划—60钻石包",
    "product_desc" => "一零计划—60钻石包",
    "money" => 100,
    "currency_type" => "CNY",
    "notify_url" => "https://kuaishou.com",
    "extension" => "15434xxxxxxxx3813",
    "user_ip" => "171.xx.xx.34",
    "third_party_trade_no" => "123456789"
    "game_id" => "1205920xxxxxxxxxxxxxxxx30acf74"

);
$private_key="-----BEGIN RSA PRIVATE KEY-----
MIIJKQIBAAKCAgxxxxxxxxxxxxxxxxxxxxxxxavcBjFHRi3oAhhVdujfAygE
-----END RSA PRIVATE KEY-----";

$ret = sha_sign($data, $priKey);
echo $ret;

function sha_sign($data, $priKey) {
    ksort($data);
    $str = "";
    foreach ($data as $k => $v)
    {
        if(!empty($v))
        {
            $str .= $k."=".$v."&";
        }
    }
    $newstr = substr($str,0,strlen($str)-1);

echo $newstr;
echo $private_key;
$pkeyid=openssl_get_privatekey($private_key);
openssl_sign($params_str,$sign,($pkeyid),OPENSSL_ALGO_SHA512);//OPENSSL_ALGO_SHA512
openssl_free_key($pkeyid);
$sign=(base64_encode($sign));//exit($sign);
return $sign;
}

# 验签

<?php
$public_key="-----BEGIN PUBLIC KEY-----
MIICIjANBgkqhkiG9xxxxxxxxxxxxxxxxxxxxxxxxAgEAqwyPnm1hhYZyNmvuHl6O
-----END PUBLIC KEY-----";

$receiveParams=array (
    "allin_trade_no" => "AI719xxxxxxxxxxxxxxx4313",
    "app_id" => "ks709xxxxxxxxxxxx615022",
    "data" => "{"environment":"PRODUCTION"}",
    "extension" => "KUAISHOU1544xxxxxx6519_a66f1xxx7f8ba_2810001",
    "money" => "100",
    "product_id" => "1",
    "role_id" => "100xxxx1",
    "server_id" => "281xxx1",
    "sign" => "c06C3X989YhP0YMSaYaviv/uEQM7ZaH4Ua/a91Pv0R5Qx7dLQnSNQBsfCxQbH0W/p90hx5k6tZ56wqOiq1hl+Efs8f6175H9Q8xxxxxxxxjCvu3Kjd9jMvxDivQKtYEnJkr+lM1GcBlUQLUINUwYRH74o4txCMAxxxxxxxxxy6kSPIhnycZzeLlSDfJU6f4h5n9iYdJX8RfVBsPh7/H2FCPUwyHGyFQ2ap9Q4UTHYrq7L2uTY9LHY="
);

$signstr =$receiveParams["sign"];
$sign_arr=array();
foreach ($receiveParams as $key => $value) {
    if($key!="sign"){
        $sign_arr[$key]=$value;
    }
}
ksort($sign_arr);
$params_str="";
foreach ($sign_arr as $key => $value) {
    $params_str.=$key."=".$value."&";
}
$params_str=substr($params_str,0,strlen($params_str)-1);

$public_key_res = openssl_get_publickey($public_key);
$ok = openssl_verify($params_str, base64_decode($signstr), $public_key_res, OPENSSL_ALGO_SHA512);

if(!$ok){
  echo "fail";
}else{
  echo "success";
}
?>

# nodeJs

const fs = require("fs");
const logger = require("log4js").getLogger("common");
const crypto = require("crypto");
const path = require("path");

/**
 *
 * @param {Object} opts
 * @param {String} opts.appId  快手分配appId
 * @param {String} opts.notifyUrl  快手服务器主动通知商户服务器里指定的页面http/https路径
 * @param {String} opts.rsaPrivate  app私钥pem文件路径
 * @param {String} opts.rsaPublic  快手公钥pem文件路径
 * @constructor
 */

class KwaiPay{
  constructor(){
    this.app = null;
    this.rsaPrivate = null;
    this.rsaPublic = null;
  }

  init(app) {
    this.app = app;

    let rsaPrivateKey = fs.readFileSync(path.resolve(__dirname + "/../../keys/rsa_private_key.pem"), "utf-8");
    if(rsaPrivateKey.indexOf("-----BEGIN RSA PRIVATE KEY-----") === -1){
      rsaPrivateKey = this.getPrivateKey(rsaPrivateKey);
    }

    let rsaPublicKey = fs.readFileSync(path.resolve(__dirname + "/../../keys/allinpay_public_key.pem"), "utf-8");
    if(rsaPublicKey.indexOf("-----BEGIN PUBLIC KEY-----") === -1){
      rsaPublicKey = this.getPublicKey(rsaPublicKey);
    }

    this.rsaPrivate = rsaPrivateKey;
    this.rsaPublic = rsaPublicKey;
  }

  wordwrap(str, width) {
    width = width || 64;
    if (!str) {
      return str;
    }
    let regex = "(.{1," + width + "})( +|$
?)|(.{1," + width + "})";
    return str.match(RegExp(regex, "g")).join("
");
  }


  getPublicKey (publicKey) {
    let content = "-----BEGIN PUBLIC KEY-----
";
    content += this.wordwrap(publicKey) + "
";
    content += "-----END PUBLIC KEY-----";
    return content;
  }

  getPrivateKey (privateKey) {
    let content = "-----BEGIN RSA PRIVATE KEY-----
";
    content += this.wordwrap(privateKey) + "
";
    content += "-----END RSA PRIVATE KEY-----";
    return content;
  }

  //得到编码前和编码后的参数信息
  getParams(opts){
    return this._encodeParams(opts);
  }

  //对编码前的参数签名
  getSign(opts) {
    return this._signParams(opts, this.rsaPrivate);
  }

  /**
   * 签名校验
   */
  signVerify(sign, param){
    let dsign = decodeURIComponent(sign);
    let tmp = this._encodeParams(param);
    logger.info(`[signVerify] unEncodeurl:${tmp.unencode}, encodeurl:${tmp.encode}, sign:${sign}`);
    return this._signVerify(tmp.unencode, dsign, this.rsaPublic);
  }

  /**
   * 对请求参数进行组装、编码
   * @param {Object} params  请求参数
   * @returns {Object}
   */
  _encodeParams(params) {
    let keys = [];
    for(let k in params) {
      let v = params[k];
      if (params[k] !== undefined && params[k] !== "") keys.push(k);
    }
    keys.sort();

    let unencodeStr = "";
    let encodeStr = "";
    let len = keys.length;
    for(let i = 0; i < len; ++i) {
      let k = keys[i];
      if(i !== 0) {
        unencodeStr += "&";
        encodeStr += "&";
      }
      unencodeStr += k + "=" + params[k];
      encodeStr += k + "=" + encodeURIComponent(params[k]);
    }
    return {unencode:unencodeStr, encode:encodeStr};
  }

  _signParams (params, privateKey) {
    let ret = this._encodeParams(params);
    let sign = this._sign(ret.unencode, privateKey);
    logger.info(`[signParams] unEncodeurl:${ret.unencode}, encodeurl:${ret.encode}, sign:${sign}`);
    return sign;//encodeURIComponent(sign);
  }

  /**
   * 对字符串进行签名
   * @param {String} str 要签名的字符串
   * @param {String} privateKey 商户应用私钥
   * @returns {String}
   */
  _sign(str, privateKey) {
    let sha = crypto.createSign("RSA-SHA512");
    sha.update(str, "utf8");
    return sha.sign(privateKey, "base64");
  }

  /**
   * 对字符串进行签名验证
   * @param {String} str 要验证的参数字符串
   * @param {String} sign 要验证的签名
   * @param {String} publicKey 支付宝公钥
   * @returns {Boolean}
   */
   _signVerify(str, sign, publicKey) {

    let verify = crypto.createVerify("RSA-SHA512");
    verify.update(str, "utf8");
    let result = verify.verify(publicKey, sign, "base64");
    return result;
  }
}

module.exports = new KwaiPay();

# java

// 签名算法
public static final String KEY_ALGORITHM = "RSA";
/**
 * 签名算法
 */
public static final String SIGN_ALGORITHMS = "SHA512WithRSA";

/**
 * 参与下单签名的字段
 */
public static final String[] keys = new String[]{
        "app_id", "channel_id", "game_id",
        "role_id", "role_name", "role_level",
        "server_id", "server_name",
        "product_id", "product_name", "product_desc",
        "money", "currency_type",
        "notify_url", "user_ip", "third_party_trade_no","extension"
};

/**
 * 获取签名串
 *
 */
public static String getSignStringFromMap(Map<String, String> map) {
    List<String> keysList = Arrays.asList(keys);
    List<String> list = new ArrayList<String>();
    for (Map.Entry<String, String> entry : map.entrySet()) {
        if (TextUtils.isEmpty(entry.getValue())) continue;
        if (keysList.contains(entry.getKey())) {
            list.add(entry.getKey() + "=" + entry.getValue());
        } else {
            System.out.println("all_" + entry.getKey() + " = " + entry.getValue());
        }
    }
    Collections.sort(list);
    return TextUtils.join("&", list);
}

/**
 * RSA签名
 *
 * @param content    待签名数据
 * @param privateKey 商户私钥
 * @param encode     字符集编码 utf-8
 * @return 签名值
 */
public static String sign(String src, String privateKey) throws Exception {
        PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKey));
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        PrivateKey privateKey2 = keyFactory.generatePrivate(pkcs8EncodedKeySpec);
        Signature signature = Signature.getInstance(SIGN_ALGORITHMS);
        signature.initSign(privateKey2);
        signature.update(src.getBytes(StandardCharsets.UTF_8));
        return Base64.getEncoder().encodeToString(signature.sign());
    }


/**
 * RSA验签名检查
 *
 * @param content   待签名数据
 * @param sign      签名值
 * @param publicKey 分配给开发商公钥
 * @param encode    字符集编码
 * @return 布尔值
 */
public static boolean check(String content, String sign, String publicKey, String encode) {
    try {
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        byte[] encodedKey = Base64.decode(publicKey, Base64.DEFAULT);
        PublicKey pubKey = keyFactory.generatePublic(new X509EncodedKeySpec(encodedKey));
        java.security.Signature signature = java.security.Signature.getInstance(SIGN_ALGORITHMS);

        signature.initVerify(pubKey);
        signature.update(content.getBytes(encode));
        return signature.verify(Base64.decode(sign, Base64.DEFAULT));

    } catch (Exception e) {
        e.printStackTrace();
    }

    return false;
}

# C#

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Web;

namespace RsaHelper{
    public class RasHelper{
        /// <summary>
        /// Signature  签名
        /// </summary>
        /// <param name="content">Content to be signed</param>
        /// <param name="privateKey">Private key</param>
        /// <returns></returns>
        public static string RasSha512Sign(string content, string privateKey){
            byte[] Data = Encoding.UTF8.GetBytes(content);
            RSACryptoServiceProvider rsa = DecodePemPrivateKey(privateKey);
            SHA512 sh = new SHA512CryptoServiceProvider();
            byte[] signData = rsa.SignData(Data, sh);
            return Convert.ToBase64String(signData);
        }


        /// <summary>
        /// Verify signature 验证签名
        /// </summary>
        /// <param name="content">Content to be verified</param>
        /// <param name="signedString">Signature result</param>
        /// <param name="publicKey">Public key</param>
        /// <returns></returns>
        public static bool RsaSha512Verify(string content, string signedString, string publicKey){
            signedString = signedString.Replace(" ", "+").Replace("u003d", "=");
            byte[] Data = Encoding.UTF8.GetBytes(content);
            byte[] data = Convert.FromBase64String(signedString);
            // 不同部分
            Byte[] byPkey = Convert.FromBase64String(publicKey);
            RSACryptoServiceProvider rsaPub = DecodeX509PublicKey(byPkey);


            SHA512 sh = new SHA512CryptoServiceProvider();
            return rsaPub.VerifyData(Data, sh, data);
        }

        /// <summary>
        /// Parse the private key of the PEM file generated by JAVA  解析由Java生成的PEM文件的私钥
        /// </summary>
        /// <param name="pemstr"></param>
        /// <returns></returns>
        private static RSACryptoServiceProvider DecodePemPrivateKey(String pemstr){
            byte[] pkcs8privatekey;
            pkcs8privatekey = Convert.FromBase64String(pemstr);
            if (pkcs8privatekey != null)            {

                RSACryptoServiceProvider rsa = DecodePrivateKeyInfo(pkcs8privatekey);
                return rsa;
            }
            else
                return null;
        }
        /// <summary>
        /// 解码私有密钥信息
        /// </summary>
        /// <param name="pkcs8"></param>
        /// <returns></returns>
        private static RSACryptoServiceProvider DecodePrivateKeyInfo(byte[] pkcs8){

            byte[] SeqOID = { 0x30, 0x0D, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01, 0x05, 0x00 };
            byte[] seq = new byte[15];

            MemoryStream mem = new MemoryStream(pkcs8);
            int lenstream = (int)mem.Length;
            BinaryReader binr = new BinaryReader(mem);    //wrap Memory Stream with BinaryReader for easy reading
            byte bt = 0;
            ushort twobytes = 0;

            try{
                twobytes = binr.ReadUInt16();
                if (twobytes == 0x8130) //data read as little endian order (actual data order for Sequence is 30 81)
                    binr.ReadByte();    //advance 1 byte
                else if (twobytes == 0x8230)
                    binr.ReadInt16();   //advance 2 bytes
                else
                    return null;


                bt = binr.ReadByte();
                if (bt != 0x02)
                    return null;

                twobytes = binr.ReadUInt16();

                if (twobytes != 0x0001)
                    return null;

                seq = binr.ReadBytes(15);       //read the Sequence OID
                if (!CompareBytearrays(seq, SeqOID))    //make sure Sequence for OID is correct
                    return null;

                bt = binr.ReadByte();
                if (bt != 0x04) //expect an Octet string
                    return null;

                bt = binr.ReadByte();       //read next byte, or next 2 bytes is  0x81 or 0x82; otherwise bt is the byte count
                if (bt == 0x81)
                    binr.ReadByte();
                else
                    if (bt == 0x82)
                        binr.ReadUInt16();
                //------ at this stage, the remaining sequence should be the RSA private key

                byte[] rsaprivkey = binr.ReadBytes((int)(lenstream - mem.Position));
                RSACryptoServiceProvider rsacsp = DecodeRSAPrivateKey(rsaprivkey);
                return rsacsp;
            }

            catch (Exception){return null;}

            finally { binr.Close();}

        }
        /// <summary>
        /// 对比
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        private static bool CompareBytearrays(byte[] a, byte[] b){
            if (a.Length != b.Length)
                return false;
            int i = 0;
            foreach (byte c in a){
                if (c != b[i])
                    return false;
                i++;
            }
            return true;
        }
        /// <summary>
        /// RSA密钥解码
        /// </summary>
        /// <param name="privkey"></param>
        /// <returns></returns>
        private static RSACryptoServiceProvider DecodeRSAPrivateKey(byte[] privkey){
            byte[] MODULUS, E, D, P, Q, DP, DQ, IQ;

            // ---------  Set up stream to decode the asn.1 encoded RSA private key  ------
            MemoryStream mem = new MemoryStream(privkey);
            BinaryReader binr = new BinaryReader(mem);    //wrap Memory Stream with BinaryReader for easy reading
            byte bt = 0;
            ushort twobytes = 0;
            int elems = 0;
            try{
                twobytes = binr.ReadUInt16();
                if (twobytes == 0x8130) //data read as little endian order (actual data order for Sequence is 30 81)
                    binr.ReadByte();    //advance 1 byte
                else if (twobytes == 0x8230)
                    binr.ReadInt16();   //advance 2 bytes
                else
                    return null;

                twobytes = binr.ReadUInt16();
                if (twobytes != 0x0102) //version number
                    return null;
                bt = binr.ReadByte();
                if (bt != 0x00)
                    return null;


                //------  all private key components are Integer sequences ----
                elems = GetIntegerSize(binr);
                MODULUS = binr.ReadBytes(elems);

                elems = GetIntegerSize(binr);
                E = binr.ReadBytes(elems);

                elems = GetIntegerSize(binr);
                D = binr.ReadBytes(elems);

                elems = GetIntegerSize(binr);
                P = binr.ReadBytes(elems);

                elems = GetIntegerSize(binr);
                Q = binr.ReadBytes(elems);

                elems = GetIntegerSize(binr);
                DP = binr.ReadBytes(elems);

                elems = GetIntegerSize(binr);
                DQ = binr.ReadBytes(elems);

                elems = GetIntegerSize(binr);
                IQ = binr.ReadBytes(elems);

                // ------- create RSACryptoServiceProvider instance and initialize with public key -----
                RSACryptoServiceProvider RSA = new RSACryptoServiceProvider();
                RSAParameters RSAparams = new RSAParameters();
                RSAparams.Modulus = MODULUS;
                RSAparams.Exponent = E;
                RSAparams.D = D;
                RSAparams.P = P;
                RSAparams.Q = Q;
                RSAparams.DP = DP;
                RSAparams.DQ = DQ;
                RSAparams.InverseQ = IQ;
                RSA.ImportParameters(RSAparams);
                return RSA;
            }
            catch (Exception){return null;}
            finally { binr.Close(); }
        }
        /// <summary>
        /// 获得整数
        /// </summary>
        /// <param name="binr"></param>
        /// <returns></returns>
        private static int GetIntegerSize(BinaryReader binr)
        {
            byte bt = 0;
            byte lowbyte = 0x00;
            byte highbyte = 0x00;
            int count = 0;
            bt = binr.ReadByte();
            if (bt != 0x02)     //expect integer
                return 0;
            bt = binr.ReadByte();

            if (bt == 0x81)
                count = binr.ReadByte();    // data size in next byte
            else
                if (bt == 0x82)
                {
                    highbyte = binr.ReadByte(); // data size in next 2 bytes
                    lowbyte = binr.ReadByte();
                    byte[] modint = { lowbyte, highbyte, 0x00, 0x00 };
                    count = BitConverter.ToInt32(modint, 0);
                }
                else
                {
                    count = bt;     // we already have the data size
                }

            while (binr.ReadByte() == 0x00)
            {   //remove high order zeros in data
                count -= 1;
            }
            binr.BaseStream.Seek(-1, SeekOrigin.Current);       //last ReadByte wasn"t a removed zero, so back up a byte
            return count;
        }

        #region Parse the PEM file generated by .net  解析.NET生成的PEM文件


        #region  公钥解析
        private static RSACryptoServiceProvider DecodeX509PublicKey(byte[] x509key)
        {
            byte[] SeqOID = { 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01 };

            MemoryStream ms = new MemoryStream(x509key);
            BinaryReader reader = new BinaryReader(ms);

            if (reader.ReadByte() == 0x30)
                ReadASNLength(reader); //skip the size
            else
                return null;

            int identifierSize = 0; //total length of Object Identifier section
            if (reader.ReadByte() == 0x30)
                identifierSize = ReadASNLength(reader);
            else
                return null;

            if (reader.ReadByte() == 0x06) //is the next element an object identifier?
            {
                int oidLength = ReadASNLength(reader);
                byte[] oidBytes = new byte[oidLength];
                reader.Read(oidBytes, 0, oidBytes.Length);

                if (oidBytes.SequenceEqual(SeqOID) == false) //is the object identifier rsaEncryption PKCS#1?
                    return null;

                int remainingBytes = identifierSize - 2 - oidBytes.Length;
                reader.ReadBytes(remainingBytes);
            }

            if (reader.ReadByte() == 0x03){ //is the next element a bit string?
                ReadASNLength(reader); //skip the size
                reader.ReadByte(); //skip unused bits indicator
                if (reader.ReadByte() == 0x30){
                    ReadASNLength(reader); //skip the size
                    if (reader.ReadByte() == 0x02){//is it an integer?
                        int modulusSize = ReadASNLength(reader);
                        byte[] modulus = new byte[modulusSize];
                        reader.Read(modulus, 0, modulus.Length);
                        if (modulus[0] == 0x00){ //strip off the first byte if it"s 0

                            byte[] tempModulus = new byte[modulus.Length - 1];
                            Array.Copy(modulus, 1, tempModulus, 0, modulus.Length - 1);
                            modulus = tempModulus;
                        }

                        if (reader.ReadByte() == 0x02){ //is it an integer?

                            int exponentSize = ReadASNLength(reader);
                            byte[] exponent = new byte[exponentSize];
                            reader.Read(exponent, 0, exponent.Length);

                            RSACryptoServiceProvider RSA = new RSACryptoServiceProvider();
                            RSAParameters RSAKeyInfo = new RSAParameters();
                            RSAKeyInfo.Modulus = modulus;
                            RSAKeyInfo.Exponent = exponent;
                            RSA.ImportParameters(RSAKeyInfo);
                            return RSA;
                        }
                    }
                }
            }
            return null;
        }

        private static int ReadASNLength(BinaryReader reader){
            //Note: this method only reads lengths up to 4 bytes long as
            //this is satisfactory for the majority of situations.
            int length = reader.ReadByte();
            if ((length & 0x00000080) == 0x00000080){ //is the length greater than 1 byte
                int count = length & 0x0000000f;
                byte[] lengthBytes = new byte[4];
                reader.Read(lengthBytes, 4 - count, count);
                Array.Reverse(lengthBytes); //
                length = BitConverter.ToInt32(lengthBytes, 0);
            }
            return length;
        }

        #endregion
        #endregion

    }
}