title: 签名与验签 header: develop nav: function
sidebar: sign_v2
本文介绍如何在半个小时之内生成调用开放平台接口的签名,及签名加密规则,实现轻松调用百度电商开放平台的相关接口。
在签名和验签过程中,如开发者在任何一个环节没有严格按照文档要求操作均会导致此“签名错误”,请认真阅读文档。
特别推荐
推荐使用 百度电商开放平台的SDK来生成签名。
如果由于种种原因不愿意使用SDK,也可以参考里面的生成签名的代码,事半功倍,不用再次开发生成签名代码。
SDK的demo中有完成调用开放平台接口的完整示例。
签名计算基本设定
百度电商开放平台的所有接口中的所有参数的编码都是UTF-8,所以跟平台交互或者计算签名的时候字符编码都是UTF-8, 暂时没有支持其他编码的打算,请开发者自行对于自己的代码编码进行转换
您如果不清楚的自己请求参数的编码,PHP可以使用mb_detect_encoding函数进行检测编码, 因Java没有直接的函数可以考虑使用以下代码进行检测
String encode = "UTF-8";
System.out.println( str.equals( new String(str.getBytes(encode),encode) );
请求接口的参数列表均为键值对,键名为字符串,键值也必须为字符串。
键值中如果为复杂数据类型,比如结构体. 数组. 对象都必须先转化成为字符串。
建议键名中有复杂数据类型,PHP先转换成为数组array,Java先转换成为HashMap,因为平台现在对于参数的形式, 复杂只支持Json类型,PHP的array类型和Java的HashMap类型都可以很方便转换成Json
PHP中对于键名为数组情况,需要对数组中汉字进行urlencode函数进行编码,之后再json_encode。
筛选进行签名的参数
参数名为sign. sign_type参数不需要参与签名。
建议业务方也不需要在参数列表中进行签名参数之前,也不要有这样容易造成签名错误的参数
支付回调验签中全部参数签名是对平台的所有POST参数进行签名,空值参数同样要参与签名,如不参与签名,会导致验签不通过,如果商户URL里包含GET类型参数,不会参与签名。
对参数进行排序
将筛选的参数按照第一个字符的键值ASCII码递增排序(字母升序排序),如果遇到相同字符则按照第二个字符的键值ASCII码递增排序,以此类推。
PHP采用ksort函数进行排序。
Java先初始化ArrayList,然后使用sort方法进行排序。
对参数进行拼接组成待签名字符串
- 使用 = 和 & 将参数连接起来,将排序后的参数与其对应值,组合成“参数=参数值”的格式,并且把这些参数用&字符连接起来,此时生成的字符串为待签名字符串。
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={}
- 将待签名字符串和业务方私钥带入SHA1算法中得出sign
组成的待签名字符串为:
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
- 使用各自语言对应的SHA1WithRSA签名生成函数(如php: openssl_sign),传入待签名字符串. 业务方私钥,由SHA1算法中得出sign,然后base64encode。
示例生成的签名为:
rsaSign=Gzu1RT2toJSDthcLPG1ZWROI3jzvxFtO7yCPUqMT3L7cmnARncm5IIIQ6x+7S/02zWxr5FC9945WFSurO9kepVbU7YS6Lh9SEVQhvTO0YKG7TlLFTpH3Ik7JeHQalAKXYe/jNREDpHmTF9Jrq/wABeZGYXJn1M75A37h9zUt+kw=
完整的请求URL为:
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相关的签名生成的参考代码
- PHP生成签名的工具类
<?php
/**
* API入参静态检查类
* 可以对API的参数类型. 长度. 最大值等进行校验
*
**/
class NuomiRsaSign
{
/**
* @desc 私钥生成签名字符串
* @param array $assocArr
* @param $rsaPriKeyStr
* @return bool|string
* @throws Exception
*/
public static function genSignWithRsa(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扩展不存在");
}
$priKey = openssl_pkey_get_private($rsaPriKeyStr);
if (isset($assocArr['sign'])) {
unset($assocArr['sign']);
}
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
* @param $rsaPubKeyStr
* @return bool
* @throws Exception
*/
public static function checkSignWithRsa(array $assocArr, $rsaPubKeyStr)
{
if (!isset($assocArr['sign']) || empty($assocArr) || empty($rsaPubKeyStr)) {
return false;
}
if (!function_exists('openssl_pkey_get_public') || !function_exists('openssl_verify')) {
throw new Exception("openssl扩展不存在");
}
$sign = $assocArr['sign'];
unset($assocArr['sign']);
if (empty($assocArr)) {
return false;
}
ksort($assocArr); //按字母升序排序
$parts = array();
foreach ($assocArr as $k => $v) {
$parts[] = $k . '=' . $v;
}
$str = implode('&', $parts);
$sign = base64_decode($sign);
$pubKey = openssl_pkey_get_public($rsaPubKeyStr);
$result = (bool)openssl_verify($str, $sign, $pubKey);
openssl_free_key($pubKey);
return $result;
}
}
- 调用PHP签名生成类生成签名的参考代码
/**
* 第一部分:从申请的私钥文件路径中读取出私钥的内容
* @notice1: 私钥文件可以从任意存储介质中读取
*/
$rsaPriviateKeyFilePath = 'rsa/rsa_private_key.pem';
$rsaPrivateKey = file_get_contents($rsaPriviateKeyFilePath);
/**
* 第二部分:使用参数计算签名
*/
$requestApiParamsArr = array('key1'=>'value1','key2'=>'value2');
$rsaSign = NuomiRsaSign::genSignWithRsa( $requestApiParamsArr ,$rsaPrivateKey);
JAVA相关的签名生成的参考代码
- JAVA生成签名的工具类
com.nuomi.common.* 相关文件可以在开放平台的下载中找到
/**
* nuomi.com Inc.
* Copyright (c) 2004-2018 All Rights Reserved.
*/
package com.nuomi.util;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import com.nuomi.common.NuomiApiException;
import com.nuomi.common.NuomiConstants;
import com.nuomi.util.codec.Base64;
/**
* 签名工具类
* 目前只支持rsa方式 不支持rsa2
*/
public class NuomiSignature {
/**
* 获取签名
*
* @param sortedParams 排序后的参数
* @param privateKey 私钥
* @return 返回签名后的字符串
* @throws NuomiApiException
*/
public static String genSignWithRsa(Map<String, String> sortedParams, String privateKey) throws NuomiApiException {
String sortedParamsContent = getSignContent(sortedParams);
return rsaSign(sortedParamsContent, privateKey, NuomiConstants.CHARSET_UTF8);
}
/**
* 签名验证
*
* @param sortedParams
* @param pubKey
* @param sign
* @return
* @throws NuomiApiException
*/
public static boolean checkSignWithRsa(Map<String, String> sortedParams, String pubKey, String sign)
throws NuomiApiException {
String sortedParamsContent = getSignContent(sortedParams);
return doCheck(sortedParamsContent, sign, pubKey, NuomiConstants.CHARSET_UTF8);
}
/**
* @param sortedParams 已经排序的字符串
* @return 返回签名后的字符串
*/
public static String getSignContent(Map<String, String> sortedParams) {
StringBuffer content = new StringBuffer();
List<String> keys = new ArrayList<String>(sortedParams.keySet());
Collections.sort(keys);
int index = 0;
for (int i = 0; i < keys.size(); i++) {
String key = keys.get(i);
String value = sortedParams.get(key);
content.append((index == 0 ? "" : "&") + key + "=" + value);
index++;
}
return content.toString();
}
/**
* sha1WithRsa 加签
*
* @param content 需要加密的字符串
* @param privateKey 私钥
* @param charset 字符编码类型 如:utf8
* @return
* @throws NuomiApiException
*/
public static String rsaSign(String content, String privateKey,
String charset) throws NuomiApiException {
try {
PrivateKey priKey = getPrivateKeyFromPKCS8(NuomiConstants.SIGN_TYPE_RSA,
new ByteArrayInputStream(privateKey.getBytes()));
java.security.Signature signature = java.security.Signature
.getInstance(NuomiConstants.SIGN_ALGORITHMS);
signature.initSign(priKey);
if (StringUtils.isEmpty(charset)) {
signature.update(content.getBytes());
} else {
signature.update(content.getBytes(charset));
}
byte[] signed = signature.sign();
return new String(Base64.encodeBase64(signed));
} catch (InvalidKeySpecException ie) {
throw new NuomiApiException("RSA私钥格式不正确,请检查是否正确配置了PKCS8格式的私钥", ie);
} catch (Exception e) {
throw new NuomiApiException("RSAcontent = " + content + "; charset = " + charset, e);
}
}
public static PrivateKey getPrivateKeyFromPKCS8(String algorithm,
InputStream ins) throws Exception {
if (ins == null || StringUtils.isEmpty(algorithm)) {
return null;
}
KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
byte[] encodedKey = StreamUtil.readText(ins).getBytes();
encodedKey = Base64.decodeBase64(encodedKey);
return keyFactory.generatePrivate(new PKCS8EncodedKeySpec(encodedKey));
}
/**
* RSA验签名检查
*
* @param content 待签名数据
* @param sign 签名值
* @param publicKey 分配给开发商公钥
* @param encode 字符集编码
* @return 布尔值
* @throws NuomiApiException
*/
private static boolean doCheck(String content, String sign, String publicKey, String encode)
throws NuomiApiException {
try {
KeyFactory keyFactory = KeyFactory.getInstance(NuomiConstants.SIGN_TYPE_RSA);
byte[] bytes = publicKey.getBytes();
byte[] encodedKey = Base64.decodeBase64(bytes);
PublicKey pubKey = keyFactory.generatePublic(new X509EncodedKeySpec(encodedKey));
java.security.Signature signature = java.security.Signature.getInstance(NuomiConstants.SIGN_ALGORITHMS);
signature.initVerify(pubKey);
signature.update(content.getBytes(encode));
boolean bverify = signature.verify(Base64.decodeBase64(sign.getBytes()));
return bverify;
} catch (Exception e) {
throw new NuomiApiException("RSA私钥格式不正确,请检查是否正确配置了PKCS8格式的私钥", e);
}
}
}
- 调用JAVA签名生成类生成签名的参考代码
com.nuomi.common.* 相关文件可以在开放平台的下载中找到
/**
* 第一部分:赋值私钥
* @notice1:因为JAVA中的字节流. 字符流. 双字符流等概念非常复杂,建议使用常量存储私钥文件
* @notice2:私钥文件为不换行且不带私钥开头和结尾的字符串
*/
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=";
/**
* 第二部分:计算签名
*/
/**
* 1.通过方法获取到所有需要需要参与签名的参数HashMap
*/
HashMap apiParams = new HashMap();
/**
* 2.从常量中读取privateKey,然后计算RSA签名
*/
String rsaSign = NuomiSignature.genSignWithRsa(apiParams,NuomiConstants.PRIVATE_KEY);
将平台公钥转换成正确格式的公钥
将应用详情页的平台公钥和应用公钥(这2个公钥是没有开始. 结尾标示和换行符的)转换成正确的公钥格式
1.PHP代码
/**
* @desc 密钥由字符串(不换行)转为PEM格式
* @param $rsaKeyStr
* @param int $keyType 0:公钥,1:私钥
* @return string
* @throws SF_Exception_InternalException
*/
public static function convertRSAKeyStr2Pem($rsaKeyStr, $keyType = 0)
{
$rsaKeyPem = '';
$beginPublicKey = '-----BEGIN PUBLIC KEY-----';
$endPublicKey = '-----END PUBLIC KEY-----';
$beginPrivateKey = '-----BEGIN PRIVATE KEY-----';
$endPrivateKey = '-----END PRIVATE KEY-----';
$keyPrefix = $keyType ? $beginPrivateKey : $beginPublicKey;
$keySuffix = $keyType ? $endPrivateKey : $endPublicKey;
$rsaKeyPem .= $keyPrefix. "\n";
$rsaKeyPem .= wordwrap($rsaKeyStr, 64, "\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;
}