更新时间: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.0
body
test
其中:
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),
奇门接入工具-签名工具。