什么是数字签名?
简单来说,签名主要包含两个过程:摘要和非对称加密,首先对需要签名的数据做摘要(类似于常见的MD5)后得到摘要结果,然后通过签名者的私钥对摘要结果进行非对称加密即可得到签名结果。
由于计算能力的飞速发展,从安全性角度考虑,蚂蚁金服在原来SHA1WithRSA签名算法的基础上,新增了支持SHA256WithRSA的签名算法,该算法在摘要算法上比SHA1WithRSA有更强的安全能力,当然,SHA1WithRSA的签名算法会继续提供支持,但为了您的应用安全,强烈建议使用SHA256WithRSA的签名算法,同时,我们蚂蚁金服把这种签名算法命名为RSA2。
1.避免公私钥混用
不同签名算法的签名密钥是隔离的。由于同时提供了两套签名算法,若选择了特定的签名算法,请保证使用对应的私钥签名,同时使用对应的支付宝公钥进行验签。
2.协议参数
开放平台的报文标准中,sign_type参数用于指定签名算法,若使用SHA256WithRSA签名算法则在报文中需要指定sign_type=RSA2。若您使用开放平台SDK,在此做简要说明:
1)技术同学接入配置信息
把APPID、应用私钥、支付宝公钥配置在代码中,对请求内容进行签名,并对支付宝返回的内容进行验签,支付宝开放平台SDK封装了签名和验签过程,只需配置账号及密钥参数即可,强烈建议使用。
2)使用开放平台sdk接入示例
开放平台SDK封装了签名实现,只需在创建DefaultAlipayClient对象时,设置请求网关(gateway),应用id(app_id),应用私钥(private_key),编码格式(charset),支付宝公钥(alipay_public_key),签名类型(sign_type)即可,报文请求时会自动进行签名,示例如下:
1 package com.byttersoft.hibernate.erp.szmy.util;
2
3 import java.io.ByteArrayInputStream;
4 import java.io.IOException;
5 import java.io.InputStream;
6 import java.io.InputStreamReader;
7 import java.io.Reader;
8 import java.io.StringWriter;
9 import java.io.Writer;
10 import java.security.KeyFactory;
11 import java.security.PrivateKey;
12 import java.security.PublicKey;
13 import java.security.spec.PKCS8EncodedKeySpec;
14 import java.security.spec.X509EncodedKeySpec;
15 import java.util.ArrayList;
16 import java.util.Collections;
17 import java.util.List;
18 import java.util.Map;
19
20 import org.apache.commons.codec.binary.Base64;
21
22 import com.byttersoft.framework.util.StringUtil;
23 /**
24 * RSA的签名及验签
25 * @author zhouyy
26 *
27 */
28 public class RSA {
29
30 private static final String SIGN_TYPE_RSA = "RSA";
31
32 private static final String SIGN_TYPE_RSA2 = "RSA2";
33
34 private static final String SIGN_ALGORITHMS = "SHA1WithRSA";
35
36 private static final String SIGN_SHA256RSA_ALGORITHMS = "SHA256WithRSA";
37
38 private static final int DEFAULT_BUFFER_SIZE = 8192;
39
40 /**
41 * RSA/RSA2 生成签名
42 *
43 * @param map
44 * 包含 sign_type、privateKey、charset
45 * @return
46 * @throws Exception
47 */
48 public static String rsaSign(Map map) throws Exception {
49 PrivateKey priKey = null;
50 java.security.Signature signature = null;
51 String signType = map.get("sign_type").toString();
52 String privateKey = map.get("privateKey").toString();
53 String charset = map.get("charset").toString();
54 String content = getSignContent(map);
55 map.put("content", content);
56 System.out.println("请求参数生成的字符串为:" + content);
57 if (SIGN_TYPE_RSA.equals(signType)) {
58 priKey = getPrivateKeyFromPKCS8(SIGN_TYPE_RSA, new ByteArrayInputStream(privateKey.getBytes()));
59 signature = java.security.Signature.getInstance(SIGN_ALGORITHMS);
60 } else if (SIGN_TYPE_RSA2.equals(signType)) {
61 priKey = getPrivateKeyFromPKCS8(SIGN_TYPE_RSA, new ByteArrayInputStream(privateKey.getBytes()));
62 signature = java.security.Signature.getInstance(SIGN_SHA256RSA_ALGORITHMS);
63 } else {
64 throw new Exception("不是支持的签名类型 : : signType=" + signType);
65 }
66 signature.initSign(priKey);
67
68 if (StringUtil.isEmpty(charset)) {
69 signature.update(content.getBytes());
70 } else {
71 signature.update(content.getBytes(charset));
72 }
73
74 byte[] signed = signature.sign();
75
76 return new String(Base64.encodeBase64(signed));
77
78 }
79
80 /**
81 * 验签方法
82 *
83 * @param content
84 * 参数的合成字符串格式: key1=value1&key2=value2&key3=value3...
85 * @param sign
86 * @param publicKey
87 * @param charset
88 * @param signType
89 * @return
90 */
91 public static boolean rsaCheck(Map map, String sign) throws Exception {
92 java.security.Signature signature = null;
93 String signType = map.get("sign_type").toString();
94 String privateKey = map.get("privateKey").toString();
95 String charset = map.get("charset").toString();
96 String content = map.get("content").toString();
97 String publicKey = map.get("publicKey").toString();
98 System.out.println(">>验证的签名为:" + sign);
99 System.out.println(">>生成签名的参数为:" + content);
100 PublicKey pubKey = getPublicKeyFromX509("RSA", new ByteArrayInputStream(publicKey.getBytes()));
101 if (SIGN_TYPE_RSA.equals(signType)) {
102 signature = java.security.Signature.getInstance(SIGN_ALGORITHMS);
103 } else if (SIGN_TYPE_RSA2.equals(signType)) {
104 signature = java.security.Signature.getInstance(SIGN_SHA256RSA_ALGORITHMS);
105 } else {
106 throw new Exception("不是支持的签名类型 : signType=" + signType);
107 }
108 signature.initVerify(pubKey);
109
110 if (StringUtil.isEmpty(charset)) {
111 signature.update(content.getBytes());
112 } else {
113 signature.update(content.getBytes(charset));
114 }
115
116 return signature.verify(Base64.decodeBase64(sign.getBytes()));
117 }
118
119 public static PrivateKey getPrivateKeyFromPKCS8(String algorithm, InputStream ins) throws Exception {
120 if (ins == null || StringUtil.isEmpty(algorithm)) {
121 return null;
122 }
123
124 KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
125
126 byte[] encodedKey = readText(ins).getBytes();
127
128 encodedKey = Base64.decodeBase64(encodedKey);
129
130 return keyFactory.generatePrivate(new PKCS8EncodedKeySpec(encodedKey));
131 }
132
133 public static PublicKey getPublicKeyFromX509(String algorithm, InputStream ins) throws Exception {
134 KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
135
136 StringWriter writer = new StringWriter();
137 io(new InputStreamReader(ins), writer, -1);
138
139 byte[] encodedKey = writer.toString().getBytes();
140
141 encodedKey = Base64.decodeBase64(encodedKey);
142
143 return keyFactory.generatePublic(new X509EncodedKeySpec(encodedKey));
144 }
145
146 /**
147 * 把参数合成成字符串
148 *
149 * @param sortedParams
150 * @return
151 */
152 public static String getSignContent(Map<String, String> sortedParams) {
153 StringBuffer content = new StringBuffer();
154 // app_id,method,charset,sign_type,version,bill_type,timestamp,bill_date
155 String[] sign_param = sortedParams.get("sign_param").split(",");// 生成签名所需的参数
156 List<String> keys = new ArrayList<String>();
157 for (int i = 0; i < sign_param.length; i++) {
158 keys.add(sign_param[i]);
159 }
160 Collections.sort(keys);
161 int index = 0;
162 for (int i = 0; i < keys.size(); i++) {
163 String key = keys.get(i);
164 /*if ("biz_content".equals(key)) {
165 content.append(
166 (index == 0 ? "" : "&") + key + "={\"bill_date\":\"" + sortedParams.get("bill_date") + "\",")
167 .append("\"bill_type\":\"" + sortedParams.get("bill_type") + "\"}");
168 index++;
169 } else {*/
170 String value = sortedParams.get(key);
171 if (StringUtil.isNotEmpty(key) && StringUtil.isNotEmpty(value)) {
172 content.append((index == 0 ? "" : "&") + key + "=" + value);
173 index++;
174 }
175 // }
176 }
177 return content.toString();
178 }
179
180 private static String readText(InputStream ins) throws IOException {
181 Reader reader = new InputStreamReader(ins);
182 StringWriter writer = new StringWriter();
183
184 io(reader, writer, -1);
185 return writer.toString();
186 }
187
188 private static void io(Reader in, Writer out, int bufferSize) throws IOException {
189 if (bufferSize == -1) {
190 bufferSize = DEFAULT_BUFFER_SIZE >> 1;
191 }
192
193 char[] buffer = new char[bufferSize];
194 int amount;
195
196 while ((amount = in.read(buffer)) >= 0) {
197 out.write(buffer, 0, amount);
198 }
199 }
200
201 }