1. 签名的作用
Question
如何分辨出请求消息的内容是否被篡改?
解决方法
Answer
通过数字签名就可以解决上述的问题。具体为:约定sign_key作为密钥,该sign_key仅贵企业和滴滴知道,在传输中不可见,用于参与签名计算。 企业在发送请求前,将消息内容与sign_key按照滴滴提供的签名算法计算出签名。滴滴在收到请求时,也按相同算法计算出签名。 如果为同一签名,则可信任来源为贵企业,并且内容是完整的。
-
如果非贵企业来源,由于攻击者没有正确的sign_key,无法算出正确的签名;
-
如果消息内容被篡改,由于滴滴会将接收的消息内容与sign_key重算一次签名,该值与参数的签名不一致,则会拒绝该请求。

2. 签名算法步骤
- 生成签名的时候,将颁发的sign_key加入到传递的参数中,参与加密
- 传递的参数(包含sign_key)按照参数名升序排序,注:字符串前后的空格需要去掉
- 以&形式连接所有的请求参数(类似格式为a=xxx&b=xxx&c=xxx...),生成小写的32位md5串
3. 算法实现
Java版本
import java.io.*;
import java.util.*;
import java.security.*;
import java.math.BigInteger;
class test
{
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!");
}
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)
{
// 1、加入sign_key
params.put("sign_key", signKey);
// 2、hashmap已自动排序
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());
}
});
// 3、使用&连接参数
for (Map.Entry<String, String> item : infoIds) {
if (item.getKey() != null || item.getKey() != "") {
String key = item.getKey();
String val = item.getValue();
if (result == "") {
result += key + "=" + val;
} else {
result += "&" + key + "=" + val;
}
}
}
//System.out.println(result);
} catch(Exception e) {
throw new RuntimeException("error");
}
// 4、计算md5值
string sign = md5(result);
// 5、记录加密串和加密值,请求接口报签名错误时,请提供str和sign
log.Write("original_str=%s||md5_str=%s", str, sign);
return sign;
}
}
PHP版本
function genSign(array $params, $signKey)
{
// 1、添加秘钥到参数中
$params['sign_key'] = $signKey;
// 2、按照key进行排序
ksort($params);
// 3、使用&连接参数
$str = '';
foreach ($params as $k => $v) {
if ('' == $str) {
$str .= $k . '=' . trim($v);
} else {
$str .= '&' . $k . '=' . trim($v);
}
}
// 4、计算md5值
$sign = md5($str); //此处md5值为小写的32个字符
// 5、记录加密串和加密值,请求接口报签名错误时,请提供str和sign
log.Write("signTag", ["original_str" => $str, "md5_str" => $sign]);
return $sign;
}
Go版本
package test
import (
"crypto/md5"
"fmt"
"log"
"sort"
)
// GenSign 签名生成
func GenSign(params map[string]interface{}, signKey string) string {
// 1、添加秘钥到参数中
params["sign_key"] = signKey
var keySlice []string
for key, _ := range params {
keySlice = append(keySlice, key)
}
// 2、按照key进行排序
sort.Strings(keySlice)
// 3、使用&连接参数
var str string
for _, key := range keySlice {
if "" == str {
str += key + "=" + fmt.Sprintf("%v", params[key])
} else {
str = str + "&" + key + "=" + fmt.Sprintf("%v", params[key])
}
}
// 4、计算md5值
sign := MD5(str)
// 5、记录加密串和加密值,请求接口报签名错误时,请提供str和md5str
log.Printf("original_str=%s||md5_str=%s", str, sign)
return sign
}
func MD5(str string) string {
hash := md5.New()
hash.Write([]byte(str))
sum := hash.Sum(nil)
return fmt.Sprintf("%x", sum)
}
C++版本
#include <iostream>
#include<map>
using namespace std;
string genSign(map<string, string> params, string signKey) {
// 1、添加秘钥到参数中
params["sign_key"] = signKey;
// 2、map已自动排序
// 3、使用&连接参数
string str = "";
for(auto iter = params.begin(); iter != params.end(); iter++){
if (str == "") {
str += (iter->first + "=" + iter->second);
} else {
str += ("&" + iter->first + "=" + iter->second);
}
}
// 4、计算md5值,请自行实现md5算法
string sign = md5(str);
// 5、记录加密串和加密值,请求接口报签名错误时,请提供str和md5str
cout << "original_str=" + str + "||md5_str=" + sign << endl;
return sign;
}