最近更新时间:

1. 签名的作用

Question

如何分辨出请求消息的内容是否被篡改?

解决方法

Answer

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

  1. 如果非贵企业来源,由于攻击者没有正确的sign_key,无法算出正确的签名;

  2. 如果消息内容被篡改,由于滴滴会将接收的消息内容与sign_key重算一次签名,该值与参数的签名不一致,则会拒绝该请求。


2. 签名算法步骤

  1. 生成签名的时候,将颁发的sign_key加入到传递的参数中,参与加密
  2. 传递的参数(包含sign_key)按照参数名升序排序,注:字符串前后的空格需要去掉
  3. 以&形式连接所有的请求参数(类似格式为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;
}