微信支付分创建支付分订单+签名+验签

签名工具类:

package app.action.signUtil;

import app.action.wx.Sign;
import com.alibaba.druid.util.StringUtils;
import okhttp3.HttpUrl;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.Signature;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Random;

/**
 * 微信支付分签名工具类
 */
public class SignUtil {

    private static final Log LOGGER = LogFactory.getLog(Sign.class);

    //method(请求类型GET、POST url(请求url) body(请求body,GET请求时body传"",POST请求时body为请求参数的json串)  merchantId(商户号) certSerialNo(API证书序列号) keyPath(API证书路径)
    public static String getToken(String method, String url, String body, String merchantId, String certSerialNo, String keyPath) throws Exception {
        String signStr = "";
        HttpUrl httpurl = HttpUrl.parse(url);
        String nonceStr = create_nonce_str();
        long timestamp = System.currentTimeMillis() / 1000;
        if (StringUtils.isEmpty(body)) {
            body = "";
        }
        String message = buildMessage(method, httpurl, timestamp, nonceStr, body);
        String signature = sign(message.getBytes("utf-8"), keyPath);
        signStr = "mchid=\"" + merchantId
                + "\",nonce_str=\"" + nonceStr
                + "\",timestamp=\"" + timestamp
                + "\",serial_no=\"" + certSerialNo
                + "\",signature=\"" + signature + "\"";
        LOGGER.info("Authorization Token:" + signStr);
        return signStr;
    }


    public static String buildMessage(String method, HttpUrl url, long timestamp, String nonceStr, String body) {
        String canonicalUrl = url.encodedPath();
        if (url.encodedQuery() != null) {
            canonicalUrl += "?" + url.encodedQuery();
        }
        return method + "\n"
                + canonicalUrl + "\n"
                + timestamp + "\n"
                + nonceStr + "\n"
                + body + "\n";
    }

    public static String sign(byte[] message, String keyPath) throws Exception {
        Signature sign = Signature.getInstance("SHA256withRSA");
        sign.initSign(getPrivateKey(keyPath));
        sign.update(message);
        return Base64.encodeBase64String(sign.sign());
    }

    /**
     * 获取私钥。
     *
     * @param filename 私钥文件路径  (required)
     * @return 私钥对象
     */
    public static PrivateKey getPrivateKey(String filename) throws IOException {

        String content = new String(Files.readAllBytes(Paths.get(filename)), "utf-8");
        LOGGER.info("File content:" + content);
        try {
            String privateKey = content.replace("-----BEGIN PRIVATE KEY-----", "")
                    .replace("-----END PRIVATE KEY-----", "")
                    .replaceAll("\\s+", "");
            LOGGER.info("privateKey:" + privateKey);
            KeyFactory kf = KeyFactory.getInstance("RSA");
            return kf.generatePrivate(
                    new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKey)));
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("当前Java环境不支持RSA", e);
        } catch (InvalidKeySpecException e) {
            LOGGER.info("异常:" + e);
            throw new RuntimeException("无效的密钥格式");
        }
    }

    /**
     * 获取32位随机数
     * @return
     */
    public static String create_nonce_str() {
        StringBuilder sb = new StringBuilder();
        Random rand = new Random();
        for (int i = 0; i < 32; i++) {
            sb.append(STR_ARR[rand.nextInt(STR_ARR.length)]);
        }
        return sb.toString();
    }

    private static String[] STR_ARR = new String[] { "a", "b", "c", "d", "e",
            "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r",
            "s", "t", "u", "v", "w", "x", "y", "z", "A", "B", "C", "D", "E",
            "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R",
            "S", "T", "U", "V", "W", "X", "Y", "Z", "1", "2", "3", "4", "5",
            "6", "7", "8", "9", "0" };
}

  创建支付分订单:

package app.action.signUtil;

import cn.hutool.http.Header;
import cn.hutool.http.HttpRequest;

/**
 * 签名并创建支付分订单
 */
public class CreateOrderInfoAction {
    public static void main(String[] args) throws Exception {
        // 微信支付分验签+创建支付分订单
        // 版本号:V1.3(版本时间---版本更新时间:2020.03.05)

        String reqdata = "{  \"out_order_no\": \"456123JKHDFE125476962\"," + // 随机数
                "  \"appid\": \"wx74654qweqwe46545\"," +          // 公众账号ID,(这个一定要确保正确,不然能坑死)
                "  \"service_id\": \"0000000000000000251514515151512\"," +
                "  \"service_introduction\": \"小米手机\"," +
                "  \"time_range\": {" +
                "    \"start_time\": \"OnAccept\"" +
                "  }," +
                "	\"post_payments\": [" +
                "	{" +
                "		\"name\": \"支付分换手机\"," +
                "		\"amount\":10," +
                "		\"description\": \"小米手机\"," +
                "		\"count\": 1" +
                "	}" +
                "]," +
                "  \"risk_fund\": {" +
                "    \"name\": \"ESTIMATE_ORDER_COST\"," +
                "    \"amount\": 10" +
                "  }," +
                "  \"notify_url\": \"https://test.com\"," +     // 设置的微信处理完回调你的地址
                "  \"openid\": \"aqweqweqweqweqweqweqweUY\"," + // 用户标识,每个用户是唯一的
                "  \"need_user_confirm\": false}"; // 我这商户是免确认模式,顾设置为false

        // 创建支付分订单API的请求URL:https://api.mch.weixin.qq.com/v3/payscore/serviceorder
        String URL = "https://api.mch.weixin.qq.com/v3/payscore/serviceorder";

        // 商户号(具体以自己的为准)
        String WX_MCHID = "123456789";

        // 商户API证书序列号serial_no(具体以自己的为准)
        String WX_SERIAL_NO = "16ASDASDASDASD561564651321ASDASDASD";

        // 签名私钥路径(最好不要有中文)
        String keyPath = "F:\\wxpert\\apiclient_key.pem";

        // 下面的post方法是Hutool工具类里的一个方法
		/*
			引入以下jar包
			<dependency>
				<groupId>cn.hutool</groupId>
				<artifactId>hutool-all</artifactId>
				<version>5.2.3</version>
			</dependency>
		*/
        String data = HttpRequest.post(URL)
                .header(Header.CONTENT_TYPE, "application/json")
                .header(Header.ACCEPT, "application/json")
                .header("Authorization", "WECHATPAY2-SHA256-RSA2048" + " "
                        + SignUtil.getToken("POST", URL, reqdata, WX_MCHID, WX_SERIAL_NO, keyPath))//头信息,多个头信息多次调用此方法即可
                .body(reqdata)
                .execute().body();
        System.out.println("data = " + data);
    }
}

  微信支付分验签公钥获取:

package app.action.signUtil;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.HttpEntity;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;

import static app.action.wx.Sign.getToken;

/**
 * 获取公钥的方式,并把获取的公钥保存在F:\key
 * F:\key(自己设置的地址)
 */
public class PublicKeyUtil {
    private static final Log LOGGER = LogFactory.getLog(PublicKeyUtil.class);

    public static void main(String[] args) throws Exception {
        getCertByAPI("123456789","https://api.mch.weixin.qq.com/v3/certificates",2,null,"16ASDASDASDASD561564651321ASDASDASD","F:\\wxpert\\apiclient_key.pem");
    }
    public static List<X509Certificate> getCertByAPI(String merchantId, String url, int timeout, String body, String certSerialNo, String keyPath) throws UnsupportedEncodingException, Exception{
        String result = "";
        //创建http请求
        HttpGet httpGet = new HttpGet(url);
        httpGet.addHeader("Content-Type", "application/json");
        httpGet.addHeader("Accept", "application/json");

        //设置认证信息
        httpGet.setHeader("Authorization", "WECHATPAY2-SHA256-RSA2048"+" "+getToken("GET",url,null,merchantId,certSerialNo,keyPath));

        //设置请求器配置:如超时限制等
        RequestConfig config = RequestConfig.custom().setSocketTimeout(timeout * 1000).setConnectTimeout(timeout * 1000).build();
        httpGet.setConfig(config);
        List<X509Certificate> x509Certs = new ArrayList<X509Certificate>();
        try {
            CloseableHttpClient httpClient = HttpClients.createDefault();
            CloseableHttpResponse response = httpClient.execute(httpGet);
            int statusCode = response.getStatusLine().getStatusCode();
            HttpEntity httpEntity = response.getEntity();
            result = EntityUtils.toString(httpEntity, "UTF-8");
            if(statusCode == 200){
                LOGGER.info("下载平台证书返回结果:"+result);
                List<CertificateItem> certList = new ArrayList<CertificateItem>();
                JSONObject json = JSONObject.parseObject(result);
                LOGGER.info("查询结果json字符串转证书List:"+json.get("data"));
                JSONArray jsonArray = (JSONArray)json.get("data");
                for(int i=0;i<jsonArray.size();i++){
                    CertificateItem certificateItem = new CertificateItem();
                    EncryptedCertificateItem encryptCertificate = new EncryptedCertificateItem();
                    JSONObject bo = JSONObject.parseObject(jsonArray.get(i).toString());
                    certificateItem.setSerial_no(bo.get("serial_no").toString());
                    certificateItem.setEffective_time(bo.get("effective_time").toString());
                    certificateItem.setExpire_time(bo.get("expire_time").toString());
                    JSONObject encryptBo = JSONObject.parseObject(bo.get("encrypt_certificate").toString());
                    encryptCertificate.setAlgorithm(encryptBo.get("algorithm").toString());
                    encryptCertificate.setNonce(encryptBo.get("nonce").toString());
                    encryptCertificate.setAssociated_data(encryptBo.get("associated_data").toString());
                    encryptCertificate.setCiphertext(encryptBo.get("ciphertext").toString());
                    certificateItem.setEncrypt_certificate(encryptCertificate);
                    certList.add(certificateItem);
                }
                LOGGER.info("证书List:"+certList);

                List<PlainCertificateItem> plainList = decrypt(certList,response);
                if(CollectionUtils.isNotEmpty(plainList)){
                    LOGGER.info("平台证书开始保存");
                    x509Certs = saveCertificate(plainList);
                }
            }
            response.close();
            httpClient.close(); //throw
            return x509Certs;
        } catch (Exception e) {
            e.printStackTrace();
            LOGGER.error("下载平台证书返回结果:"+e);
        }
        return x509Certs;
    }

    private static List<PlainCertificateItem> decrypt(List<CertificateItem> certList,CloseableHttpResponse response) throws GeneralSecurityException, IOException {
        List<PlainCertificateItem> plainCertificateList = new ArrayList<PlainCertificateItem>();
//        qweqweqweqweqweqweqweqwApi3为自己的apiv3秘药
        AesUtil aesUtil = new AesUtil(("qweqweqweqweqweqweqweqwApi3").getBytes(StandardCharsets.UTF_8));
        for(CertificateItem item:certList){
            PlainCertificateItem bo = new PlainCertificateItem();
            bo.setSerialNo(item.getSerial_no());
            bo.setEffectiveTime(item.getEffective_time());
            bo.setExpireTime(item.getExpire_time());
            LOGGER.info("平台证书密文解密");
            bo.setPlainCertificate(aesUtil.decryptToString(item.getEncrypt_certificate().getAssociated_data().getBytes(StandardCharsets.UTF_8),
                    item.getEncrypt_certificate().getNonce().getBytes(StandardCharsets.UTF_8), item.getEncrypt_certificate().getCiphertext()));
            LOGGER.info("平台证书公钥明文:"+bo.getPlainCertificate());
            plainCertificateList.add(bo);
        }
        return plainCertificateList;
    }

    //证书保存地址
    private static List<X509Certificate> saveCertificate(List<PlainCertificateItem> cert) throws IOException {
        List<X509Certificate> x509Certs = new ArrayList<X509Certificate>();
        // 公钥保存在F:\key文件夹下名字为publicKey.pem
        File file = new File("F:\\key");
        file.mkdirs();
        for (PlainCertificateItem item : cert) {
            ByteArrayInputStream inputStream = new ByteArrayInputStream(item.getPlainCertificate().getBytes(StandardCharsets.UTF_8));
            X509Certificate x509Cert = PemUtil.loadCertificate(inputStream);
            x509Certs.add(x509Cert);
            String outputAbsoluteFilename = file.getAbsolutePath() + File.separator + "publicKey.pem";
            try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outputAbsoluteFilename), StandardCharsets.UTF_8))) {
                writer.write(item.getPlainCertificate());
            }
            LOGGER.info("输出证书文件目录:"+outputAbsoluteFilename);
        }
        return x509Certs;
    }

}

  微信支付分验签工具类:

package app.action.signUtil;

import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.PublicKey;
import java.security.Signature;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

import static app.action.signUtil.SignUtil.getToken;


/**
 * 微信支付分验签工具类
 */
public class CheckSignUtil {
    private static String CHARSET_ENCODING = "UTF-8";
    private static String ALGORITHM = "SHA256withRSA";

    public static void main(String[] args) throws Exception {
        getCheckSign("123456789","https://api.mch.weixin.qq.com/v3/certificates",2,null,"16ASDASDASDASD561564651321ASDASDASD","F:\\wxpert\\apiclient_key.pem");
    }

    public static String getCheckSign(String merchantId, String url, int timeout, String body, String certSerialNo, String keyPath) throws UnsupportedEncodingException, Exception{
        String result = "";
        //创建http请求
        HttpGet httpGet = new HttpGet(url);
        httpGet.addHeader("Content-Type", "application/json");
        httpGet.addHeader("Accept", "application/json");

        //设置认证信息
        httpGet.setHeader("Authorization", "WECHATPAY2-SHA256-RSA2048"+" "+getToken("GET",url,null,merchantId,certSerialNo,keyPath));

        //设置请求器配置:如超时限制等
        RequestConfig config = RequestConfig.custom().setSocketTimeout(timeout * 1000).setConnectTimeout(timeout * 1000).build();
        httpGet.setConfig(config);
        try {
            CloseableHttpClient httpClient = HttpClients.createDefault();
            CloseableHttpResponse response = httpClient.execute(httpGet);
            HttpEntity httpEntity = response.getEntity();
            result = EntityUtils.toString(httpEntity, "UTF-8");
            Header[] allHeaders = response.getAllHeaders();
            Map<String, String> headers = new HashMap<String, String>();
            for(int i=0;i<allHeaders.length;i++){
                String key = allHeaders[i].getName();
                String value = allHeaders[i].getValue();
                headers.put(key, value);
            }
            String Nonce = headers.get("Wechatpay-Nonce");
            String Signature = headers.get("Wechatpay-Signature");
            String Timestamp = headers.get("Wechatpay-Timestamp");
            String srcData = Timestamp+"\n"
                    + Nonce+"\n"
                    + result+"\n";
            // publicKeyPath 为自己公钥保存的地址如:F:\key\publicKey.pem
            String publicKeyPath = "F:\\key\\publicKey.pem";
            boolean verify = verify(srcData, Signature, publicKeyPath);
            System.out.println("验签结果 = " + verify);
        }catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 验签
     * @param srcData
     * @param signedData
     * @param publicKeyPath
     * @return
     */
    public static boolean verify(String srcData, String signedData, String publicKeyPath){
        if(srcData==null || signedData==null || publicKeyPath==null){
            return false;
        }
        try {
            PublicKey publicKey = readPublic(publicKeyPath);
            Signature sign = Signature.getInstance(ALGORITHM);
            sign.initVerify(publicKey);
            sign.update(srcData.getBytes(CHARSET_ENCODING));
            return sign.verify(Base64.getDecoder().decode(signedData));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }


    /**
     * 读取公钥
     * @param publicKeyPath
     * @return
     */
    private static PublicKey readPublic(String publicKeyPath){
        if(publicKeyPath==null){
            return null;
        }
        PublicKey pk = null;
        FileInputStream bais = null;
        try {
            CertificateFactory certificatefactory = CertificateFactory.getInstance("X.509");
            bais = new FileInputStream(publicKeyPath);
            X509Certificate cert = (X509Certificate)certificatefactory.generateCertificate(bais);
            pk = cert.getPublicKey();
        } catch (CertificateException e) {
            e.printStackTrace();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } finally{
            if(bais != null){
                try {
                    bais.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return pk;
    }
}

  以下是需要的工具及实体类:

package app.action.signUtil;

import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;

public class AesUtil {

    static final int KEY_LENGTH_BYTE = 32;
    static final int TAG_LENGTH_BIT = 128;
    private final byte[] aesKey;

    public AesUtil(byte[] key) {
        if (key.length != KEY_LENGTH_BYTE) {
            throw new IllegalArgumentException("无效的ApiV3Key,长度必须为32个字节");
        }
        this.aesKey = key;
    }

    public String decryptToString(byte[] associatedData, byte[] nonce, String ciphertext)
            throws GeneralSecurityException, IOException {
        try {
            Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");

            SecretKeySpec key = new SecretKeySpec(aesKey, "AES");
            GCMParameterSpec spec = new GCMParameterSpec(TAG_LENGTH_BIT, nonce);

            cipher.init(Cipher.DECRYPT_MODE, key, spec);
            cipher.updateAAD(associatedData);

            return new String(cipher.doFinal(Base64.getDecoder().decode(ciphertext)), "utf-8");
        } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
            throw new IllegalStateException(e);
        } catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
            throw new IllegalArgumentException(e);
        }
    }
}

  

package app.action.signUtil;


//平台证书item
public class CertificateItem {

    //加密的平台证书序列号
    private String serial_no;

    //加密的平台证书序列号
    private String effective_time;

    //证书弃用时间
    private String expire_time;

    //证书加密信息
    private EncryptedCertificateItem encrypt_certificate;

    public String getSerial_no() {
        return serial_no;
    }

    public void setSerial_no(String serial_no) {
        this.serial_no = serial_no;
    }

    public String getEffective_time() {
        return effective_time;
    }

    public void setEffective_time(String effective_time) {
        this.effective_time = effective_time;
    }

    public String getExpire_time() {
        return expire_time;
    }

    public void setExpire_time(String expire_time) {
        this.expire_time = expire_time;
    }

    public EncryptedCertificateItem getEncrypt_certificate() {
        return encrypt_certificate;
    }

    public void setEncrypt_certificate(EncryptedCertificateItem encrypt_certificate) {
        this.encrypt_certificate = encrypt_certificate;
    }
}

  

package app.action.signUtil;

public class EncryptedCertificateItem {

    //加密的平台证书序列号
    private String algorithm;

    //加密的平台证书序列号
    private String nonce;

    //证书弃用时间
    private String associated_data;

    //证书弃用时间
    private String ciphertext;

    public String getAlgorithm() {
        return algorithm;
    }

    public void setAlgorithm(String algorithm) {
        this.algorithm = algorithm;
    }

    public String getNonce() {
        return nonce;
    }

    public void setNonce(String nonce) {
        this.nonce = nonce;
    }

    public String getAssociated_data() {
        return associated_data;
    }

    public void setAssociated_data(String associated_data) {
        this.associated_data = associated_data;
    }

    public String getCiphertext() {
        return ciphertext;
    }

    public void setCiphertext(String ciphertext) {
        this.ciphertext = ciphertext;
    }
}

  

package app.action.signUtil;

//证书明文item
public class PlainCertificateItem {

    private String serialNo;

    private String effectiveTime;

    private String expireTime;

    private String plainCertificate;

    public String getSerialNo() {
        return serialNo;
    }

    public void setSerialNo(String serialNo) {
        this.serialNo = serialNo;
    }

    public String getEffectiveTime() {
        return effectiveTime;
    }

    public void setEffectiveTime(String effectiveTime) {
        this.effectiveTime = effectiveTime;
    }

    public String getExpireTime() {
        return expireTime;
    }

    public void setExpireTime(String expireTime) {
        this.expireTime = expireTime;
    }

    public String getPlainCertificate() {
        return plainCertificate;
    }

    public void setPlainCertificate(String plainCertificate) {
        this.plainCertificate = plainCertificate;
    }
}

  

posted @ 2020-04-14 12:18  蛋挞小子  阅读(2359)  评论(7编辑  收藏  举报