加签与验签
RSA算法是现今使用最广泛的公钥密码算法,高德云店采用RSA2算法对传输数据进行加签和验签。
1 简介
使用场景
高德调用商家或者商家调用高德时,对请求参数使用RSA2算法私钥进行加签,接收方在接收到数据后,使用配对的公钥进行验签,以防止数据在传输过程中被篡改,以及鉴别请求来源的真伪。
2 商家调用高德
商家调用高德时,必须对接口进行签名。否则调用高德接口将会出现签名异常。
签名计算规则如下:
1对参与签名计算的六个参数(app_id、biz_content、charset、method、utc_timestamp、version),按照首字符的键值ASCII码进行递增排序(字母升序排序),如果遇到相同字符则按照第二个字符的键值ASCII码递增排序,以此类推。
2将排序好的参数用&连接
3对拼接好的参数进行加签
例如请求参数如下:
1charset=UTF-8
2biz_content={"name":"lisi"}
3utc_timestamp=1563004103212
4app_id=23432423422
5version=1.0
6method=com.eeresc.dddd
则按照规则对请求参数进行排序后的待加签字符串如下:
1app_id=23432423422&biz_content={"name":"lisi"}&charset=UTF-8
2&method=com.eeresc.dddd&utc_timestamp=1563004103212&version=1.0
之后对待加签的字符串使用RSA2算法进行加签,加签结果即sign参数,则最终请求高德接口的参数如下所示:
1charset=UTF-8
2biz_content={"name":"lisi"}
3utc_timestamp=1563004103212
4app_id=23432423422
5version=1.0
6method=com.eeresc.dddd
7sign_type=RSA2
8sign=OLBN2GfF/4RkaXEV1VT5v6Ce/EGj8T+vxBqUCVwuX0fG6zo2YArz/RDAjJFdu1PtwP/ENgYVOgGvkWinH6bZWfvP9caeej5CGNTgfGHVtFTANXO10bgvAOg28lzP+qo3xC0tozEvc/uPj2WBcRtoERfPomI+rGHLLUdq2xLmZJpo6+QoQOE3k0MFusXwGa8xT8v+G7SIT0GCyOgF8fOg==
注:加签后商家请求高德新增加两个参数:sign(加签结果)、sign_type(默认为RSA2)
3 高德调用商家
高德调用商家时也会携带签名sign信息,商家可以根据实际情况决定是否对请求做验签。高德调用商家时的加签逻辑、参数和商家调用高德时的加签逻辑、参数均一致,所以这里不再赘述。
4 加签&验签密钥获取途径
4.1 商家调用高德场景
商家在调用高德时,需要使用商家私钥进行加签,高德接收到请求后使用对应的商家公钥进行验签。
商家私钥获取途径:
商家登录「高德云店平台」,进入「控制台」->「商家中心」->「商家信息」页面,点击对应行业接入准备配置进入「接入准备及配置」页面。
预览
进入「接入准备及配置」页面,点击密钥:一键生成 -> 生成密钥 -> 确定,最后点击生效配置进行信息保存,即可获得商家私钥。
预览
注意3点:
商家私钥高德不会保存,需要商家妥善保存处理,丢失无法找回,需要重新生成
一但接口上线,商家将由于保障线上安全原因无法重新生成商家公私钥
配置填写完成后请务必点击生效配置,否则后台不会更新商家公私钥
4.2 高德调用商家场景
高德在调用商家时使用高德私钥加签,商家在接到请求后按需决定是否使用高德公钥验签
高德公钥获取途径:
商家登录「高德云店平台」,进入「控制台」->「商家中心」->「API对接信息」页面,即可获取高德公钥
预览
5 代码示例
5.1 商家调用高德时,商家加签Java版本
1import com.alibaba.fastjson.JSON;
2import com.alibaba.fastjson.JSONObject;
3
4import java.io.*;
5import java.security.GeneralSecurityException;
6import java.security.KeyFactory;
7import java.security.PrivateKey;
8import java.security.Signature;
9import java.security.spec.PKCS8EncodedKeySpec;
10import java.util.*;
11
12/**
13 * 描述:商家调用高德加签示例,仅供参考
14 * 本示例中使用到jdk security包提供的RSA2签名算法能力
15 * 本示例中使用到fastJson提供json序列化能力,仅作为参考
16 *
17 * @authoer 高小德
18 */
19public class GenerateSignUtils {
20
21 public static void main(String[] args) throws Exception {
22 Map
23
24 // 构造业务参数
25 JSONObject bizContent = new JSONObject();
26 bizContent.put("source", "example_api");
27 bizContent.put("merchantId", "8888777711119999");
28 bizContent.put("shopInfos", new String[]{});
29 paramMap.put("biz_content", JSON.toJSONString(bizContent));
30
31 // 构造参与加签的公共参数
32 paramMap.put("app_id", "202210130194031937");
33 paramMap.put("utc_timestamp", String.valueOf(System.currentTimeMillis()));
34 paramMap.put("version", "1.0");
35 paramMap.put("charset", "UTF-8");
36 paramMap.put("method", "amap.brand.merchant.createShop");
37 paramMap.put("sign", generateSign(paramMap));
38 paramMap.put("sign_type", "RSA2");
39
40 //打印最终请求高德参数
41 System.out.println(paramMap);
42 }
43
44 /**
45 * 使用商家私钥生成签名
46 *
47 * @param paramMap
48 * @return
49 * @throws Exception
50 */
51 public static String generateSign(Map
52 //使用商家私钥进行加签,请在高德云店「接入准备及配置」页面生成并获取商家私钥
53 String merchantPrivateKey = "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCJujqRA/s7dESFWKOp/R458vJ8DW+LhfpqQtANAjdHDkWqaK6iUMGQ4Z7CeYZCI7wO5Dpj+ZDlS23woaRDTQ1KooynlTpeun3pTrSmfXtszxsPQ25c01sJ8lpbg0NJgyTQ9yCnMj0XJtIu/zTIIXpx6xBuRxSL6xiDigA2Nfjgz/YXmhrxe7+hZbgG7/lD2gnvG83RS5t8a7AT579Djgx3lE3FojJhASwmIsDIdaO89vZhfzIw6I/NHMvVNIy35NVZKrGsUBn3monAUuirS8lbcAErBkiWYGz5vtDBYCrD1PsohmqHxsyZS/P9Rj+zOXMQnqIRoj1IjvIyUGh8irfVAgMBAAECggEALZCxI5z/lzHmIX+r1edqAp3/307KooxWxqUIE+WxyA5OugwcCpNRFs+uKITwCB2ub/bN4pmEeHe2DbBMmf958EFK636Raxmj4nYpihNlrB/wweH7NzatneLSRghtUHrUyVWZNfeHAovTab0BB10cF2g/bVySzZ+f4pTbcRP2HNnzBbhqAMlwIjT5T0zkDZDFoql5ZdB0wzAtO1RiEa9irUz/eVGun3lvePl74QpAi2UNP03jpjRrQkApOuTH1wdW+e3wM2puSk6OeCdVnC22uIKSfhXha2oMBIoLaoyYbTVeGdzNJjvzcYpmj/I5It9gyzDY/JD7EnwWWqmCcy0BWQKBgQDYGE5qfyZqKufoVZc5dtilMSe4YmjkNlukibdq7RVApXeZ3upQi1oCept0cfzStikbxbbY3hJcZq/Jyg6LVsKWND8BcRddEIBX9LWFhe6+Ti+qU3X0L2ZNlPkDjZMLHRdBkSM5be+sKHBUKpVoWCzgmjXXpn32q4THn8dgHhSegwKBgQCjKSuHuMfZ5QoVomWINTpoRGYXeOxeBElT7DcdIJdNH4vkZpkvQF09VfCbWB+VeeFT9PustCZCPtT2lAn9jmNTsnPYdCXNirzc0tfPrpGxIHU5DpSD5gDb/gDoeDYHhSQHese6f7jVbMxSOR6KE9i+QoiSuEoJ+pW9NPRSZBaAxwKBgQC0mXvP3XxJdppivkWorP7a8H31x6lKMXPdy4sTPhlo8eFHI9pIfm43bCjH5QwbPkqU/2SVUuOdfeSkjM4i/dualQejONUB3mylFcsvUIP7YHTNsPr/nS2u4TZMFeIyg5mQHrCFqq+H/jQC46QClM7M15TwKiFUKnzKKWsDlaA96QKBgGkZ1BiRRuttRpm5Cn47C2yu4rSsCFZnnHSa7MWugMgFUi/Gh1aQt38TJPJsSawX3rYeUSBmy15Q6w4LPoQ+fG0lvsnnx5InlJEKoEn/wYm/xsMCSVjNiDAt5pfZF7SwZw0KYi4YqA+TDerJMIrxTeUBJsicPdU/vcUrn1aTcotxAoGBAJC0bD+4k1Kthw616e0A+3UZv9AfG5uDkPFUI+0yKb0/9v+hA/60syWYvyMk6xFk5oxZWq4c/mkVhCHII8F28UZ3NnFljqXC1UVjryCqkRHnnoejs+qLx2jx/axUpDS9sqGHJIT62WLx9QUtwX4hZf4yySANPkfoeSJzPM+XJRdz";
54 String signContent = getSignContent(paramMap);
55 return getSign(signContent, merchantPrivateKey);
56 }
57
58 /**
59 * 参数转换为待加签字符串
60 *
61 * @param paramMap 待生成加密sign的参数集合
62 */
63 private static String getSignContent(Map
64 StringBuilder content = new StringBuilder();
65 List
66 // 将参数集合排序
67 Collections.sort(keys);
68 for (int i = 0; i < keys.size(); i++) {
69 String key = keys.get(i);
70 //排除不需要参与签名的公共参数
71 if ("sign_type".equals(key) || "sign".equals(key) || "need_encrypt".equals(key)) {
72 continue;
73 }
74 String value = paramMap.get(key);
75 // 拼装所有非空参数
76 if (key != null && !"".equalsIgnoreCase(key) && value != null && !"".equalsIgnoreCase(value)) {
77 content.append(i == 0 ? "" : "&").append(key).append("=").append(value);
78 }
79 }
80 return content.toString();
81 }
82
83 /**
84 * 字符串加签
85 *
86 * @param signContent 待加密的参数字符串
87 * @param merchantPrivateKey 商家应用私钥
88 * @throws IOException
89 * @throws GeneralSecurityException
90 */
91 private static String getSign(String signContent, String merchantPrivateKey) throws IOException, GeneralSecurityException {
92 KeyFactory keyFactory = KeyFactory.getInstance("RSA");
93 byte[] encodedKey = readText(new ByteArrayInputStream(merchantPrivateKey.getBytes())).getBytes();
94 encodedKey = Base64.getDecoder().decode(encodedKey);
95 PrivateKey priKey = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(encodedKey));
96
97 Signature signature = Signature.getInstance("SHA256WithRSA");
98 signature.initSign(priKey);
99 signature.update(signContent.getBytes("UTF-8"));
100 byte[] signed = signature.sign();
101 return new String(Base64.getEncoder().encode(signed));
102 }
103
104 private static String readText(InputStream in) throws IOException {
105 Reader reader = new InputStreamReader(in);
106 StringWriter writer = new StringWriter();
107
108 int bufferSize = 4096;
109 char[] buffer = new char[bufferSize];
110 int amount;
111 while ((amount = reader.read(buffer)) >= 0) {
112 writer.write(buffer, 0, amount);
113 }
114 return writer.toString();
115 }
116}
117
5.2 高德调用商家时,商家验签Java版本
1import com.alibaba.fastjson.JSON;
2import com.alibaba.fastjson.JSONObject;
3
4import java.io.*;
5import java.security.*;
6import java.security.spec.X509EncodedKeySpec;
7import java.util.*;
8
9/**
10 * 描述:高德调用商家验签示例,仅供参考
11 * 本示例中使用到jdk security包提供的RSA2签名算法能力
12 * 本示例中使用到fastJson提供json序列化能力,仅作为参考
13 *
14 * @authoer 高小德
15 */
16public class CheckSignUtils {
17
18 /**
19 * 方法调用入口示例
20 *
21 * @param args
22 * @throws Exception
23 */
24 public static void main(String[] args) throws Exception {
25 //模拟高德调用商家参数
26 Map
27 JSONObject bizContent = new JSONObject();
28 bizContent.put("source", "example_api");
29 bizContent.put("merchantId", "8888777711119999");
30 bizContent.put("shopId", "123");
31 bizContent.put("cpid", "456");
32 bizContent.put("shopStatus", 1);
33 params.put("biz_content", JSON.toJSONString(bizContent));
34
35 params.put("app_id", "202210130194031937");
36 params.put("utc_timestamp", "1666936656706");
37 params.put("version", "1.0");
38 params.put("charset", "UTF-8");
39 params.put("sign_type", "RSA2");
40 params.put("method", "amap.brand.createShop.audit.callback");
41 params.put("sign", "W3p3dwa9udsLwoX79xlQ9aZu4nQltYth47poHhnzUEgVsv4cj8te04C2Awmc3+1a8Q/8M82J9YluyO1NrSQ2tB30qFOQiWwwgCDUpDD/SZ8gb0v168qGQfe3SdV1d2hnKQH1t+vqOiQfFs5c5+cFi7Kdr7L8OjHcIg7Oeg6aPk/EVhJqeXqpqaW/S6FBAHUp2xl5kxrXlK8rd//RDLYe+y5qkGzngcJisyH82d0E/83TJMHv86DX0frrpMjwLRc9RmzdSLXzaPU3gQaPq1DqMNLJRzqcOtPPVk929LW4TP0aDNsDP/wmWwuR1T7fQ3lwJ3kBfP8VD5Rx1ZJx35W4gA==");
42
43 //使用高德公钥进行验签
44 boolean checkSignResult = checkSign(params);
45
46 //打印验签结果
47 System.out.println(checkSignResult);
48 }
49
50 /**
51 * 使用高德公钥验证签名
52 *
53 * @param paramFromAmap
54 * @return
55 * @throws Exception
56 */
57 public static boolean checkSign(Map
58 //使用高德公钥进行验签,请在高德云店「API对接信息」页面直接获取高德公钥
59 String amapPublicKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAle9Evy4y/5EDQfz5hxJL00B+OMnFEl/HUqEqLpkh0da/keZBNuxpoX6MJiF3cSTj8AukoMCcj8ycSX4iqprahveT68Y2+j/D/3XCeJxbeISBDb9+8qT+ff/Y8xtLxXRJGkmNZPDodLlqcI5rtR78yT9a985gzKPOWesauPesxdgRwcxyPZqDfAuqVoRGFMgJhVIg/fMYDq3hXT75yO4tE6DTlvdZmb8iHoxZ6hXms6tOfQEdiiXpjhautnHJcAsdw55kSvms1zOsdv68tw3Y2ogqm8Wg0ukI6tBxaBtI65gyxwM4GLW5b4Z6DbGg9KJUdAGPcqPs+QzvUl0mqXm2CQIDAQAB";
60 String signContent = getSignContent(paramFromAmap);
61 return checkSign(signContent, paramFromAmap.get("sign"), amapPublicKey);
62 }
63
64 /**
65 * 参数转换为待验签字符串
66 *
67 * @param paramMap 待生成加密sign的参数集合
68 */
69 private static String getSignContent(Map
70 StringBuilder content = new StringBuilder();
71 List
72 // 将参数集合排序
73 Collections.sort(keys);
74 for (int i = 0; i < keys.size(); i++) {
75 String key = keys.get(i);
76 //排除不需要参与签名的公共参数
77 if ("sign_type".equals(key) || "sign".equals(key) || "need_encrypt".equals(key)) {
78 continue;
79 }
80 String value = paramMap.get(key);
81 // 拼装所有非空参数
82 if (key != null && !"".equalsIgnoreCase(key) && value != null && !"".equalsIgnoreCase(value)) {
83 content.append(i == 0 ? "" : "&").append(key).append("=").append(value);
84 }
85 }
86 return content.toString();
87 }
88
89 /**
90 * 对加密字符串进行验签
91 *
92 * @param content 待验签内容
93 * @param sign 待验证签名
94 * @param amapPublicKey 高德公钥
95 * @throws IOException
96 * @throws GeneralSecurityException
97 */
98 private static boolean checkSign(String content, String sign, String amapPublicKey) throws IOException, GeneralSecurityException {
99 KeyFactory keyFactory = KeyFactory.getInstance("RSA");
100 StringWriter writer = new StringWriter();
101 io(new InputStreamReader(new ByteArrayInputStream(amapPublicKey.getBytes())), writer);
102 byte[] encodedKey = writer.toString().getBytes();
103 encodedKey = org.apache.commons.codec.binary.Base64.decodeBase64(encodedKey);
104 PublicKey pubKey = keyFactory.generatePublic(new X509EncodedKeySpec(encodedKey));
105 Signature signature = Signature.getInstance("SHA256WithRSA");
106 signature.initVerify(pubKey);
107 signature.update(content.getBytes("UTF-8"));
108 return signature.verify(Base64.getDecoder().decode(sign.getBytes()));
109 }
110
111 public static void io(Reader in, Writer out) throws IOException {
112 int bufferSize = 4096;
113 char[] buffer = new char[bufferSize];
114 int amount;
115 while ((amount = in.read(buffer)) >= 0) {
116 out.write(buffer, 0, amount);
117 }
118 }
119}
120
121
注:其他语言版本的加签验签工具请联系高德业务方或开发人员获取
附1 签名算法明细
签名算法 标准签名算法 描述
RSA2 SHA256WithRSA 强制要求 RSA 密钥的长度至少为 2048。
_
请求参数中除了六个固定的参数之外,其他的参数均不参与签名生成,这六个参数分别为app_id、biz_content、charset、method、utc_timestamp、version。将参数按照第一个字符的键值ASCII码递增排序(字母升序排序),如果遇到相同字符则按照第二个字符的键值ASCII码递增排序,以此类推。
拼接排序后的参数与其对应值,组合成“参数=参数值”的格式,并且把这些参数用&字符连接起来,此时生成的字符串为待签名字符串。
把拼装好的待签名字符串采用utf-8编码,使用签名RSA2算法对编码后的字节流进行处理,再用Base64算法对处理后的字节流进行编码,生成的字符串即为签名结果

