title: 签名与验签 header: develop nav: function

sidebar: sign_v2

本文介绍如何在半个小时之内生成调用开放平台接口的签名,及签名加密规则,实现轻松调用百度电商开放平台的相关接口。

在签名和验签过程中,如开发者在任何一个环节没有严格按照文档要求操作均会导致此“签名错误”,请认真阅读文档。

特别推荐

  1. 推荐使用 百度电商开放平台的SDK来生成签名。

    开放平台SDK使用说明

  2. 如果由于种种原因不愿意使用SDK,也可以参考里面的生成签名的代码,事半功倍,不用再次开发生成签名代码。

  3. SDK的demo中有完成调用开放平台接口的完整示例。

签名计算基本设定

  1. 百度电商开放平台的所有接口中的所有参数的编码都是UTF-8,所以跟平台交互或者计算签名的时候字符编码都是UTF-8, 暂时没有支持其他编码的打算,请开发者自行对于自己的代码编码进行转换

  2. 您如果不清楚的自己请求参数的编码,PHP可以使用mb_detect_encoding函数进行检测编码, 因Java没有直接的函数可以考虑使用以下代码进行检测

  1. String encode = "UTF-8";
  2. System.out.println( str.equals( new String(str.getBytes(encode),encode) );
  1. 请求接口的参数列表均为键值对,键名为字符串,键值也必须为字符串。

  2. 键值中如果为复杂数据类型,比如结构体. 数组. 对象都必须先转化成为字符串。

  3. 建议键名中有复杂数据类型,PHP先转换成为数组array,Java先转换成为HashMap,因为平台现在对于参数的形式, 复杂只支持Json类型,PHP的array类型和Java的HashMap类型都可以很方便转换成Json

  4. PHP中对于键名为数组情况,需要对数组中汉字进行urlencode函数进行编码,之后再json_encode。

筛选进行签名的参数

  1. 参数名为sign. sign_type参数不需要参与签名。

  2. 建议业务方也不需要在参数列表中进行签名参数之前,也不要有这样容易造成签名错误的参数

  3. 支付回调验签中全部参数签名是对平台的所有POST参数进行签名,空值参数同样要参与签名,如不参与签名,会导致验签不通过,如果商户URL里包含GET类型参数,不会参与签名。

对参数进行排序

将筛选的参数按照第一个字符的键值ASCII码递增排序(字母升序排序),如果遇到相同字符则按照第二个字符的键值ASCII码递增排序,以此类推。

  1. PHP采用ksort函数进行排序。

  2. Java先初始化ArrayList,然后使用sort方法进行排序。

对参数进行拼接组成待签名字符串

  1. 使用 = 和 & 将参数连接起来,将排序后的参数与其对应值,组合成“参数=参数值”的格式,并且把这些参数用&字符连接起来,此时生成的字符串为待签名字符串。
  1. orderId=800020199&tpOrderId=33330020199&status=2&unitPrice=800&count=2&payMoney=1200&promoMoney=100&hbMoney=100&hbBalanceMoney=100&giftCardMoney=100&dealId=7423328&payTime=1463037529&payType=9101&partnerId=1000000003&promoDetail={}
  1. 将待签名字符串和业务方私钥带入SHA1算法中得出sign

组成的待签名字符串为:

  1. count=2&dealId=7423328&giftCardMoney=100&hbBalanceMoney=100&hbMoney=100&orderId=800020199&partnerId=1000000003&payMoney=1200&payTime=1463037529&payType=9101&promoDetail={}&promoMoney=100&status=2&tpOrderId=33330020199&unitPrice=800
  1. 使用各自语言对应的SHA1WithRSA签名生成函数(如php: openssl_sign),传入待签名字符串. 业务方私钥,由SHA1算法中得出sign,然后base64encode。

示例生成的签名为:

  1. rsaSign=Gzu1RT2toJSDthcLPG1ZWROI3jzvxFtO7yCPUqMT3L7cmnARncm5IIIQ6x+7S/02zWxr5FC9945WFSurO9kepVbU7YS6Lh9SEVQhvTO0YKG7TlLFTpH3Ik7JeHQalAKXYe/jNREDpHmTF9Jrq/wABeZGYXJn1M75A37h9zUt+kw=

完整的请求URL为:

  1. https://xxx.tpbusiness.xxx/SyncPayInfo?orderId=800020199&tpOrderId=33330020199&status=2&unitPrice=800&count=2&payMoney=1200&promoMoney=100&hbMoney=100&hbBalanceMoney=100&giftCardMoney=100&dealId=7423328&payTime=1463037529&payType=9101&partnerId=1000000003&promoDetail={}&rsaSign=Gzu1RT2toJSDthcLPG1ZWROI3jzvxFtO7yCPUqMT3L7cmnARncm5IIIQ6x+7S/02zWxr5FC9945WFSurO9kepVbU7YS6Lh9SEVQhvTO0YKG7TlLFTpH3Ik7JeHQalAKXYe/jNREDpHmTF9Jrq/wABeZGYXJn1M75A37h9zUt+kw=

PHP相关的签名生成的参考代码

  1. PHP生成签名的工具类
  1. <?php
  2. /**
  3. * API入参静态检查类
  4. * 可以对API的参数类型. 长度. 最大值等进行校验
  5. *
  6. **/
  7. class NuomiRsaSign
  8. {
  9. /**
  10. * @desc 私钥生成签名字符串
  11. * @param array $assocArr
  12. * @param $rsaPriKeyStr
  13. * @return bool|string
  14. * @throws Exception
  15. */
  16. public static function genSignWithRsa(array $assocArr, $rsaPriKeyStr)
  17. {
  18. $sign = '';
  19. if (empty($rsaPriKeyStr) || empty($assocArr)) {
  20. return $sign;
  21. }
  22. if (!function_exists('openssl_pkey_get_private') || !function_exists('openssl_sign')) {
  23. throw new Exception("openssl扩展不存在");
  24. }
  25. $priKey = openssl_pkey_get_private($rsaPriKeyStr);
  26. if (isset($assocArr['sign'])) {
  27. unset($assocArr['sign']);
  28. }
  29. ksort($assocArr); //按字母升序排序
  30. $parts = array();
  31. foreach ($assocArr as $k => $v) {
  32. $parts[] = $k . '=' . $v;
  33. }
  34. $str = implode('&', $parts);
  35. openssl_sign($str, $sign, $priKey);
  36. openssl_free_key($priKey);
  37. return base64_encode($sign);
  38. }
  39. /**
  40. * @desc 公钥校验签名
  41. * @param array $assocArr
  42. * @param $rsaPubKeyStr
  43. * @return bool
  44. * @throws Exception
  45. */
  46. public static function checkSignWithRsa(array $assocArr, $rsaPubKeyStr)
  47. {
  48. if (!isset($assocArr['sign']) || empty($assocArr) || empty($rsaPubKeyStr)) {
  49. return false;
  50. }
  51. if (!function_exists('openssl_pkey_get_public') || !function_exists('openssl_verify')) {
  52. throw new Exception("openssl扩展不存在");
  53. }
  54. $sign = $assocArr['sign'];
  55. unset($assocArr['sign']);
  56. if (empty($assocArr)) {
  57. return false;
  58. }
  59. ksort($assocArr); //按字母升序排序
  60. $parts = array();
  61. foreach ($assocArr as $k => $v) {
  62. $parts[] = $k . '=' . $v;
  63. }
  64. $str = implode('&', $parts);
  65. $sign = base64_decode($sign);
  66. $pubKey = openssl_pkey_get_public($rsaPubKeyStr);
  67. $result = (bool)openssl_verify($str, $sign, $pubKey);
  68. openssl_free_key($pubKey);
  69. return $result;
  70. }
  71. }
  1. 调用PHP签名生成类生成签名的参考代码
  1. /**
  2. * 第一部分:从申请的私钥文件路径中读取出私钥的内容
  3. * @notice1: 私钥文件可以从任意存储介质中读取
  4. */
  5. $rsaPriviateKeyFilePath = 'rsa/rsa_private_key.pem';
  6. $rsaPrivateKey = file_get_contents($rsaPriviateKeyFilePath);
  7. /**
  8. * 第二部分:使用参数计算签名
  9. */
  10. $requestApiParamsArr = array('key1'=>'value1','key2'=>'value2');
  11. $rsaSign = NuomiRsaSign::genSignWithRsa( $requestApiParamsArr ,$rsaPrivateKey);

JAVA相关的签名生成的参考代码

  1. JAVA生成签名的工具类

com.nuomi.common.* 相关文件可以在开放平台的下载中找到

  1. /**
  2. * nuomi.com Inc.
  3. * Copyright (c) 2004-2018 All Rights Reserved.
  4. */
  5. package com.nuomi.util;
  6. import java.io.ByteArrayInputStream;
  7. import java.io.InputStream;
  8. import java.security.KeyFactory;
  9. import java.security.PrivateKey;
  10. import java.security.PublicKey;
  11. import java.security.spec.InvalidKeySpecException;
  12. import java.security.spec.PKCS8EncodedKeySpec;
  13. import java.security.spec.X509EncodedKeySpec;
  14. import java.util.ArrayList;
  15. import java.util.Collections;
  16. import java.util.List;
  17. import java.util.Map;
  18. import com.nuomi.common.NuomiApiException;
  19. import com.nuomi.common.NuomiConstants;
  20. import com.nuomi.util.codec.Base64;
  21. /**
  22. * 签名工具类
  23. * 目前只支持rsa方式 不支持rsa2
  24. */
  25. public class NuomiSignature {
  26. /**
  27. * 获取签名
  28. *
  29. * @param sortedParams 排序后的参数
  30. * @param privateKey 私钥
  31. * @return 返回签名后的字符串
  32. * @throws NuomiApiException
  33. */
  34. public static String genSignWithRsa(Map<String, String> sortedParams, String privateKey) throws NuomiApiException {
  35. String sortedParamsContent = getSignContent(sortedParams);
  36. return rsaSign(sortedParamsContent, privateKey, NuomiConstants.CHARSET_UTF8);
  37. }
  38. /**
  39. * 签名验证
  40. *
  41. * @param sortedParams
  42. * @param pubKey
  43. * @param sign
  44. * @return
  45. * @throws NuomiApiException
  46. */
  47. public static boolean checkSignWithRsa(Map<String, String> sortedParams, String pubKey, String sign)
  48. throws NuomiApiException {
  49. String sortedParamsContent = getSignContent(sortedParams);
  50. return doCheck(sortedParamsContent, sign, pubKey, NuomiConstants.CHARSET_UTF8);
  51. }
  52. /**
  53. * @param sortedParams 已经排序的字符串
  54. * @return 返回签名后的字符串
  55. */
  56. public static String getSignContent(Map<String, String> sortedParams) {
  57. StringBuffer content = new StringBuffer();
  58. List<String> keys = new ArrayList<String>(sortedParams.keySet());
  59. Collections.sort(keys);
  60. int index = 0;
  61. for (int i = 0; i < keys.size(); i++) {
  62. String key = keys.get(i);
  63. String value = sortedParams.get(key);
  64. content.append((index == 0 ? "" : "&") + key + "=" + value);
  65. index++;
  66. }
  67. return content.toString();
  68. }
  69. /**
  70. * sha1WithRsa 加签
  71. *
  72. * @param content 需要加密的字符串
  73. * @param privateKey 私钥
  74. * @param charset 字符编码类型 如:utf8
  75. * @return
  76. * @throws NuomiApiException
  77. */
  78. public static String rsaSign(String content, String privateKey,
  79. String charset) throws NuomiApiException {
  80. try {
  81. PrivateKey priKey = getPrivateKeyFromPKCS8(NuomiConstants.SIGN_TYPE_RSA,
  82. new ByteArrayInputStream(privateKey.getBytes()));
  83. java.security.Signature signature = java.security.Signature
  84. .getInstance(NuomiConstants.SIGN_ALGORITHMS);
  85. signature.initSign(priKey);
  86. if (StringUtils.isEmpty(charset)) {
  87. signature.update(content.getBytes());
  88. } else {
  89. signature.update(content.getBytes(charset));
  90. }
  91. byte[] signed = signature.sign();
  92. return new String(Base64.encodeBase64(signed));
  93. } catch (InvalidKeySpecException ie) {
  94. throw new NuomiApiException("RSA私钥格式不正确,请检查是否正确配置了PKCS8格式的私钥", ie);
  95. } catch (Exception e) {
  96. throw new NuomiApiException("RSAcontent = " + content + "; charset = " + charset, e);
  97. }
  98. }
  99. public static PrivateKey getPrivateKeyFromPKCS8(String algorithm,
  100. InputStream ins) throws Exception {
  101. if (ins == null || StringUtils.isEmpty(algorithm)) {
  102. return null;
  103. }
  104. KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
  105. byte[] encodedKey = StreamUtil.readText(ins).getBytes();
  106. encodedKey = Base64.decodeBase64(encodedKey);
  107. return keyFactory.generatePrivate(new PKCS8EncodedKeySpec(encodedKey));
  108. }
  109. /**
  110. * RSA验签名检查
  111. *
  112. * @param content 待签名数据
  113. * @param sign 签名值
  114. * @param publicKey 分配给开发商公钥
  115. * @param encode 字符集编码
  116. * @return 布尔值
  117. * @throws NuomiApiException
  118. */
  119. private static boolean doCheck(String content, String sign, String publicKey, String encode)
  120. throws NuomiApiException {
  121. try {
  122. KeyFactory keyFactory = KeyFactory.getInstance(NuomiConstants.SIGN_TYPE_RSA);
  123. byte[] bytes = publicKey.getBytes();
  124. byte[] encodedKey = Base64.decodeBase64(bytes);
  125. PublicKey pubKey = keyFactory.generatePublic(new X509EncodedKeySpec(encodedKey));
  126. java.security.Signature signature = java.security.Signature.getInstance(NuomiConstants.SIGN_ALGORITHMS);
  127. signature.initVerify(pubKey);
  128. signature.update(content.getBytes(encode));
  129. boolean bverify = signature.verify(Base64.decodeBase64(sign.getBytes()));
  130. return bverify;
  131. } catch (Exception e) {
  132. throw new NuomiApiException("RSA私钥格式不正确,请检查是否正确配置了PKCS8格式的私钥", e);
  133. }
  134. }
  135. }
  1. 调用JAVA签名生成类生成签名的参考代码

com.nuomi.common.* 相关文件可以在开放平台的下载中找到

  1. /**
  2. * 第一部分:赋值私钥
  3. * @notice1:因为JAVA中的字节流. 字符流. 双字符流等概念非常复杂,建议使用常量存储私钥文件
  4. * @notice2:私钥文件为不换行且不带私钥开头和结尾的字符串
  5. */
  6. String PRIVATE_KEY = "MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAM8vojS3eWYEnB0Xl73+8+D/xdzeWTvbZc0SPO6nKmm3WxBYX/fFI6S7DhhK0QAUKjbSD4hDyqLkgy8azi8ETvYSYIoTjdR55nNklCNQ9RAtPVeuAAFzN0h2DmfY3/F7FsUFg9Qd/9YaGpU+CEnZDThjvWxBg22HvlN09xKfAYltAgMBAAECgYAr8wDHifv4hhXPngeUSBbXran9NjVbmyi3HZ1LSq6WikyI5RZGas0qznso8AXxrFVgF6Mv1qGPeEXToi4GjzVoX5ocfUoSlqE5xmhdfmc4aqKz/BlncCVlgNnlQEp5oHpGiIzVEpabC4OiBMRAhi/Brvu14GOUkP1VEZmfuCQCCQJBAPKytmmzznsDaiO15AeorPi/nUNDMLoOoiFwZgUxXWW7PI+uZq1ja5NpMjuRu3eVt3dFexB7x+ZnBb9tWTGQtDMCQQDaiqc4vR1eiSpVMf+rB6+Xbj+dDrtoTaH66YrBKXE5tbWPlsm1MWWpmDREFntU+f3yAQqjgVAtCULmp8odkCvfAkEAge9aJ+dDIarnVW0ZQ1x0Fs0Hli5P1Rzmgn6ZsCgIt+Fxf/9AK44x1v8YDLpuIoz+Z5XEWEPc9yaq9hzGBvpQ7wJAErDLDnI2IdCvWyv0hscYgGYAcMlCw+/ny5LPuCd4NIxS493skF+SJ0gKKEyX7bOXwWvPYh58Ie3p19o/0flzlwJBAJ8Ut/aPdzIFIlvR8BdQ7O/6BCf2490vWjNrzu+TOWCEeEM4IMfgXSg3chhExJg8TXwU0IbiB5fnDeIreWbPPWY=";
  7. /**
  8. * 第二部分:计算签名
  9. */
  10. /**
  11. * 1.通过方法获取到所有需要需要参与签名的参数HashMap
  12. */
  13. HashMap apiParams = new HashMap();
  14. /**
  15. * 2.从常量中读取privateKey,然后计算RSA签名
  16. */
  17. String rsaSign = NuomiSignature.genSignWithRsa(apiParams,NuomiConstants.PRIVATE_KEY);

将平台公钥转换成正确格式的公钥

将应用详情页的平台公钥和应用公钥(这2个公钥是没有开始. 结尾标示和换行符的)转换成正确的公钥格式

1.PHP代码

  1. /**
  2. * @desc 密钥由字符串(不换行)转为PEM格式
  3. * @param $rsaKeyStr
  4. * @param int $keyType 0:公钥,1:私钥
  5. * @return string
  6. * @throws SF_Exception_InternalException
  7. */
  8. public static function convertRSAKeyStr2Pem($rsaKeyStr, $keyType = 0)
  9. {
  10. $rsaKeyPem = '';
  11. $beginPublicKey = '-----BEGIN PUBLIC KEY-----';
  12. $endPublicKey = '-----END PUBLIC KEY-----';
  13. $beginPrivateKey = '-----BEGIN PRIVATE KEY-----';
  14. $endPrivateKey = '-----END PRIVATE KEY-----';
  15. $keyPrefix = $keyType ? $beginPrivateKey : $beginPublicKey;
  16. $keySuffix = $keyType ? $endPrivateKey : $endPublicKey;
  17. $rsaKeyPem .= $keyPrefix. "\n";
  18. $rsaKeyPem .= wordwrap($rsaKeyStr, 64, "\n", true) . "\n";
  19. $rsaKeyPem .= $keySuffix;
  20. if(!function_exists('openssl_pkey_get_public') || !function_exists('openssl_pkey_get_private')){
  21. return false;
  22. }
  23. if($keyType == 0 && false == openssl_pkey_get_public($rsaKeyPem)){
  24. return false;
  25. }
  26. if($keyType == 1 && false == openssl_pkey_get_private($rsaKeyPem)){
  27. return false;
  28. }
  29. return $rsaKeyPem;
  30. }