签名

基于安全考虑,我们要求所有的 IOTPay API 请求都必须经过签名

此签名应作为每个 API 请求的一部分包含在内。

签名算法

示例: var ex_body= { "c":"cat", "a":"apple", "b":"boat", "d":""};

步骤 1:

从复制的请求主体对象中删除所有值为空的键值对

ex_body= { "c":"cat", "a":"apple", "b":"boat"};

步骤 2:

按升序的词典序(a,b,c,...,z)对结果主体进行排序

ex_body= {"a":"apple" "b":"boat", "c":"cat"};

步骤 3:

将结果主体连接成新字符串,格式为 URL 参数 "key1=value1&key2=value2"

mystr="a=apple&b=boat&c=cat"

步骤 4:

将您的商户密钥(在 IOTPay 入门过程中分配给您)附加到 mystr 的末尾

mystr+="&key={your_merchant_key}"

步骤 5:

mystr 进行 MD5 哈希,然后将哈希结果转换为全大写字母。这就是您的 sign

sign = toUpper(md5(mystr));

步骤 6:

sign 的值附加到原始请求主体

ex_body.sign = sign; // 现在您可以发送签名请求

//发送请求前的 ex_body
{"a":"apple", "b":"boat", "c":"cat", "sign": "DAC619FA1BC9526EBDA688A9DC842B7A"};

推荐: 使用 签名验证器 来确认您的签名结果是否正确

步骤 7 (条件性):

仅当满足以下所有条件时,您可能需要执行一个额外的步骤:

  • 您已经完成了步骤 1-6,并仍然遇到签名验证错误

  • 请求中存在 subject 且不为空

  • subject 包含非 UTF8 编码的字符

    如果所有上述条件都适用,则应对 subject 的内容进行 URL 编码,并覆盖原始值;如下所示:

    ex_body.subject = url_encode(ex_body.subject);

步骤 8 (条件性):

body 字段重复执行步骤 7

步骤 9:

Info

如果端点是用于信用卡,则跳过此步骤,否则必须执行。

在验证了 sign 值后,使用 json_encode 将 json 对象串联起来,然后与 param = 连接。

params={"a":"apple", "b":"boat", "c":"cat", "sign": "DAC619FA1BC9526EBDA688A9DC842B7A"}

使用头部 Content-Type: application/x-www-form-urlencoded 发送请求。

演示代码


/** 对字符串进行签名
	*$prestr 要签名的字符串
	*返回签名结果
 */
function md5sign($prestr,$sign_type)
{
    $sign='';
    if($sign_type == 'MD5')
	{
        $sign = strtoupper(md5($prestr)); // 转为大写
    }
	else
	{
        die("only support MD5 for now".$sign_type);
    }
    return $sign;
}
/**
    * 将数组转换为 "键=值&键2=值2..." 格式的字符串
	*$array
	*返回字符串
*/
function create_linkstring($array)
{
    $arg = "";
    foreach ($array as $key => $val) {
        if ($val !== '') {
            $arg .= $key . "=" . $val . "&";
        }
    }
    $arg = substr($arg, 0, strlen($arg) - 1); // 移除最后的'&'
    return $arg;
}
function build_mysign($sort_array,$key,$sign_type = "MD5")
{
    $prestr = create_linkstring($sort_array);
    $prestr = $prestr."&key=".$key;

    $mysgin = md5sign($prestr,$sign_type);
    return $mysgin;
}

function arg_sort($array)
{
    ksort($array,SORT_NATURAL | SORT_FLAG_CASE);
    reset($array);
    return $array;
}

$arr = array(
    'mchId'=>$merchant_id,
    'mchOrderNo'=>$order_sn,
    'extra'=> $sceneInfo,
    'channelId'=>$channelId,
    'currency'=>'CAD',
    'amount'=>intval($order_amount*100),
    'clientIp'=>$ip,
    'device'=>'WEB',
    'notifyUrl'=>$notifyUrl,
    'subject'=>$subject,
    'body'=>$body,
  );

$sort_array = arg_sort($arr);
$arr['sign']= build_mysign($sort_array,$merchant_key,"MD5"); // 生成签名
$arr['subject'] = urlencode($arr['subject']); // 计算签名后进行 URL 编码
$arr['body'] = urlencode($arr['body']); // 计算签名后进行 URL 编码
$param = 'params='.json_encode($arr); // 信用卡与微信支付宝的主题不同,不需要 'params=',直接发送 JSON 字符串即可

$resBody = request($url,$param); // 提交请求

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;


import com.alibaba.fastjson.JSON;

public class TestSign {
	private static String encodingCharset = "UTF-8";
	public static void main(String[] args) {
		Map<String, Object> map = new HashMap<String, Object>();
		map.put("mchId", "10000XXX");
		map.put("mchOrderNo", "6199200000006");
		map.put("channelId", "WX_MICROPAY");
		map.put("currency", "CAD");
		map.put("codeType", "barcode");
		map.put("amount", "1");
		map.put("clientIp", "127.0.0.1");
		map.put("identityCode", "135021906891251756");
		map.put("device", "superscanner");
		map.put("deviceId", "superscanner_NS010");
		map.put("notifyUrl", "http://www.xxxx.ca/getnotify");
		map.put("subject", "test");
		map.put("body", "test body");
		String merchantKey = "xxxxxxxxxxxxxxxxxxxx";
		String sign = getSign(map, merchantKey);
		System.out.println("签名为 [" + sign + "]");
		// 签名为 [E7F9184356482199D44C9FC20704B798]
		map.put("sign", sign);
		try {
			String strSubjectEncode = URLEncoder.encode(map.get("subject")+"", StandardCharsets.UTF_8.toString());
			map.put("subject", strSubjectEncode);
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		}
		try {
			String strBodyEncode = URLEncoder.encode(map.get("body")+"", StandardCharsets.UTF_8.toString());
			map.put("body", strBodyEncode);
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		}

		String strParam = JSON.toJSONString(map);

		String strOutputParams = "params=" + strParam; // 信用卡与微信支付宝的主题不同,不需要 'params=',直接发送 JSON 字符串即可
		String url = "https://api.iotpaycloud.com/v1/create_order";
		String result = Http(url, strOutputParams);

	}
	public static String Http(String url, String param) {
		String result = "";
		// 请在这里添加你的 http 请求。
		return result;
	}
	public static String toHex(byte input[]) {
		if (input == null)
			return null;
		StringBuffer output = new StringBuffer(input.length * 2);
		for (int i = 0; i < input.length; i++) {
			int current = input[i] & 0xff;
			if (current < 16)
				output.append("0");
			output.append(Integer.toString(current, 16));
		}

		return output.toString();
	}
	public static String md5(String value, String charset) {
		MessageDigest md = null;
		try {
			byte[] data = value.getBytes(charset);
			md = MessageDigest.getInstance("MD5");
			byte[] digestData = md.digest(data);
			return toHex(digestData);
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
			return null;
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
			return null;
		}
	}
	public static String getSign(Map<String,Object> map, String key){
		ArrayList<String> list = new ArrayList<String>();
		for(Map.Entry<String,Object> entry:map.entrySet()){
			if(!"".equals(entry.getValue())){
				list.add(entry.getKey() + "=" + entry.getValue() + "&");
			}
		}
		int size = list.size();
		String [] arrayToSort = list.toArray(new String[size]);
		Arrays.sort(arrayToSort, String.CASE_INSENSITIVE_ORDER);
		StringBuilder sb = new StringBuilder();
		for(int i = 0; i < size; i ++) {
			sb.append(arrayToSort[i]);
		}
		String result = sb.toString();
		result += "key=" + key;
		result = md5(result, encodingCharset).toUpperCase();
		return result;
	}
}

using System;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Text;

namespace demo_sign
{
    class Program
    {
        // 从 IOTPay 获取商户密钥
        static string merchantKey = "xxxxxxxxxxxxxxxxxxx";

        public static string GetMD5Hash(string str)
        {
            StringBuilder sb = new StringBuilder();
            using (MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider())
            {
                byte[] data = md5.ComputeHash(Encoding.UTF8.GetBytes(str));
                int length = data.Length;
                for (int i = 0; i < length; i++)
                    sb.Append(data[i].ToString("X2"));
            }
            return sb.ToString();
        }

        static string create_linkstring(SortedDictionary<string, string> dict)
        {
            string arg = "";
            foreach (KeyValuePair<string, string> kv in dict)
            {
                if (kv.Value != "")
                {
                    arg += kv.Key + "=" + kv.Value + "&";
                }

            }
            arg = arg.TrimEnd('&');
            return arg;

        }

        static void Main(string[] args)
        {
            // 将所有参数添加到排序字典中,按字母顺序排序
            SortedDictionary<string, string> para_array = new SortedDictionary<string, string>();
            para_array.Add("mchId", "10000XXX");
            para_array.Add("mchOrderNo", "6199200000006");
            para_array.Add("channelId", "WX_MICROPAY");
            para_array.Add("currency", "CAD");
            para_array.Add("codeType", "barcode");
            para_array.Add("amount", "1");
            para_array.Add("clientIp", "127.0.0.1");
            para_array.Add("identityCode", "135021906891251756");
            para_array.Add("device", "superscanner");
            para_array.Add("deviceId", "superscanner_NS010");
            para_array.Add("notifyUrl", "http://www.xxxx.ca/getnotify");
            para_array.Add("subject", "test");
            para_array.Add("body", "test body");

            Console.WriteLine(para_array.Count);
            // 将字典转换为 URL 字符串
            string link_str = create_linkstring(para_array);
            // 在字符串末尾添加商户密钥
            link_str += ("&key="+merchantKey);
            Console.WriteLine(link_str);
            // 获取 md5 签名值
            string sign = GetMD5Hash(link_str);
            Console.WriteLine(sign);

        }
    }
}
Private Sub test()
    Dim disc As Object
    Set disc = CreateObject("Scripting.Dictionary")
    '从 IOTPay 获取商户密钥
    Dim merchantKey As String
    merchantKey = "xxxxxxxxxxxxxxxxxxxx"

    '将所有参数添加到排序字典中,按字母顺序排序
    disc.Add "mchId", "10000xxx"
    disc.Add "mchOrderNo", "6199200000006"
    disc.Add "channelId", "WX_MICROPAY"
    disc.Add "currency", "CAD"
    disc.Add "codeType", "barcode"
    disc.Add "amount", "1"
    disc.Add "clientIp", "127.0.0.1"
    disc.Add "identityCode", "135021906891251756"
    disc.Add "device", "superscanner"
    disc.Add "deviceId", "superscanner_NS010"
    disc.Add "notifyUrl", "http://www.xxxx.ca/getnotify"
    disc.Add "subject", "test"
    disc.Add "body", "test body"

    sign = GetSign(disc, merchantKey)

    Debug.Print sign


End Sub
Function GetSign(disc As Object, merchantKey As String) As String

    ' 对键进行排序
    Dim arrList As Object
    Set arrList = CreateObject("System.Collections.ArrayList")

    ' 将键放入 ArrayList
    Dim key As Variant, coll As New Collection
    For Each key In disc
        arrList.Add key
    Next key

    arrList.Sort

    ' 将字典转换为 URL 字符串
    Dim result As String
    Dim result_segment As String
    result_segment = ""
    For Each key In arrList
        result = result & result_segment & key & "=" & disc(key)
        result_segment = "&"
    Next key

    ' 在结果字符串末尾添加商户密钥
    result = result & result_segment & "key" & "=" & merchantKey

    ' 获取 md5 签名值并转为大写
    GetSign = UCase(StringToMD5Hex(result))
End Function


Function StringToMD5Hex(ByVal s As String) As String
    Dim enc As Object
    Dim bytes() As Byte
    Dim pos As Long
    Dim outstr As String

    Set enc = CreateObject("System.Security.Cryptography.MD5CryptoServiceProvider")

    bytes = StrConv(s, vbFromUnicode)
    bytes = enc.ComputeHash_2(bytes)

    For pos = 1 To UBound(bytes) + 1
        outstr = outstr & LCase(Right("0" & Hex(AscB(MidB(bytes, pos, 1))), 2))
    Next pos

    StringToMD5Hex = outstr
    Set enc = Nothing
End Function

// 参数1 arr: 请求体,对象
// 参数2: 商户密钥,字符串
// 返回: 附带签名的请求体
const getSignedBody = (arr, merchantKey) => {
  // 步骤 1 + 2: 删除空键;按升序字母顺序对请求体排序
  const sorted = Object.keys(arr)
    .sort()
    .reduce((accumulator, key) => {
      if (arr[key] != '') {
        accumulator[key] = arr[key]
        return accumulator
      } else {
        // 跳过空字段
        return accumulator
      }
    }, {})
  arr = sorted
  // 步骤 3: 将结果体连接成新的字符串,以 URL 参数格式
  var queryString = Object.keys(arr)
    .map((key) => key + '=' + arr[key])
    .join('&')

  // 步骤 4: 在 queryString 末尾添加商户密钥
  queryString += `&key=${merchantKey}`

  // 步骤 5: 对 queryString 进行 md5,将哈希字符串转为大写
  let sign = md5(queryString).toUpperCase()

  // 步骤 6: 将签名值附加到原始请求体
  arr.sign = sign

  // 步骤 7 + 8 (条件性的): 详细信息请参考指南
  // if ("body" in arr) {
  //   arr.body = encodeURIComponent(arr.body);
  // }
  // if ("subject" in arr) {
  //   arr.subject = encodeURIComponent(arr.subject);
  // }

  // 步骤 9: 信用卡与微信支付宝的主题不同,不需要 'params=',直接发送 JSON 字符串即可
  arr = 'params=' + JSON.stringify(arr)
  return arr
}

在线签名验证器

签名验证器在新窗口打开

上次更新: