更新時間:2016/10/10 訪問次數:22405
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×tamp=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×tamp=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怎麼獲取?
3.Q:如何驗證簽名算法,有沒有工具?
A:有,在應用管理的奇門聯調配置工具裏(http://cloud.tmall.com/my/qimen/center/signatureBuilder.htm?spm=0.0.0.0.rEEw3E),奇門接入工具-簽名工具。