欢迎光临
我们一直在努力

九八云百度小程序教程:签名与验签

  • 签名与验签
    • 基本限定
    • 签名规则
  • 签名计算过程示例
  • 签名工具参考代码
  • 验证
    • 加签逻辑验证
    • 验签逻辑验证

    签名与验签

    本章节主要介绍百度收银台使用的双向RSA加密签名规则与相关示例。

    基本限定

    1.统一字符集:UTF-8
    百度收银台接口中的所有参数字符集均保持为 UTF-8 ,与收银台交互接入或者计算签名时,要统一使用 UTF-8 ,暂不支持其他字符集,请接入业务方自行转换;
    2.请求接口的参数列表均为字符串类型键值对;
    3.键值中如果为复杂数据类型,比如结构体、数组、对象都必须先转化为 JSON 结构字符串;
    4.参数中包含汉字的部分,需要做 URLEncode 处理。

    签名规则

    1.排除参数列表中名为 sign 和 sign_type 的参数;
    2.将剩余参数按参数名字典序正序排列;
    3.将参数与其对应的值使用 “=” 连接,组成参数字符串,将参数字符串按排序结果,使用 “&” 连接,组成待签名字符串;
    4.将待签名字符串和业务方私钥使用 SHA1WithRSA 签名算法得出最终签名。

    签名计算过程示例

    使用密钥生成中的示例公私钥来做计算演示。

    1.初始请求业务参数

    参数名 示例取值
    appKey MMMabc
    dealId 470193086
    tpOrderId 3028903626
    totalAmount 11300

    2.生成待签名字符串

    
     
    1. appKey=MMMabc&dealId=470193086&totalAmount=11300&tpOrderId=3028903626

    3.生成最终签名串

    
     
    1. TN0ZNPyQeTnPjCN5hUa7JwrXOhR8uDASXPazidVQHFSiGCH5aouBkVvJxtf8PeqzGYWAASwS2oOt2eJfunzC5dTFd/pWJeJToMgCSgRY7KtQUCCDnMrtpqiMAf+dLiXps3HpWhVB4CK6MXfHc64ejP5a2fu5bg8B0BTcHrqaGc0=

    4.签名的完整请求参数

    参数名 示例取值
    appKey MMMabc
    dealId 470193086
    tpOrderId 3028903626
    totalAmount 11300
    rsaSign TN0ZNPyQeTnPjCN5hUa7JwrXOhR8uDASXPazidVQHFSiGCH5aouBkVvJxtf8PeqzGYWAASwS2oOt2eJfunzC5dTFd/pWJeJToMgCSgRY7KtQUCCDnMrtpqiMAf+dLiXps3HpWhVB4CK6MXfHc64ejP5a2fu5bg8B0BTcHrqaGc0=

    签名工具参考代码

    • PHP签名工具类
    
     
    1. <?php
    2. // 通用签名工具,基于openssl扩展,提供使用私钥生成签名和使用公钥验证签名的接口
    3. class RSASign
    4. {
    5. /**
    6. * @desc 使用私钥生成签名字符串
    7. * @param array $assocArr 入参数组
    8. * @param string $rsaPriKeyStr 私钥原始字符串,不含PEM格式前后缀
    9. * @return string 签名结果字符串
    10. * @throws Exception
    11. */
    12. public static function sign(array $assocArr, $rsaPriKeyStr)
    13. {
    14. $sign = '';
    15. if (empty($rsaPriKeyStr) || empty($assocArr)) {
    16. return $sign;
    17. }
    18. if (!function_exists('openssl_pkey_get_private') || !function_exists('openssl_sign')) {
    19. throw new Exception("openssl扩展不存在");
    20. }
    21. $rsaPriKeyPem = self::convertRSAKeyStr2Pem($rsaPriKeyStr, 1);
    22. $priKey = openssl_pkey_get_private($rsaPriKeyPem);
    23. if (isset($assocArr['sign'])) {
    24. unset($assocArr['sign']);
    25. }
    26. // 参数按字典顺序排序
    27. ksort($assocArr);
    28. $parts = array();
    29. foreach ($assocArr as $k => $v) {
    30. $parts[] = $k . '=' . $v;
    31. }
    32. $str = implode('&', $parts);
    33. openssl_sign($str, $sign, $priKey);
    34. openssl_free_key($priKey);
    35. return base64_encode($sign);
    36. }
    37. /**
    38. * @desc 使用公钥校验签名
    39. * @param array $assocArr 入参数据,签名属性名固定为rsaSign
    40. * @param string $rsaPubKeyStr 公钥原始字符串,不含PEM格式前后缀
    41. * @return bool true 验签通过|false 验签不通过
    42. * @throws Exception
    43. */
    44. public static function checkSign(array $assocArr, $rsaPubKeyStr)
    45. {
    46. if (!isset($assocArr['rsaSign']) || empty($assocArr) || empty($rsaPubKeyStr)) {
    47. return false;
    48. }
    49. if (!function_exists('openssl_pkey_get_public') || !function_exists('openssl_verify')) {
    50. throw new Exception("openssl扩展不存在");
    51. }
    52. $sign = $assocArr['rsaSign'];
    53. unset($assocArr['rsaSign']);
    54. if (empty($assocArr)) {
    55. return false;
    56. }
    57. // 参数按字典顺序排序
    58. ksort($assocArr);
    59. $parts = array();
    60. foreach ($assocArr as $k => $v) {
    61. $parts[] = $k . '=' . $v;
    62. }
    63. $str = implode('&', $parts);
    64. $sign = base64_decode($sign);
    65. $rsaPubKeyPem = self::convertRSAKeyStr2Pem($rsaPubKeyStr);
    66. $pubKey = openssl_pkey_get_public($rsaPubKeyPem);
    67. $result = (bool)openssl_verify($str, $sign, $pubKey);
    68. openssl_free_key($pubKey);
    69. return $result;
    70. }
    71. /**
    72. * @desc 将密钥由字符串(不换行)转为PEM格式
    73. * @param string $rsaKeyStr 原始密钥字符串
    74. * @param int $keyType 0 公钥|1 私钥,默认0
    75. * @return string PEM格式密钥
    76. * @throws Exception
    77. */
    78. public static function convertRSAKeyStr2Pem($rsaKeyStr, $keyType = 0)
    79. {
    80. $pemWidth = 64;
    81. $rsaKeyPem = '';
    82. $begin = '-----BEGIN ';
    83. $end = '-----END ';
    84. $key = ' KEY-----';
    85. $type = $keyType ? 'PRIVATE' : 'PUBLIC';
    86. $keyPrefix = $begin . $type . $key;
    87. $keySuffix = $end . $type . $key;
    88. $rsaKeyPem .= $keyPrefix . "\n";
    89. $rsaKeyPem .= wordwrap($rsaKeyStr, $pemWidth, "\n", true) . "\n";
    90. $rsaKeyPem .= $keySuffix;
    91. if (!function_exists('openssl_pkey_get_public') || !function_exists('openssl_pkey_get_private')) {
    92. return false;
    93. }
    94. if ($keyType == 0 && false == openssl_pkey_get_public($rsaKeyPem)) {
    95. return false;
    96. }
    97. if ($keyType == 1 && false == openssl_pkey_get_private($rsaKeyPem)) {
    98. return false;
    99. }
    100. return $rsaKeyPem;
    101. }
    102. }
    • Java签名工具类
    
     
    1. /*
    2. * Copyright (C) 2020 Baidu, Inc. All Rights Reserved.
    3. */
    4. package com.baidu.*;
    5. import static org.springframework.util.Assert.isTrue;
    6. import static org.springframework.util.Assert.notNull;
    7. import java.io.UnsupportedEncodingException;
    8. import java.net.URLEncoder;
    9. import java.security.KeyFactory;
    10. import java.security.NoSuchAlgorithmException;
    11. import java.security.PrivateKey;
    12. import java.security.PublicKey;
    13. import java.security.Signature;
    14. import java.security.spec.InvalidKeySpecException;
    15. import java.security.spec.PKCS8EncodedKeySpec;
    16. import java.security.spec.X509EncodedKeySpec;
    17. import java.util.Base64;
    18. import java.util.Comparator;
    19. import java.util.Map;
    20. import java.util.TreeMap;
    21. import org.springframework.util.CollectionUtils;
    22. import org.springframework.util.StringUtils;
    23. // 百度收银台双向RSA签名工具,JDK版本要求:1.8+
    24. public class RSASign {
    25. private static final String CHARSET = "UTF-8";
    26. private static final String SIGN_TYPE_RSA = "RSA";
    27. private static final String SIGN_ALGORITHMS = "SHA1WithRSA";
    28. private static final String SIGN_KEY = "rsaSign";
    29. /**
    30. * 使用私钥生成签名字符串
    31. *
    32. * @param params 待签名参数集合
    33. * @param privateKey 私钥原始字符串
    34. *
    35. * @return 签名结果字符串
    36. *
    37. * @throws Exception
    38. */
    39. public static String sign(Map<String, Object> params, String privateKey) throws Exception {
    40. isTrue(!CollectionUtils.isEmpty(params), "params is required");
    41. notNull(privateKey, "privateKey is required");
    42. String signContent = signContent(params);
    43. Signature signature = Signature.getInstance(SIGN_ALGORITHMS);
    44. signature.initSign(getPrivateKeyPKCS8(privateKey));
    45. signature.update(signContent.getBytes(CHARSET));
    46. byte[] signed = signature.sign();
    47. return new String(Base64.getEncoder().encode(signed));
    48. }
    49. /**
    50. * 使用公钥校验签名
    51. *
    52. * @param params 入参数据,签名属性名固定为rsaSign
    53. * @param publicKey 公钥原始字符串
    54. *
    55. * @return true 验签通过 | false 验签不通过
    56. *
    57. * @throws Exception
    58. */
    59. public static boolean checkSign(Map<String, Object> params, String publicKey) throws Exception {
    60. isTrue(!CollectionUtils.isEmpty(params), "params is required");
    61. notNull(publicKey, "publicKey is required");
    62. // sign & content
    63. String content = signContent(params);
    64. String rsaSign = params.get(SIGN_KEY).toString();
    65. // verify
    66. Signature signature = Signature.getInstance(SIGN_ALGORITHMS);
    67. signature.initVerify(getPublicKeyX509(publicKey));
    68. signature.update(content.getBytes(CHARSET));
    69. return signature.verify(Base64.getDecoder().decode(rsaSign.getBytes(CHARSET)));
    70. }
    71. /**
    72. * 对输入参数进行key过滤排序和字符串拼接
    73. *
    74. * @param params 待签名参数集合
    75. *
    76. * @return 待签名内容
    77. *
    78. * @throws UnsupportedEncodingException
    79. */
    80. private static String signContent(Map<String, Object> params) throws UnsupportedEncodingException {
    81. Map<String, String> sortedParams = new TreeMap<>(Comparator.naturalOrder());
    82. for (Map.Entry<String, Object> entry : params.entrySet()) {
    83. String key = entry.getKey();
    84. if (legalKey(key)) {
    85. String value =
    86. entry.getValue() == null ? null : URLEncoder.encode(entry.getValue().toString(), CHARSET);
    87. sortedParams.put(key, value);
    88. }
    89. }
    90. StringBuilder builder = new StringBuilder();
    91. if (!CollectionUtils.isEmpty(sortedParams)) {
    92. for (Map.Entry<String, String> entry : sortedParams.entrySet()) {
    93. builder.append(entry.getKey());
    94. builder.append("=");
    95. builder.append(entry.getValue());
    96. builder.append("&");
    97. }
    98. builder.deleteCharAt(builder.length() - 1);
    99. }
    100. return builder.toString();
    101. }
    102. /**
    103. * 将公钥字符串进行Base64 decode之后,生成X509标准公钥
    104. *
    105. * @param publicKey 公钥原始字符串
    106. *
    107. * @return X509标准公钥
    108. *
    109. * @throws InvalidKeySpecException
    110. * @throws NoSuchAlgorithmException
    111. */
    112. private static PublicKey getPublicKeyX509(String publicKey) throws InvalidKeySpecException,
    113. NoSuchAlgorithmException, UnsupportedEncodingException {
    114. if (StringUtils.isEmpty(publicKey)) {
    115. return null;
    116. }
    117. KeyFactory keyFactory = KeyFactory.getInstance(SIGN_TYPE_RSA);
    118. byte[] decodedKey = Base64.getDecoder().decode(publicKey.getBytes(CHARSET));
    119. return keyFactory.generatePublic(new X509EncodedKeySpec(decodedKey));
    120. }
    121. /**
    122. * 将私钥字符串进行Base64 decode之后,生成PKCS #8标准的私钥
    123. *
    124. * @param privateKey 私钥原始字符串
    125. *
    126. * @return PKCS #8标准的私钥
    127. *
    128. * @throws Exception
    129. */
    130. private static PrivateKey getPrivateKeyPKCS8(String privateKey) throws Exception {
    131. if (StringUtils.isEmpty(privateKey)) {
    132. return null;
    133. }
    134. KeyFactory keyFactory = KeyFactory.getInstance(SIGN_TYPE_RSA);
    135. byte[] decodedKey = Base64.getDecoder().decode(privateKey.getBytes(CHARSET));
    136. return keyFactory.generatePrivate(new PKCS8EncodedKeySpec(decodedKey));
    137. }
    138. /**
    139. * 有效的待签名参数key值
    140. * 非空、且非签名字段
    141. *
    142. * @param key 待签名参数key值
    143. *
    144. * @return true | false
    145. */
    146. private static boolean legalKey(String key) {
    147. return StringUtils.hasText(key) && !SIGN_KEY.equalsIgnoreCase(key);
    148. }
    149. }

    验证

    加签逻辑验证

    开发者实现加签逻辑之后,使用计算示例中步骤 1 的初始请求参数作为输入,结合密钥生成中的示例私钥,进行 RSA 签名的生成,如果结果与步骤 3 中最终签名串一致,说明加签逻辑正确。

    验签逻辑验证

    使用计算示例中步骤 4 完整请求参数作为输入,结合密钥生成中的示例公钥,进行 RSA 签名的 check ,返回 true 则说明验签逻辑正确。

    赞(0) 打赏
    未经允许不得转载:九八云安全 » 九八云百度小程序教程:签名与验签

    评论 抢沙发