十年網(wǎng)站開發(fā)經(jīng)驗 + 多家企業(yè)客戶 + 靠譜的建站團(tuán)隊
量身定制 + 運營維護(hù)+專業(yè)推廣+無憂售后,網(wǎng)站問題一站解決
本章節(jié)主要介紹百度收銀臺使用的雙向RSA加密簽名規(guī)則與相關(guān)示例。

10年積累的網(wǎng)站建設(shè)、成都網(wǎng)站制作經(jīng)驗,可以快速應(yīng)對客戶對網(wǎng)站的新想法和需求。提供各種問題對應(yīng)的解決方案。讓選擇我們的客戶得到更好、更有力的網(wǎng)絡(luò)服務(wù)。我雖然不認(rèn)識你,你也不認(rèn)識我。但先建設(shè)網(wǎng)站后付款的網(wǎng)站建設(shè)流程,更有天涯免費網(wǎng)站建設(shè)讓你可以放心的選擇與我們合作。
1.統(tǒng)一字符集:UTF-8 ;
百度收銀臺接口中的所有參數(shù)字符集均保持為 UTF-8 ,與收銀臺交互接入或者計算簽名時,要統(tǒng)一使用 UTF-8 ,暫不支持其他字符集,請接入業(yè)務(wù)方自行轉(zhuǎn)換;
2.請求接口的參數(shù)列表均為字符串類型鍵值對;
3.鍵值中如果為復(fù)雜數(shù)據(jù)類型,比如結(jié)構(gòu)體、數(shù)組、對象都必須先轉(zhuǎn)化為 JSON 結(jié)構(gòu)字符串;
4.參數(shù)中包含漢字的部分,需要做 URLEncode 處理。
1.排除參數(shù)列表中名為 sign 和 sign_type 的參數(shù);
2.將剩余參數(shù)按參數(shù)名字典序正序排列;
3.將參數(shù)與其對應(yīng)的值使用 “=” 連接,組成參數(shù)字符串,將參數(shù)字符串按排序結(jié)果,使用 “&” 連接,組成待簽名字符串;
4.將待簽名字符串和業(yè)務(wù)方私鑰使用 SHA1WithRSA 簽名算法得出最終簽名。
使用密鑰生成中的示例公私鑰來做計算演示。
1.初始請求業(yè)務(wù)參數(shù)
| 參數(shù)名 | 示例取值 |
|---|---|
| appKey | MMMabc |
| dealId | 470193086 |
| tpOrderId | 3028903626 |
| totalAmount | 11300 |
2.生成待簽名字符串
appKey=MMMabc&dealId=470193086&totalAmount=11300&tpOrderId=3028903626
3.生成最終簽名串
TN0ZNPyQeTnPjCN5hUa7JwrXOhR8uDASXPazidVQHFSiGCH5aouBkVvJxtf8PeqzGYWAASwS2oOt2eJfunzC5dTFd/pWJeJToMgCSgRY7KtQUCCDnMrtpqiMAf+dLiXps3HpWhVB4CK6MXfHc64ejP5a2fu5bg8B0BTcHrqaGc0=
4.簽名的完整請求參數(shù)
| 參數(shù)名 | 示例取值 |
|---|---|
| appKey | MMMabc |
| dealId | 470193086 |
| tpOrderId | 3028903626 |
| totalAmount | 11300 |
| rsaSign | TN0ZNPyQeTnPjCN5hUa7JwrXOhR8uDASXPazidVQHFSiGCH5aouBkVvJxtf8PeqzGYWAASwS2oOt2eJfunzC5dTFd/pWJeJToMgCSgRY7KtQUCCDnMrtpqiMAf+dLiXps3HpWhVB4CK6MXfHc64ejP5a2fu5bg8B0BTcHrqaGc0= |
// 通用簽名工具,基于openssl擴(kuò)展,提供使用私鑰生成簽名和使用公鑰驗證簽名的接口class RSASign{/*** @desc 使用私鑰生成簽名字符串* @param array $assocArr 入?yún)?shù)組* @param string $rsaPriKeyStr 私鑰原始字符串,不含PEM格式前后綴* @return string 簽名結(jié)果字符串* @throws Exception*/public static function sign(array $assocArr, $rsaPriKeyStr){$sign = '';if (empty($rsaPriKeyStr) || empty($assocArr)) {return $sign;}if (!function_exists('openssl_pkey_get_private') || !function_exists('openssl_sign')) {throw new Exception("openssl擴(kuò)展不存在");}$rsaPriKeyPem = self::convertRSAKeyStr2Pem($rsaPriKeyStr, 1);$priKey = openssl_pkey_get_private($rsaPriKeyPem);if (isset($assocArr['sign'])) {unset($assocArr['sign']);}// 參數(shù)按字典順序排序ksort($assocArr);$parts = array();foreach ($assocArr as $k => $v) {$parts[] = $k . '=' . $v;}$str = implode('&', $parts);openssl_sign($str, $sign, $priKey);openssl_free_key($priKey);return base64_encode($sign);}/*** @desc 使用公鑰校驗簽名* @param array $assocArr 入?yún)?shù)據(jù),簽名屬性名固定為rsaSign* @param string $rsaPubKeyStr 公鑰原始字符串,不含PEM格式前后綴* @return bool true 驗簽通過|false 驗簽不通過* @throws Exception*/public static function checkSign(array $assocArr, $rsaPubKeyStr){if (!isset($assocArr['rsaSign']) || empty($assocArr) || empty($rsaPubKeyStr)) {return false;}if (!function_exists('openssl_pkey_get_public') || !function_exists('openssl_verify')) {throw new Exception("openssl擴(kuò)展不存在");}$sign = $assocArr['rsaSign'];unset($assocArr['rsaSign']);if (empty($assocArr)) {return false;}// 參數(shù)按字典順序排序ksort($assocArr);$parts = array();foreach ($assocArr as $k => $v) {$parts[] = $k . '=' . $v;}$str = implode('&', $parts);$sign = base64_decode($sign);$rsaPubKeyPem = self::convertRSAKeyStr2Pem($rsaPubKeyStr);$pubKey = openssl_pkey_get_public($rsaPubKeyPem);$result = (bool)openssl_verify($str, $sign, $pubKey);openssl_free_key($pubKey);return $result;}/*** @desc 將密鑰由字符串(不換行)轉(zhuǎn)為PEM格式* @param string $rsaKeyStr 原始密鑰字符串* @param int $keyType 0 公鑰|1 私鑰,默認(rèn)0* @return string PEM格式密鑰* @throws Exception*/public static function convertRSAKeyStr2Pem($rsaKeyStr, $keyType = 0){$pemWidth = 64;$rsaKeyPem = '';$begin = '-----BEGIN ';$end = '-----END ';$key = ' KEY-----';$type = $keyType ? 'PRIVATE' : 'PUBLIC';$keyPrefix = $begin . $type . $key;$keySuffix = $end . $type . $key;$rsaKeyPem .= $keyPrefix . "\n";$rsaKeyPem .= wordwrap($rsaKeyStr, $pemWidth, "\n", true) . "\n";$rsaKeyPem .= $keySuffix;if (!function_exists('openssl_pkey_get_public') || !function_exists('openssl_pkey_get_private')) {return false;}if ($keyType == 0 && false == openssl_pkey_get_public($rsaKeyPem)) {return false;}if ($keyType == 1 && false == openssl_pkey_get_private($rsaKeyPem)) {return false;}return $rsaKeyPem;}}
/** Copyright (C) 2020 Baidu, Inc. All Rights Reserved.*/package com.baidu.*;import static org.springframework.util.Assert.isTrue;import static org.springframework.util.Assert.notNull;import java.io.UnsupportedEncodingException;import java.net.URLEncoder;import java.security.KeyFactory;import java.security.NoSuchAlgorithmException;import java.security.PrivateKey;import java.security.PublicKey;import java.security.Signature;import java.security.spec.InvalidKeySpecException;import java.security.spec.PKCS8EncodedKeySpec;import java.security.spec.X509EncodedKeySpec;import java.util.Base64;import java.util.Comparator;import java.util.Map;import java.util.TreeMap;import org.springframework.util.CollectionUtils;import org.springframework.util.StringUtils;// 百度收銀臺雙向RSA簽名工具,JDK版本要求:1.8+public class RSASign {private static final String CHARSET = "UTF-8";private static final String SIGN_TYPE_RSA = "RSA";private static final String SIGN_ALGORITHMS = "SHA1WithRSA";private static final String SIGN_KEY = "rsaSign";/*** 使用私鑰生成簽名字符串** @param params 待簽名參數(shù)集合* @param privateKey 私鑰原始字符串** @return 簽名結(jié)果字符串** @throws Exception*/public static String sign(Mapparams, String privateKey) throws Exception { isTrue(!CollectionUtils.isEmpty(params), "params is required");notNull(privateKey, "privateKey is required");String signContent = signContent(params);Signature signature = Signature.getInstance(SIGN_ALGORITHMS);signature.initSign(getPrivateKeyPKCS8(privateKey));signature.update(signContent.getBytes(CHARSET));byte[] signed = signature.sign();return new String(Base64.getEncoder().encode(signed));}/*** 使用公鑰校驗簽名** @param params 入?yún)?shù)據(jù),簽名屬性名固定為rsaSign* @param publicKey 公鑰原始字符串** @return true 驗簽通過 | false 驗簽不通過** @throws Exception*/public static boolean checkSign(Mapparams, String publicKey) throws Exception { isTrue(!CollectionUtils.isEmpty(params), "params is required");notNull(publicKey, "publicKey is required");// sign & contentString content = signContent(params);String rsaSign = params.get(SIGN_KEY).toString();// verifySignature signature = Signature.getInstance(SIGN_ALGORITHMS);signature.initVerify(getPublicKeyX509(publicKey));signature.update(content.getBytes(CHARSET));return signature.verify(Base64.getDecoder().decode(rsaSign.getBytes(CHARSET)));}/*** 對輸入?yún)?shù)進(jìn)行key過濾排序和字符串拼接** @param params 待簽名參數(shù)集合** @return 待簽名內(nèi)容** @throws UnsupportedEncodingException*/private static String signContent(Mapparams) throws UnsupportedEncodingException { MapsortedParams = new TreeMap<>(Comparator.naturalOrder()); for (Map.Entryentry : params.entrySet()) { String key = entry.getKey();if (legalKey(key)) {String value =entry.getValue() == null ? null : URLEncoder.encode(entry.getValue().toString(), CHARSET);sortedParams.put(key, value);}}StringBuilder builder = new StringBuilder();if (!CollectionUtils.isEmpty(sortedParams)) {for (Map.Entryentry : sortedParams.entrySet()) { builder.append(entry.getKey());builder.append("=");builder.append(entry.getValue());builder.append("&");}builder.deleteCharAt(builder.length() - 1);}return builder.toString();}/*** 將公鑰字符串進(jìn)行Base64 decode之后,生成X509標(biāo)準(zhǔn)公鑰** @param publicKey 公鑰原始字符串** @return X509標(biāo)準(zhǔn)公鑰** @throws InvalidKeySpecException* @throws NoSuchAlgorithmException*/private static PublicKey getPublicKeyX509(String publicKey) throws InvalidKeySpecException,NoSuchAlgorithmException, UnsupportedEncodingException {if (StringUtils.isEmpty(publicKey)) {return null;}KeyFactory keyFactory = KeyFactory.getInstance(SIGN_TYPE_RSA);byte[] decodedKey = Base64.getDecoder().decode(publicKey.getBytes(CHARSET));return keyFactory.generatePublic(new X509EncodedKeySpec(decodedKey));}/*** 將私鑰字符串進(jìn)行Base64 decode之后,生成PKCS #8標(biāo)準(zhǔn)的私鑰** @param privateKey 私鑰原始字符串** @return PKCS #8標(biāo)準(zhǔn)的私鑰** @throws Exception*/private static PrivateKey getPrivateKeyPKCS8(String privateKey) throws Exception {if (StringUtils.isEmpty(privateKey)) {return null;}KeyFactory keyFactory = KeyFactory.getInstance(SIGN_TYPE_RSA);byte[] decodedKey = Base64.getDecoder().decode(privateKey.getBytes(CHARSET));return keyFactory.generatePrivate(new PKCS8EncodedKeySpec(decodedKey));}/*** 有效的待簽名參數(shù)key值* 非空、且非簽名字段** @param key 待簽名參數(shù)key值** @return true | false*/private static boolean legalKey(String key) {return StringUtils.hasText(key) && !SIGN_KEY.equalsIgnoreCase(key);}}
開發(fā)者實現(xiàn)加簽邏輯之后,使用計算示例中步驟 1 的初始請求參數(shù)作為輸入,結(jié)合密鑰生成中的示例私鑰,進(jìn)行 RSA 簽名的生成,如果結(jié)果與步驟 3 中最終簽名串一致,說明加簽邏輯正確。
使用計算示例中步驟 4 完整請求參數(shù)作為輸入,結(jié)合密鑰生成中的示例公鑰,進(jìn)行 RSA 簽名的 check ,返回 true 則說明驗簽邏輯正確。