最近更新时间:
注意

签名中,不支持特殊字符“\0”,“\t”,“\n”,“\x0B”,“\r”, “ “(空格)进行加密处理

加密方法选择MD5还是SHA256,需要根据应用管理-安全设置中的签名算法决定;默认为SHA256。如需使用MD5,请管理员进行调整。

签名作用

问题
  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串或者sha256串
  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算法或者sha256算法加密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"
    hash("sha256","client_id=client_id1&client_secret=client_secret1&grant_type=client_credentials&phone=11000001234&sign_key=sign_key1&timestamp=1566477389")
    sign = "6f296c236346659f6351d548e3ec4260ac9847b117a1cc9428fd709a5976a8c5"

算法实现

php版本
function genSign(array $params, $signKey, $signMethod) 
{
    $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";  //待加密字符串
    // 如果$signMethod == 1 : 采用sha256加密方式
    if ($signMethod == 1) {     
        return hash("sha256", $str);
    }
    //采用md5加密:此处md5值为小写的32个字符
    return md5($str); 
}

$params = array(
    "client_id"    => "client_id_test",
    "access_token" => "access_token_test",
    "timestamp"    => 1562325354,
);
$signMethod = 1;
$signKey = "sign_key_test";
$sign = genSign($params, $signKey, $signMethod);
echo $sign;变量说明

Java版本

import java.io.*;
import java.util.*;
import java.security.*;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;

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!");
        }
        
        
        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;
    }
    //java sha256算法
    public static String sha256Hex(String input) {
        try {
            // 创建一个MessageDigest实例,并指定使用SHA-256算法
            MessageDigest digest = MessageDigest.getInstance("SHA-256");
 
            // 将输入字符串转换为字节数组,并更新摘要
            byte[] hash = digest.digest(input.getBytes(StandardCharsets.UTF_8));
 
            // 将字节数组转换为十六进制字符串
            StringBuilder hexString = new StringBuilder();
            for (byte b : hash) {
                String hex = Integer.toHexString(0xff & b);
                if (hex.length() == 1) hexString.append('0');
                hexString.append(hex);
            }
 
            return hexString.toString();
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException(e); 
        }
    }
    
    public static String genSign(HashMap<String, String> params, String signKey, Integer signMethod)
    {
        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");
        }
        if(signMethod == 1) {
          return sha256Hex(result);
        }
        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";
        Integer signMethod = 1;
        String sign = genSign(map, signKey, signMethod);
        System.out.println(sign);
    }
}




变量名 类型 说明
sign_key string 申请应用时分配的密钥(保密存储,不要作为参数传递)
params array 调用api时要传递的所有参数组成的数组(参数名为键,参数值为元素值)


签名示例:

签名失败:
{
    "errno": 19999,
    "errmsg": "签名失败",
    "data": {
        "params_sign_str": "client_id=dc60c9de0a96c20fccf2ccaa40_test&client_secret=d23c6d90dff1993982dc151601&company_id=112591566167936&grant_type=client_credentials&phone=13100011245&sign_key=**&timestamp=1709630326"
    },
    "request_id": "b7tL/EnaLhrcf2ekGRDn8fNhX+VqpYY4qg6jaxlmO7vAKpziv4aLLv7hhwwn6Rl+"
}

params_sign_str 为签名前的字符串,需要客户自行进行对比



签名示例
请求参数:
{
    "client_id": "dc60c9de0a96c20fccf2ccaa40_test",
    "client_secret": "d23c6d90dff1993982dc151601",
    "company_id": "112591566167936",
    "grant_type": "client_credentials",
    "phone": "13100011245",
    "timestamp": 1709630572
}
生成签名前的字符串:
signstr client_id=dc60c9de0a96c20fccf2ccaa40_test&client_secret=d23c6d90dff1993982dc151601&company_id=112591566167936&grant_type=client_credentials&phone=13100011245&sign_key=f86b6735313052FD7d0f&timestamp=1709630572


请求参数
{
    "access_token": "6f185cb4a60770ca8e615abd1084e7a9d605f27a",
    "client_id": "dc60c9de0a96c20fccf2ccaa40_test",
    "company_id": "112591566167936",
    "data": "{\"member_type\":1,\"realname\":\"中文+ Con\", \"phone\":\"11100009915\", \"project_ids\":\"\"}",
    "sign": "966e90d7ee6527ba8d1cdc0f96c2d844",
    "timestamp": 1709630852
}

生成签名前的字符串:
access_token=6f185cb4a60770ca8e615abd1084e7a9d605f27a&client_id=dc60c9de0a96c20fccf2ccaa40_test&company_id=112591566167936&data={"member_type":1,"realname":"中文+ Con", "phone":"11100009915", "project_ids":""}&sign_key=f86b6735313052FD7d0f&timestamp=1709630852