Utils:BASE64-AES-256-ECB加解密(PKCS7Padding)之微信退款通知解密

star2017 1年前 ⋅ 363 阅读

最近在做支付通道开发,需要对接微信和支付宝,银联,云闪付,各大银行等。

微信支付的开放接口对于开发者来说,相对支付宝并不那么友好,主要是有些小坑,开放文档描述的不够一致。

例如,同一个属性,返回的字段名与调起支付的字段名不一致,对开发者来说容易搞混。微信支付的 SDK 中定义为抽象的方法却使用 private 控制修饰符,导致外部不能重写。

微信退款通知

微信退款通知中的退款业务数据是一个加密信息字段 req_info,需要使用商户秘钥进行解密。

备注:官方文档没有说明的一个坑,加密信息字段req_info的 BASE64 字符串使用的是 ISO_8859_1编码,而不是通常默认的 UTF-8,否则会解密失败。

解密步骤如下:

  1. 对加密串 A 做 base64 解码,得到加密串 B。
  2. 对商户 key 做md5,得到 32 位 小写 key*
  3. 用 key* 对加密串 B 做 AES-256-ECB 解密(PKCS7Padding)。

加解密工具类

依赖

JDK 8 默认支持的是 PKCS5Padding,要支持 PKCS7Padding 需要引入对应的 JCE 包。

支付宝SDK 和 spring-cloud-start 包含了 bcprov-jdk15on 依赖。

<dependency>
    <groupId>org.bouncycastle</groupId>
    <artifactId>bcprov-jdk15on</artifactId>
    <version>1.62</version>
</dependency>

源码

package com.clearofchina.pay.test;

import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.springframework.util.DigestUtils;

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.Security;
import java.util.Base64;

/**
 * @desc: BASE64-AES-256-ECB加解密
 * @author: gxing
 * @date: 2020/6/19
 */
public class AESUtil {

    /**
     * 密钥算法
     */
    private static final String ALGORITHM = "AES";
    /**
     * 加解密算法/工作模式/填充方式
     */
    private static final String ALGORITHM_MODE_PADDING = "AES/ECB/PKCS7Padding";

    /**
     * @desc: 解密
     * @param: [data, key]
     * @author: gxing
     * @date: 2020/6/20
     */
    public static String decryptData(String data, String key) throws Exception {
        Base64.Decoder decoder = Base64.getDecoder();
        // key md5值小写
        String lowMD5 = DigestUtils.md5DigestAsHex(key.getBytes(Charset.defaultCharset())).toLowerCase();
        // 密钥
        SecretKeySpec secretKey = new SecretKeySpec(lowMD5.getBytes(), ALGORITHM);
        // 支付PKCS7Padding
        Security.addProvider(new BouncyCastleProvider());
        Cipher cipher = Cipher.getInstance(ALGORITHM_MODE_PADDING, "BC");
        cipher.init(Cipher.DECRYPT_MODE, secretKey);
        // BASE64解密(主意字符编码,微信的 req_info 使用的是ISO_8859_1, 不是UTF-8 )
        String info = new String(decoder.decode(data), StandardCharsets.ISO_8859_1);
        byte[] bytes = cipher.doFinal(info.getBytes(StandardCharsets.ISO_8859_1));
        return new String(bytes, Charset.defaultCharset());
    }

    /**
     * @desc: 加密
     * @param: [data, key]
     * @author: gxing
     * @date: 2020/6/20
     */
    public static String encryptData(String data, String key) throws Exception {
        // key md5值小写
        String lowMD5 = DigestUtils.md5DigestAsHex(key.getBytes(Charset.defaultCharset())).toLowerCase();
        SecretKeySpec secretKey = new SecretKeySpec(lowMD5.getBytes(), ALGORITHM);
        // 支付PKCS7Padding
        Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
        Cipher cipher = Cipher.getInstance(ALGORITHM_MODE_PADDING, "BC");
        cipher.init(Cipher.ENCRYPT_MODE, secretKey);
        Base64.Encoder encoder = Base64.getEncoder();
        byte[] bytes = cipher.doFinal(data.getBytes());
        // BASE64加密
        return encoder.encodeToString(bytes);
    }

    public static void main(String[] args) throws Exception {
        String apiKey = "4567qwer53321ty444rewq87656uyt";
        String data = "<root>\n" +
                "<out_refund_no><![CDATA[131811191610442717309]]></out_refund_no>\n" +
                "<out_trade_no><![CDATA[71106718111915575302817]]></out_trade_no>\n" +
                "<refund_account><![CDATA[REFUND_SOURCE_RECHARGE_FUNDS]]></refund_account>\n" +
                "<refund_fee><![CDATA[3960]]></refund_fee>\n" +
                "<refund_id><![CDATA[50000408942018111907145868882]]></refund_id>\n" +
                "<refund_recv_accout><![CDATA[支付用户零钱]]></refund_recv_accout>\n" +
                "<refund_request_source><![CDATA[API]]></refund_request_source>\n" +
                "<refund_status><![CDATA[SUCCESS]]></refund_status>\n" +
                "<settlement_refund_fee><![CDATA[3960]]></settlement_refund_fee>\n" +
                "<settlement_total_fee><![CDATA[3960]]></settlement_total_fee>\n" +
                "<success_time><![CDATA[2018-11-19 16:24:13]]></success_time>\n" +
                "<total_fee><![CDATA[3960]]></total_fee>\n" +
                "<transaction_id><![CDATA[4200000215201811190261405420]]></transaction_id>\n" +
                "</root>";
        String encode = encryptData(data, apiKey);
        String decode = decryptData(encode, apiKey);
        System.out.println(decode);
    }
}

相关参考

  1. 微信支付中退款踩坑记录
更多内容请访问:IT源点

全部评论: 0

    我有话说: