兑换系统开放接口


签名生成

<h2>签名生成</h2> <p>商家可以按照下述步骤生成请求的签名。在本节的最后,我们准备了java语言的演示代码供开发者参考。</p> <p>平台要求商家对请求进行签名。平台会在收到请求后进行签名的验证。如果签名验证不通过,平台将会拒绝处理请求。</p> <h3>构造签名串</h3> <ol> <li> <p>将鉴权头部信息(排除<code>sign</code>属性,并忽略空值,如果signType不传值则不加入签名串中)以键值对的方式,按属性名字母升序后用<code>&amp;amp;</code>拼接:</p> <pre><code>businessId=754ea3e3a699404f9b82e8f881107f19&amp;amp;nonce=24bc2c17-0fef-440e-ba32-385c47c6cfa4&amp;amp;signType=SHA256-RSA&amp;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&amp;lt;String, String&amp;gt; headerParams, String dataJson) { String headerString = headerParams.keySet().stream() .sorted() .map(key -&amp;gt; key + &amp;quot;=&amp;quot; + headerParams.get(key)) .collect(Collectors.joining(&amp;quot;&amp;amp;&amp;quot;)); return headerString + dataJson; }</code></pre> <p>结果:</p> <pre><code>businessId=754ea3e3a699404f9b82e8f881107f19&amp;amp;nonce=24bc2c17-0fef-440e-ba32-385c47c6cfa4&amp;amp;signType=SHA256-RSA&amp;amp;timestamp=1628772138668{&amp;quot;businessId&amp;quot;:&amp;quot;754ea3e3a699404f9b82e8f881107f19&amp;quot;,&amp;quot;merchantId&amp;quot;:&amp;quot;eb1bb305ca3940f8adf1d4e964555699&amp;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(&amp;quot;SHA256withRSA&amp;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 &amp;lt;T&amp;gt; * @return 响应体 */ private &amp;lt;T&amp;gt; ResponseEntity&amp;lt;String&amp;gt; exchangeWithSign(String businessId, String privateKey, String api, T data) { // 头部参数 Map&amp;lt;String, String&amp;gt; headerParams = generatorHeaderParams(businessId); // 签名 String dataJson = JSON.toJSONString(data); String signString = getSignString(headerParams, dataJson); log.info(&amp;quot;待签名字符串:{}&amp;quot;, signString); // 使用商户私钥签名 try { String sign = signWithSHA256(signString, privateKey); headerParams.put(&amp;quot;sign&amp;quot;, sign); } catch (Exception e) { e.printStackTrace(); } UriComponents uriComponents = UriComponentsBuilder.fromHttpUrl(&amp;quot;http://127.0.0.1:8080/&amp;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(&amp;quot;application/json;charset=UTF-8&amp;quot;)); // 将认证信息加入请求头 headerParams.forEach(headers::add); RequestEntity&amp;lt;String&amp;gt; requestEntity = RequestEntity .post(uri) .headers(headers) .body(dataJson); return restTemplate.exchange(requestEntity, String.class); } /** * 构建鉴权头部数据 * @param businessId * @return */ private Map&amp;lt;String, String&amp;gt; generatorHeaderParams(String businessId) { Map&amp;lt;String, String&amp;gt; headerParams = new HashMap&amp;lt;&amp;gt;(); headerParams.put(&amp;quot;businessId&amp;quot;, businessId); headerParams.put(&amp;quot;nonce&amp;quot;, UUID.randomUUID().toString()); headerParams.put(&amp;quot;timestamp&amp;quot;, String.valueOf(System.currentTimeMillis())); headerParams.put(&amp;quot;signType&amp;quot;, &amp;quot;SHA256-RSA&amp;quot;); return headerParams; } /** * 构建待签名串 * @param headerParams 鉴权头数据 * @param dataJson 业务数据的JSON串 * @return 待签名串 */ private String getSignString(Map&amp;lt;String, String&amp;gt; headerParams, String dataJson) { String headerString = headerParams.keySet().stream() .sorted() .map(key -&amp;gt; key + &amp;quot;=&amp;quot; + headerParams.get(key)) .collect(Collectors.joining(&amp;quot;&amp;amp;&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(&amp;quot;SHA256withRSA&amp;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 = &amp;quot;RSA&amp;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>

页面列表

ITEM_HTML