奇門算法

1 簽名算法

      簽名策略:假設 ERP向WMS發起正向接口調用,那麼ERP對URL+body信息進行MD5加密(加密算法下面有詳細介紹),奇門收到ERP的請求後,會用ERP的 appkey的secret進行sign校驗,驗證是否報文有被篡改。如果驗證通過,奇門轉發給WMS之前再做一次簽名,用WMS的appkey的 secret進行MD5加密,作爲sign參數的值,其他信息不變。這樣WMS在收到奇門傳過來的報文後,可以用自己WMS的secret進行簽名驗證, 此次調用的簽名驗證完成。
 
我們用ERP調用奇門URL演示加密算法:
http://qimenapi.tbsandbox.com/router/qimen/service?method=taobao.qimen.entryorde.rquery&timestamp=2015-04-26 00:00:07&format=xml&app_key=testerp_appkey&v=2.0&sign=abc&sign_method=md5&customerId=stub-cust-code
 
 
    1)、輸入參數爲
method= taobao.qimen.entryorder.query
       timestamp=2015-04-26 00:00:07
       format=xml
       app_key= testerp_appkey
       v=2.0
       sign_method=md5
       customerId =test
 
    2)、按首字母升序排列
app_key= testerp_appkey
customerId = stub-cust-code
format=xml
method= taobao.qimen.entryorder.query
sign_method=md5
       timestamp=2015-04-26 00:00:07
       v=2.0             
 
    3)、連接字符串
連接參數名與參數值,並在首尾加上secret,此處假設secret=test,如下:
       testapp_keytesterp_appkeycustomerIdstub-cust-codeformatxmlmethodtaobao.qimen.entryorder.querysign_methodmd5timestamp2015-04-26 00:00:07v2.0bodytest
其中:body用請求中的body內容代替
 
    4)、生成簽名 sign
32位大寫MD5-> D06D88CB34B2EC0E5C9BAB396C9542B6
 
    5)、拼裝URL請求
將所有參數值轉換爲UTF-8編碼,然後拼裝,通過瀏覽器訪問該地址,即成功調用一次接口,如下:
http://qimenapi.tbsandbox.com/router/qimen/service?method=taobao.qimen.entryorder.query&timestamp=2015-04-26 00:00:07&format=xml&app_key=testerp_appkey&v=2.0&sign= D06D88CB34B2EC0E5C9BAB396C9542B6 &sign_method=md5&customerId=stub-cust-code
 
 

2 算法JAVA示例

 
package md5;
 
 
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
 
/**
 * Created by jiqian.zzp on 2015/7/6.
 */
public class QimenSign {
    public static void main(String[] args) {
                        + "method=your_method&"
                        + "timestamp=2015-04-26%2000:00:07&"
                        + "format=xml&"
                        + "app_key=your_appkey&"
                        + "v=your_version&"
                        + "sign=your_sign&"
                        + "sign_method=md5&"
                        + "customerId=your_customerId";
        String body = "your_body";
        String secretKey = "your_secretKey";
 
        QimenSign sign = new QimenSign();
        String md5 = sign.sign(url, body, secretKey);
        System.out.println(md5);//6A4B6FCFAFE80280565406E110C27DC8
    }
 
    public String sign(String url, String body, String secretKey) {
 
        Map<String, String> params = getParamsFromUrl(url);
 
        // 1. 第一步,確保參數已經排序
        String[] keys = params.keySet().toArray(new String[0]);
        Arrays.sort(keys);
 
        // 2. 第二步,把所有參數名和參數值拼接在一起(包含body)
        String joinedParams = joinRequestParams(params, body, secretKey, keys);
 
        //your_secretKeyapp_keyyour_appkeycustomerIdyour_customerIdformatxmlmethodyour_methodsign_methodmd5timestamp2015-04-26 00:00:07vyour_versionyour_bodyyour_secretKey
        System.out.println(joinedParams);
 
        // 3. 第三步,使用加密算法進行加密(目前僅支持md5算法)
        String signMethod = params.get("sign_method");
        if (!"md5".equalsIgnoreCase(signMethod)) {
            //TODO
            return null;
        }
        byte[] abstractMesaage = digest(joinedParams);
 
        // 4. 把二進制轉換成大寫的十六進制
        String sign = byte2Hex(abstractMesaage);
 
        return sign;
 
    }
 
    private Map<String, String> getParamsFromUrl(String url) {
        Map<String, String> requestParams = new HashMap<String, String>();
        try {
            String fullUrl = URLDecoder.decode(url, "UTF-8");
            String[] urls = fullUrl.split("\\?");
            if (urls.length == 2) {
                String[] paramArray = urls[1].split("&");
                for (String param : paramArray) {
                    String[] params = param.split("=");
                    if (params.length == 2) {
                        requestParams.put(params[0], params[1]);
                    }
                }
            }
        } catch (UnsupportedEncodingException e) {
            // TODO
        }
        return requestParams;
    }
 
    private String byte2Hex(byte[] bytes) {
        char hexDigits[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
        int j = bytes.length;
        char str[] = new char[j * 2];
        int k = 0;
        for (byte byte0 : bytes) {
            str[k++] = hexDigits[byte0 >>> 4 & 0xf];
            str[k++] = hexDigits[byte0 & 0xf];
        }
        return new String(str);
    }
 
    private byte[] digest(String message)  {
        try {
            MessageDigest md5Instance = MessageDigest.getInstance("MD5");
            md5Instance.update(message.getBytes("UTF-8"));
            return md5Instance.digest();
        } catch (UnsupportedEncodingException e) {
            //TODO
            return null;
        } catch (NoSuchAlgorithmException e) {
            //TODO
            return null;
        }
    }
 
    private String joinRequestParams(Map<String, String> params, String body, String secretKey, String[] sortedKes) {
        StringBuilder sb = new StringBuilder(secretKey); // 前面加上secretKey
 
        for (String key : sortedKes) {
            if ("sign".equals(key)) {
                continue; // 簽名時不計算sign本身
            } else {
                String value = params.get(key);
                if (isNotEmpty(key) && isNotEmpty(value)) {
                    sb.append(key).append(value);
                }
            }
        }
        sb.append(body); // 拼接body
        sb.append(secretKey); // 最後加上secretKey
        return sb.toString();
    }
 
    private boolean isNotEmpty(String s) {
        return null != s && !"".equals(s);
    }
}
 
 

3.算法.net示例

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Diagnostics;
using System.Text;
/// <summary>
/// QimenSign 的摘要說明
/// </summary>
public class QimenSign
{
        public QimenSign()
        {
        
        }

    public String sign(String url, String body, String secretKey)
    {
        Dictionary<String, String> param = getParamsFromUrl(url);

        // 1. 第一步,確保參數已經排序
        string[] keys = new string[param.Keys.Count];
        param.Keys.CopyTo(keys,0);
        Array.Sort(keys);

        // 2. 第二步,把所有參數名和參數值拼接在一起(包含body體)
        String joinedParams = joinRequestParams(param, body, secretKey, keys);

        // 3. 第三步,使用加密算法進行加密(目前僅支持md5算法)
        String signMethod = param["sign_method"];
        if (!string.Equals("md5",signMethod)) {
            //TODO
            return null;
        }
        byte[] abstractMesaage = digest(joinedParams);

        // 4. 把二進制轉換成大寫的十六進制
        String sign = byte2Hex(abstractMesaage);

        return sign;
    }

    private Dictionary<string, string> getParamsFromUrl(String url) {
        Dictionary<string, string> requestParams = new Dictionary<string, string>();
        try {
            String fullUrl = System.Web.HttpUtility.UrlDecode(url, System.Text.Encoding.UTF8);
            String[] urls = fullUrl.Split('?');
            if (urls.Length == 2) {
                String[] paramArray = urls[1].Split('&');
                foreach (String param in paramArray) {
                    String[] values = param.Split('=');
                    if (values.Length == 2) {
                        requestParams.Add(values[0], values[1]);
                    }
                }
            }
        } catch (Exception e) {
            // TODO
            return null;
        }
        return requestParams;
    }
    private String byte2Hex(byte[] bytes) {
        char[] hexDigits = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
        int j = bytes.Length;
        char[] str = new char[j * 2];
        int k = 0;
        foreach (byte byte0 in bytes) {
            str[k++] = hexDigits[byte0 >> 4 & 0xf];
            str[k++] = hexDigits[byte0 & 0xf];
        }
        return new String(str);
    }

    private byte[] digest(String message)
    {
        try
        {
            //獲取加密服務  
            System.Security.Cryptography.MD5CryptoServiceProvider md5CSP = new System.Security.Cryptography.MD5CryptoServiceProvider();  
         
            //獲取要加密的字段,並轉化爲Byte[]數組  
            byte[] testEncrypt = System.Text.Encoding.UTF8.GetBytes(message);  
  
            //加密Byte[]數組  
            byte[] resultEncrypt = md5CSP.ComputeHash(testEncrypt);
            return resultEncrypt;
        }
        catch (Exception e)
        {
            //TODO
            return null;
        }
    }

    private String joinRequestParams(Dictionary<String, String> param, String body, String secretKey, String[] sortedKes) {
        StringBuilder sb = new StringBuilder(secretKey); // 前面加上secretKey

        foreach (String key in sortedKes) {
            if ("sign".Equals(key)) {
                continue; // 簽名時不計算sign本身
            } else {
                String value = param[key];
                if (isNotEmpty(key) && isNotEmpty(value)) {
                    sb.Append(key).Append(value);
                }
            }
        }
        sb.Append(body); // 拼接body體
        sb.Append(secretKey); // 最後加上secretKey
        return sb.ToString();
    }

    private bool isNotEmpty(String s) {
        return null != s && !"".Equals(s);
    }

}
 

4.簽名算法Python版本

以下算法基於python2.x


#! /usr/bin/python
# -*- coding: utf-8 -*-
import md5

    def getTotalUrl_with_body(method_args,xml_body):#method_args爲參數列表的dict
        request_args = {}
        url_secret ='sandbox642d8c408d84760fa0045ea79'  #修改配置
        url_str = '' + url_secret
        sign_list = []
        url_list =[]
        request_args = method_args
        for key,value in request_args.items():
            sign_list.append(key+value)
            url_list.append(key + '=' + value.encode('UTF-8'))
        sign_list.sort()
        for item in sign_list:
            url_str += item
        url_str = url_str + xml_body + url_secret
        sign_md5 = md5.new()
        sign_md5.update(url_str)
        sign = sign_md5.hexdigest().upper()  #簽名生成,若僅需要簽名,url生產部分可以忽略
        for item in url_list:
            url_head += '%s&' %(urllib.quote(item))
        final_url = url_head + 'sign=' + sign  #最終url生成
        return final_url
 
 
 

FAQ

1.Q:簽名時注意timestamp字段的處理。
   A:timestamp字段做簽名的時候,要用encode之前的,驗證簽名的時候,要用decode之後的。也就是,計算簽名的時候都使用timestamp的原始數據。如:簽名的時候用encode之前的timestamp2015-08-19 12:04:24,傳URL的時候要encode,用stamptime2015-04-26%2000:00:07,校驗簽名的時候還是用timestamp2015-08-19 12:04:24。
 
2.Q:什麼是沙箱appkey,以及沙箱的secret怎麼獲取?
   A:登陸開放平臺open.taobao.com,進入控制檯,找到對接奇門要使用的應用,應用設置裏有沙箱環境的參數。

 
 3.Q:如何驗證簽名算法,有沒有工具
   A:有,在應用管理的奇門聯調配置工具裏(http://cloud.tmall.com/my/qimen/center/signatureBuilder.htm?spm=0.0.0.0.rEEw3E),奇門接入工具-簽名工具。


 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章