签名生成
<h2>签名生成</h2>
<p>商家可以按照下述步骤生成请求的签名。在本节的最后,我们准备了java语言的演示代码供开发者参考。</p>
<p>平台要求商家对请求进行签名。平台会在收到请求后进行签名的验证。如果签名验证不通过,平台将会拒绝处理请求。</p>
<h3>构造签名串</h3>
<ol>
<li>
<p>将鉴权头部信息(排除<code>sign</code>属性,并忽略空值,如果signType不传值则不加入签名串中)以键值对的方式,按属性名字母升序后用<code>&amp;</code>拼接:</p>
<pre><code>businessId=754ea3e3a699404f9b82e8f881107f19&amp;nonce=24bc2c17-0fef-440e-ba32-385c47c6cfa4&amp;signType=SHA256-RSA&amp;timestamp=1628772138668</code></pre>
</li>
<li>
<p>将请求体转为JSON串(字段不分前后),拼接到步骤1后,生成待签名串:</p>
<pre><code class="language-java"> /**
* 构建待签名串
* @param headerParams 鉴权头数据(不含sign)
* @param dataJson 业务数据的JSON串
* @return 待签名串
*/
private String getSignString(Map&lt;String, String&gt; headerParams, String dataJson) {
String headerString = headerParams.keySet().stream()
.sorted()
.map(key -&gt; key + &quot;=&quot; + headerParams.get(key))
.collect(Collectors.joining(&quot;&amp;&quot;));
return headerString + dataJson;
}</code></pre>
<p>结果:</p>
<pre><code>businessId=754ea3e3a699404f9b82e8f881107f19&amp;nonce=24bc2c17-0fef-440e-ba32-385c47c6cfa4&amp;signType=SHA256-RSA&amp;timestamp=1628772138668{&quot;businessId&quot;:&quot;754ea3e3a699404f9b82e8f881107f19&quot;,&quot;merchantId&quot;:&quot;eb1bb305ca3940f8adf1d4e964555699&quot;}</code></pre>
</li>
</ol>
<h3>计算签名值</h3>
<p>使用<strong>商户私钥</strong>对<strong>待签名串</strong>进行<strong>SHA256 with RSA</strong>签名,并对签名结果进行<strong>Base64编码</strong>得到签名值</p>
<pre><code class="language-java"> /**
* 数字签名-SHA256withRSA
* @param content 待签名内容
* @param keyBase64 签名用的私钥字符串-Base64编码后的
* @return 签名串
* @throws Exception
*/
public static String signWithSHA256(String content, String keyBase64) throws Exception {
// 获得私钥
PrivateKey privateKey = getPrivateKey(keyBase64);
// 生成签名
Signature sign = Signature.getInstance(&quot;SHA256withRSA&quot;);
sign.initSign(privateKey);
sign.update(content.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(sign.sign());
}</code></pre>
<h3>设置HTTP头</h3>
<p>将签名和鉴权头部信息放入HTTP请求头中</p>
<h3>演示代码</h3>
<pre><code class="language-java"> @Autowired
RestTemplate restTemplate;
/**
* 签名并发送请求
* @param businessId 业务ID
* @param privateKey 私钥
* @param api API接口
* @param data 业务数据
* @param &lt;T&gt;
* @return 响应体
*/
private &lt;T&gt; ResponseEntity&lt;String&gt; exchangeWithSign(String businessId, String privateKey, String api, T data) {
// 头部参数
Map&lt;String, String&gt; headerParams = generatorHeaderParams(businessId);
// 签名
String dataJson = JSON.toJSONString(data);
String signString = getSignString(headerParams, dataJson);
log.info(&quot;待签名字符串:{}&quot;, signString);
// 使用商户私钥签名
try {
String sign = signWithSHA256(signString, privateKey);
headerParams.put(&quot;sign&quot;, sign);
} catch (Exception e) {
e.printStackTrace();
}
UriComponents uriComponents = UriComponentsBuilder.fromHttpUrl(&quot;http://127.0.0.1:8080/&quot;)
.path(api)
.build(true);
URI uri = uriComponents.toUri();
// 设置请求头信息
HttpHeaders headers = new HttpHeaders();
headers.add(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE);
headers.setContentType(MediaType.parseMediaType(&quot;application/json;charset=UTF-8&quot;));
// 将认证信息加入请求头
headerParams.forEach(headers::add);
RequestEntity&lt;String&gt; requestEntity = RequestEntity
.post(uri)
.headers(headers)
.body(dataJson);
return restTemplate.exchange(requestEntity, String.class);
}
/**
* 构建鉴权头部数据
* @param businessId
* @return
*/
private Map&lt;String, String&gt; generatorHeaderParams(String businessId) {
Map&lt;String, String&gt; headerParams = new HashMap&lt;&gt;();
headerParams.put(&quot;businessId&quot;, businessId);
headerParams.put(&quot;nonce&quot;, UUID.randomUUID().toString());
headerParams.put(&quot;timestamp&quot;, String.valueOf(System.currentTimeMillis()));
headerParams.put(&quot;signType&quot;, &quot;SHA256-RSA&quot;);
return headerParams;
}
/**
* 构建待签名串
* @param headerParams 鉴权头数据
* @param dataJson 业务数据的JSON串
* @return 待签名串
*/
private String getSignString(Map&lt;String, String&gt; headerParams, String dataJson) {
String headerString = headerParams.keySet().stream()
.sorted()
.map(key -&gt; key + &quot;=&quot; + headerParams.get(key))
.collect(Collectors.joining(&quot;&amp;&quot;));
return headerString + dataJson;
}
/**
* 数字签名-SHA256withRSA
* @param content 待签名内容
* @param keyBase64 签名用的私钥字符串-Base64编码后的
* @return 签名串
* @throws Exception
*/
public static String signWithSHA256(String content, String keyBase64) throws Exception {
// 获得私钥
PrivateKey privateKey = RSAUtils.getPrivateKey(keyBase64);
// 生成签名
Signature sign = Signature.getInstance(&quot;SHA256withRSA&quot;);
sign.initSign(privateKey);
sign.update(content.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(sign.sign());
}</code></pre>
<p>RSAUtils:</p>
<pre><code class="language-java">
import javax.crypto.Cipher;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
/** 算法名称 */
private static final String ALGORITHM = &quot;RSA&quot;;
/** 密钥长度 */
private static final int KEY_SIZE = 2048;
/**
* 随机生成密钥对(包含公钥和私钥)
*/
public static KeyPair generateKeyPair() throws Exception {
// 获取指定算法的密钥对生成器
KeyPairGenerator gen = KeyPairGenerator.getInstance(ALGORITHM);
// 初始化密钥对生成器(指定密钥长度, 使用默认的安全随机数源)
gen.initialize(KEY_SIZE);
// 随机生成一对密钥(包含公钥和私钥)
return gen.generateKeyPair();
}
/**
* 将公/私钥转为字符串方便保存
* @param key
* @return
*/
public static String getKeString(Key key) {
byte[] encoded = key.getEncoded();
return Base64.getEncoder().encodeToString(encoded);
}
/**
* 根据公钥的 Base64 文本创建公钥对象
*/
public static PublicKey getPublicKey(String pubKeyBase64) throws Exception {
// 把 公钥的Base64文本 转换为已编码的 公钥bytes
byte[] encPubKey = Base64.getDecoder().decode(pubKeyBase64);
// 创建 已编码的公钥规格
X509EncodedKeySpec encPubKeySpec = new X509EncodedKeySpec(encPubKey);
// 获取指定算法的密钥工厂, 根据 已编码的公钥规格, 生成公钥对象
return KeyFactory.getInstance(ALGORITHM).generatePublic(encPubKeySpec);
}
/**
* 根据私钥的 Base64 文本创建私钥对象
*/
public static PrivateKey getPrivateKey(String priKeyBase64) throws Exception {
// 把 私钥的Base64文本 转换为已编码的 私钥bytes
byte[] encPriKey = Base64.getDecoder().decode(priKeyBase64);
// 创建 已编码的私钥规格
PKCS8EncodedKeySpec encPriKeySpec = new PKCS8EncodedKeySpec(encPriKey);
// 获取指定算法的密钥工厂, 根据 已编码的私钥规格, 生成私钥对象
return KeyFactory.getInstance(ALGORITHM).generatePrivate(encPriKeySpec);
}
/**
* 公钥加密数据
*/
public static byte[] encrypt(byte[] plainData, PublicKey pubKey) throws Exception {
// 获取指定算法的密码器
Cipher cipher = Cipher.getInstance(ALGORITHM);
// 初始化密码器(公钥加密模型)
cipher.init(Cipher.ENCRYPT_MODE, pubKey);
// 加密数据, 返回加密后的密文
return cipher.doFinal(plainData);
}
/**
* 私钥解密数据
*/
public static byte[] decrypt(byte[] cipherData, PrivateKey priKey) throws Exception {
// 获取指定算法的密码器
Cipher cipher = Cipher.getInstance(ALGORITHM);
// 初始化密码器(私钥解密模型)
cipher.init(Cipher.DECRYPT_MODE, priKey);
// 解密数据, 返回解密后的明文
return cipher.doFinal(cipherData);
}</code></pre>