签名算法
签名作用
问题
- 如何分辨出是否贵企业的请求?
- 如何分辨出请求消息的内容是否被篡改?
解决方法
通过数字签名就可以解决上述的问题。具体为:约定sign_key作为密钥,该sign_key仅贵企业和滴滴知道,在传输中不可见,用于参与签名计算。 企业在发送请求前,将消息内容与sign_key按照滴滴提供的签名算法计算出签名。滴滴在收到请求时,也按相同算法计算出签名。 如果为同一签名,则可信任来源为贵企业,并且内容是完整的。
- 如果非贵企业来源,由于攻击者没有正确的sign_key,无法算出正确的签名;
- 如果消息内容被篡改,由于滴滴会将接收的消息内容与sign_key重算一次签名,该值与参数的签名不一致,则会拒绝该请求。
签名算法步骤
- 生成签名的时候,将颁发的sign_key加入到传递的参数中,参与加密
- 传递的参数(包含sign_key)按照参数名升序排序,然后,以&形式连接(类似格式为a=xxx&b=xxx&c=xxx...),生成小写的md5串
- 生成sign后,sign和其他参数一起传递,对于中文在传递的过程中,需要进行urlencode,加密的时候不进行urlencode(如是以POST方法json格式传参不需要urlencode)
- sign_key不要参与参数传递,只参与生成sign
- 对于所有API的调用中用到的签名都可以用此工具测试。签名验证工具
示例
以授权认证接口为例,接口传参如下:
client_id = "client_id1"
client_secret = "client_secret1"
grant_type = "client_credentials"
phone = "11000001234"
timestamp = 1566477389
---------------------------------------------------------------------------------------------------------------------------
假设sign_key = "sign_key1",则授权认证接口参与签名的参数有:client_id、client_secret、grant_type、phone、timestamp、sign_key
---------------------------------------------------------------------------------------------------------------------------
首先,将所有参与签名的参数进行排序,排序后的参数顺序为:client_id、client_secret、grant_type、phone、sign_key、timestamp;
接着,将排序好的参数用&连接后的字符串str为:client_id=client_id1&client_secret=client_secret1&grant_type=client_credentials&phone=11000001234&sign_key=sign_key1×tamp=1566477389
最后,使用md5算法加密str得到sign,即md5("client_id=client_id1&client_secret=client_secret1&grant_type=client_credentials&phone=11000001234&sign_key=sign_key1×tamp=1566477389")
sign = "c52b8bac5e980da9ac557db412c20580"
算法实现
说明
会去除字符串首尾处的空白字符(或者其他字符),将去除这些字符 支持如下:
字符 | 名称 | 说明 |
\0 | NULL | |
\t | 制表符 | |
\n | 换行 | |
\r | 回车 | |
" " | 空格 | |
\x0B | 垂直制表符 | php版本支持,java版本不支持 |
例如 "name\r" 计算签名时会使用 "name"。
php版本
function genSign(array $params, $signKey)
{
$params['sign_key'] = $signKey;
ksort($params); //对数组(map)根据键名升序排序
$str = '';
foreach ($params as $k => $v)
{
if ('' == $str)
{
$str .= $k . '=' . trim($v);
}
else
{
$str .= '&' . $k . '=' . trim($v);
}
}
echo $str . "\n"; //待加密字符串
$sign = md5($str); //此处md5值为小写的32个字符
return $sign;
}
$params = array(
"client_id" => "client_id_test",
"access_token" => "access_token_test",
"timestamp" => 1562325354,
);
$signKey = "sign_key_test";
$sign = genSign($params, $signKey);
echo $sign;
java版本
import java.io.*;
import java.util.*;
import java.security.*;
import java.math.BigInteger;
class test
{
//java md5算法
public static String md5(String plainText)
{
byte[] secretBytes = null;
try {
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(plainText.getBytes("utf-8"));
secretBytes = md.digest();
}
catch (NoSuchAlgorithmException e) {
throw new RuntimeException("no such algorithm!");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
String md5code = new BigInteger(1, secretBytes).toString(16);
int length = md5code.length();
for (int i = 0; i < 32 - length; i++) {
md5code = "0" + md5code;
}
return md5code;
}
public static String genSign(HashMap<String, String> params, String signKey)
{
params.put("sign_key", signKey);
String result = "";
try {
List<Map.Entry<String, String>> infoIds = new ArrayList<Map.Entry<String, String>>(params.entrySet());
Collections.sort(infoIds, new Comparator<Map.Entry<String, String>>() {
public int compare(Map.Entry<String, String> o1, Map.Entry<String, String> o2) {
return (o1.getKey()).toString().compareTo(o2.getKey());
}
});
// 构造签名键值对的格式
for (Map.Entry<String, String> item : infoIds) {
if (item.getKey() != null || item.getKey() != "") {
String key = item.getKey();
String val = item.getValue().trim(); // 去除空白字符,空白字符参考如上说明
if (result == "") {
result += key + "=" + val;
} else {
result += "&" + key + "=" + val;
}
}
}
//System.out.println(result);
} catch(Exception e) {
throw new RuntimeException("error");
}
return md5(result);
}
public static void main (String[] args) throws java.lang.Exception
{
HashMap<String, String> map = new HashMap<String, String>();
map.put("client_id", "client_id_test");
map.put("access_token", "access_token_test");
map.put("timestamp", "1562325354");
String signKey = "sign_key_test";
String sign = genSign(map, signKey);
System.out.println(sign);
}
}
变量说明
变量名 | 类型 | 说明 |
sign_key | string | 申请应用时分配的密钥(保密存储,不要作为参数传递) |
params | array | 调用api时要传递的所有参数组成的数组(参数名为键,参数值为元素值) |