签名算法

签名作用

问题
  1. 如何分辨出是否贵企业的请求?
  2. 如何分辨出请求消息的内容是否被篡改?
解决方法

通过数字签名就可以解决上述的问题。具体为:约定sign_key作为密钥,该sign_key仅贵企业和滴滴知道,在传输中不可见,用于参与签名计算。 企业在发送请求前,将消息内容与sign_key按照滴滴提供的签名算法计算出签名。滴滴在收到请求时,也按相同算法计算出签名。 如果为同一签名,则可信任来源为贵企业,并且内容是完整的。

  1. 如果非贵企业来源,由于攻击者没有正确的sign_key,无法算出正确的签名;
  2. 如果消息内容被篡改,由于滴滴会将接收的消息内容与sign_key重算一次签名,该值与参数的签名不一致,则会拒绝该请求。

签名算法步骤

  1. 生成签名的时候,将颁发的sign_key加入到传递的参数中,参与加密
  2. 传递的参数(包含sign_key)按照参数名升序排序,然后,以&形式连接(类似格式为a=xxx&b=xxx&c=xxx...),生成小写的md5串
  3. 生成sign后,sign和其他参数一起传递,对于中文在传递的过程中,需要进行urlencode,加密的时候不进行urlencode(如是以POST方法json格式传参不需要urlencode)
  4. sign_key不要参与参数传递,只参与生成sign
  5. 对于所有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&timestamp=1566477389
    最后,使用md5算法加密str得到sign,即md5("client_id=client_id1&client_secret=client_secret1&grant_type=client_credentials&phone=11000001234&sign_key=sign_key1&timestamp=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时要传递的所有参数组成的数组(参数名为键,参数值为元素值)

results matching ""

    No results matching ""