签名算法

    源串是由3部分内容用“&”拼接起来的: HTTP请求方式 & urlencode(uri) & urlencode(a=x&b=y&…)

    源串构造步骤如下:第1步:将请求的URI路径进行URL编码(URI不含host,URI示例:/openapi/apollo_verify_openid_openkey)。请开发者关注:URL编码注意事项,否则容易导致后面签名不能通过验证。

    第2步:将除“sig”外的所有参数按key进行字典升序排列。 注:除文档中特别标注了某参数不参与签名,否则除sig外的所有参数都要参与签名。

    第3步:将第2步中排序后的参数(key=value)用&拼接起来,并进行URL编码。URLENCODE时,要求对字符串中除了“-”、“_”、“.”之外的所有非字母数字字符都替换成百分号(%)后跟两位十六进制数。十六进制数中字母必须为大写。否则会导致校验不过。

    源串构造示例如下(由于是通用说明,这里以/openapi/apollo_verify_openid_openkey作为示例,且示例中的请求串不可直接复制访问)

    1. appkey228bf094169a40a3
    2. HTTP请求方式:POST
    3. 请求的URI路径(不含HOST):/openapi/apollo_verify_openid_openkey
    4. 请求参数:appid=1&gameid=2017&openid=222&openkey=1111&rnd=1512981097&sig=xxxxxxxx&ts=1111
    5. 2. 下面开始构造源串:
    6. 1步:将请求的URI路径进行URL编码,得到: %2Fopenapi%2Fapollo_verify_openid_openkey
    7. 2步:将除“sig”外的所有参数按key进行字典升序排列,排列结果为:appidgameidopenidopenkeyrndts
    8. 3步:将第2步中排序后的参数(key=value)用&拼接起来:
    9. appid=1&gameid=2017&openid=222&openkey=1111&rnd=1512981097&ts=1111
    10. 然后进行URL编码( 编码时请关注URL编码注意事项,否则容易导致后面签名不能通过验证),编码结果为:
    11. appid%3D1%26gameid%3D2017%26openid%3D222%26openkey%3D1111%26rnd%3D1512981097%26ts%3D1111
    12. 4步:将HTTP请求方式,第1步以及第3步中的到的字符串用&拼接起来,得到源串:
    13. POST&%2Fopenapi%2Fapollo_verify_openid_openkey&appid%3D1%26gameid%3D2017%26openid%3D222%26openkey%3D1111%26rnd%3D1512981097%26ts%3D1111

    Step 2. 构造密钥

    得到密钥的方式:在应用的appkey末尾加上一个字节的“&”,即appkey&,例如:

    • 使用HMAC-SHA1加密算法,使用Step2中得到的密钥对Step1中得到的源串加密。 (注:一般程序语言中会内置HMAC-SHA1加密算法的函数,例如PHP5.1.2之后的版本可直接调用hash_hmac函数,注意输出格式要选择二进制输出)

    • 然后将加密后的字符串经过Base64编码。 (注:一般程序语言中会内置Base64编码函数,例如PHP中可直接调用 base64_encode() 函数。)

    1. UUkRyyx0NVfIinwB8P/saj00df8=

    依赖的库

    • openssl

    • UrlEncode

    • Base64Encode

      1. int Base64Encode(const unsigned char * buffer, unsigned int length, char ** b64text, unsigned int & outlen)
      2. {
      3. int iRet = 0;
      4. //Encodes a binary safe base 64 string
      5. BIO *bio, *b64;
      6. BUF_MEM *bufferPtr;
      7. b64 = BIO_new(BIO_f_base64());
      8. bio = BIO_new(BIO_s_mem());
      9. bio = BIO_push(b64, bio);
      10. BIO_set_flags(bio, BIO_FLAGS_BASE64_NO_NL);
      11. iRet = BIO_write(bio, buffer, length);
      12. iRet = BIO_flush(bio);
      13. BIO_get_mem_ptr(bio, &bufferPtr);
      14. iRet = BIO_set_close(bio, BIO_NOCLOSE);
      15. memcpy(*b64text, bufferPtr->data, bufferPtr->length);
      16. (*b64text)[bufferPtr->length] = '\0';
      17. outlen = bufferPtr->length;
      18. BIO_free_all(bio);
      19. return (0);
      20. }
    • GetSignByHttpParamsSha1

    php签名类,引用自腾讯开放平台的openapi接口,使用makeSig接口获取签名,注意secret传入前在尾部加"&"

    1. <?php
    2. * 生成签名类
    3. *
    4. * @version 3.0.3
    5. * @author open.qq.com
    6. * @copyright © 2012, Tencent Corporation. All rights reserved.
    7. * @ History:
    8. * 3.0.3 | nemozhang | 2012-08-28 16:40:20 | support cpay callback sig verifictaion.
    9. * 3.0.2 | sparkeli | 2012-03-06 17:58:20 | add statistic fuction which can report API's access time and number to background server
    10. * 3.0.1 | nemozhang | 2012-02-14 17:58:20 | resolve a bug: at line 108, change 'post' to $method
    11. * 3.0.0 | nemozhang | 2011-12-12 11:11:11 | initialization
    12. */
    13. /**
    14. * 生成签名类
    15. */
    16. class SnsSigCheck
    17. {
    18. /**
    19. * 生成签名
    20. *
    21. * @param string $method 请求方法 "POST"
    22. * @param string $url_path
    23. * @param array $params 表单参数
    24. * @param string $secret 密钥
    25. */
    26. static public function makeSig($method, $url_path, $params, $secret)
    27. {
    28. $mk = self::makeSource($method, $url_path, $params);
    29. $my_sign = hash_hmac("sha1", $mk, strtr($secret, '-_', '+/'), true);
    30. $my_sign = base64_encode($my_sign);
    31. return $my_sign;
    32. }
    33. static private function makeSource($method, $url_path, $params)
    34. {
    35. $strs = strtoupper($method) . '&' . rawurlencode($url_path) . '&';
    36. ksort($params);
    37. $query_string = array();
    38. foreach ($params as $key => $val )
    39. {
    40. array_push($query_string, $key . '=' . $val);
    41. }
    42. $query_string = join('&', $query_string);
    43. }
    44. /**
    45. * 验证URL的签名 (注意和普通的OpenAPI签名算法不一样,详见@refer的说明)
    46. *
    47. * @param string $url_path
    48. * @param array $params 腾讯调用发货回调URL携带的请求参数
    49. * @param string $secret 密钥
    50. * @param string $sig 腾讯调用发货回调URL时传递的签名
    51. *
    52. * @refer
    53. * http://wiki.open.qq.com/wiki/%E5%9B%9E%E8%B0%83%E5%8F%91%E8%B4%A7URL%E7%9A%84%E5%8D%8F%E8%AE%AE%E8%AF%B4%E6%98%8E_V3
    54. */
    55. static public function verifySig($method, $url_path, $params, $secret, $sig)
    56. {
    57. unset($params['sig']);
    58. // 先使用专用的编码规则对value编码
    59. foreach ($params as $k => $v)
    60. {
    61. $params[$k] = self::encodeValue($v);
    62. }
    63. // 再计算签名
    64. $sig_new = self::makeSig($method, $url_path, $params, $secret);
    65. return $sig_new == $sig;
    66. }
    67. /**
    68. * URL专用的编码算法
    69. * 编码规则为:除了 0~9 a~z A~Z !*()之外其他字符按其ASCII码的十六进制加%进行表示,例如"-"编码为"%2D"
    70. * @refer
    71. * http://wiki.open.qq.com/wiki/%E5%9B%9E%E8%B0%83%E5%8F%91%E8%B4%A7URL%E7%9A%84%E5%8D%8F%E8%AE%AE%E8%AF%B4%E6%98%8E_V3
    72. */
    73. static private function encodeValue($value)
    74. {
    75. $rst = '';
    76. $len = strlen($value);
    77. for ($i=0; $i<$len; $i++)
    78. {
    79. $c = $value[$i];
    80. if (preg_match ("/[a-zA-Z0-9!\(\)*]{1,1}/", $c))
    81. {
    82. $rst .= $c;
    83. }
    84. else
    85. {
    86. $rst .= ("%" . sprintf("%02X", ord($c)));
    87. }
    88. }
    89. return $rst;
    90. }
    91. }
    92. // end of script