All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.alipay.v3.util.AlipayConfigUtil Maven / Gradle / Ivy

The newest version!
/**
 * Alipay.com Inc.
 * Copyright (c) 2004-2022 All Rights Reserved.
 */
package com.alipay.v3.util;

import com.alipay.v3.ApiException;
import com.google.common.base.Strings;
import org.bouncycastle.util.encoders.Base64;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.ByteArrayOutputStream;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author jishupei.jsp
 * @version : AlipayConfigUtil, v0.1 2022年06月17日 11:21 上午 jishupei.jsp Exp $
 */
public class AlipayConfigUtil {

    /**
     * 开放平台上创建的应用的ID
     */
    private String appId;

    /**
     * 字符串编码,推荐:utf-8
     */
    private String charset = "utf-8";

    /**
     * 商户私钥
     */
    private String privateKey;

    /**
     * 支付宝公钥字符串(公钥模式下设置,证书模式下无需设置)
     */
    private String alipayPublicKey;

    /**
     * 支付宝根证书内容
     */
    private String rootCertContent;

    /**
     * 支付宝根证书序列号
     */
    private String rootCertSN;

    /**
     * 商户应用公钥证书序列号
     */
    private String appCertSN;

    /**
     * 缓存的不同支付宝公钥证书序列号对应的支付宝公钥
     */
    private ConcurrentHashMap cachedAlipayPublicKey = new ConcurrentHashMap();

    /**
     * 敏感信息对称加密算法类型,推荐:AES
     */
    private String encryptType = "AES";

    /**
     * 敏感信息对称加密算法密钥
     */
    private String encryptKey;

    /**
     * RSA2对应的真实非对称加密算法名称
     */
    public static final String RSA = "RSA";

    /**
     * RSA2对应的真实签名算法名称
     */
    public static final String SHA_256_WITH_RSA = "SHA256WithRSA";

    public static final String ALIPAY_SHA_256_WITH_RSA = "ALIPAY-SHA256withRSA";

    private static final String AES_ALG = "AES";
    private static final String AES_CBC_PCK_ALG = "AES/CBC/PKCS5Padding";
    private static final byte[] AES_IV = initIV();

    /**
     * RSA2最大加密明文大小(2048/8-11=244)
     */
    private static final int MAX_ENCRYPT_BLOCK_SIZE = 244;
    /**
     * RSA2最大解密密文大小(2048/8=256)
     */
    private static final int MAX_DECRYPT_BLOCK_SIZE = 256;

    /**
     * 计算签名并添加header
     *
     * @param httpMethod      本次请求的http方法
     * @param httpRequestUri  本次请求的uri信息(不包括域名)
     * @param httpRequestBody 本次请求的body内容,body为空时按空字符串处理,即""
     * @param headerParams    header
     */
    public void sign(String httpMethod, String httpRequestUri, String httpRequestBody, Map headerParams) throws ApiException {
        /*if (Strings.isNullOrEmpty(this.privateKey)) {
            throw new ApiException("私钥[privateKey]不可为空");
        }*/
        String appAuthToken = headerParams.get("alipay-app-auth-token");
        String nonce = UUID.randomUUID().toString();
        String timestamp = String.valueOf(System.currentTimeMillis());
        String authString = "app_id=" + this.appId
                + (Strings.isNullOrEmpty(this.appCertSN) ? "" : ",app_cert_sn=" + this.appCertSN)
                + ",nonce=" + nonce
                + ",timestamp=" + timestamp;
        String content = authString + "\n"
                + httpMethod + "\n"
                + httpRequestUri + "\n"
                + (Strings.isNullOrEmpty(httpRequestBody) ? "" : httpRequestBody) + "\n"
                + (Strings.isNullOrEmpty(appAuthToken) ? "" : appAuthToken + "\n");
        if (Strings.isNullOrEmpty(this.privateKey)) {
            headerParams.put("Authorization", ALIPAY_SHA_256_WITH_RSA + " " + authString);
        } else {
            headerParams.put("Authorization", ALIPAY_SHA_256_WITH_RSA + " " + authString
                    + ",sign=" + generateSign(content));
        }
        if (!Strings.isNullOrEmpty(this.rootCertSN)) {
            headerParams.put("alipay-root-cert-sn", this.rootCertSN);
        }
    }

    public String generateSign(String content) throws ApiException {
        try {
            byte[] encodedKey = this.privateKey.getBytes();
            encodedKey = Base64.decode(encodedKey);
            PrivateKey priKey = KeyFactory.getInstance(RSA).generatePrivate(new PKCS8EncodedKeySpec(encodedKey));

            Signature signature = Signature.getInstance(SHA_256_WITH_RSA);
            signature.initSign(priKey);
            signature.update(content.getBytes(this.charset));
            byte[] signed = signature.sign();
            return new String(Base64.encode(signed));
        } catch (Exception e) {
            String errorMessage = "签名遭遇异常,请检查私钥格式是否正确。content=" + content + " privateKeySize=" + this.privateKey.length() + " reason=" + e.getMessage();
            throw new ApiException(errorMessage);
        }
    }

    /**
     * 验证签名
     *
     * @param content      待验签的内容
     * @param sign         签名值的Base64串
     * @param alipayCertSN 支付宝公钥证书序列号
     * @param timestamp    时间戳
     * @param nonce        nonce
     * @return true:验证成功;false:验证失败
     */
    public boolean verify(String content, String sign, String alipayCertSN, String timestamp, String nonce) throws ApiException {
        String alipayPublicKey = this.alipayPublicKey;
        if (!Strings.isNullOrEmpty(this.appCertSN)) {//证书模式
            alipayPublicKey = getAlipayPublicKey(alipayCertSN);
        }
        if (Strings.isNullOrEmpty(alipayPublicKey)) {
            if (Strings.isNullOrEmpty(this.privateKey)) {
                return true;
            }
            throw new ApiException("公钥不可为空");
        }
//        if (Strings.isNullOrEmpty(content)) {
//            throw new ApiException("sign check fail: Body is Empty!");
//        }
        content = timestamp + "\n"
                + nonce + "\n"
                + (content == null ? "" : content) + "\n";
        return generateVerify(content, sign, alipayPublicKey);
    }

    public boolean generateVerify(String content, String sign, String alipayPublicKey) throws ApiException {
        try {
            KeyFactory keyFactory = KeyFactory.getInstance(RSA);
            byte[] encodedKey = alipayPublicKey.getBytes();
            encodedKey = Base64.decode(encodedKey);
            PublicKey publicKey = keyFactory.generatePublic(new X509EncodedKeySpec(encodedKey));

            Signature signature = Signature.getInstance(SHA_256_WITH_RSA);
            signature.initVerify(publicKey);
            signature.update(content.getBytes(this.charset));
            return signature.verify(Base64.decode(sign.getBytes()));
        } catch (Exception e) {
            String errorMessage = "验签遭遇异常,请检查公钥格式或签名是否正确。content=" + content + " sign=" + sign +
                    " publicKey=" + alipayPublicKey + " reason=" + e.getMessage();
            throw new ApiException(errorMessage);
        }
    }

    private String getAlipayPublicKey(String sn) throws ApiException {
        //如果没有指定sn,则默认取缓存中的第一个值
        if (Strings.isNullOrEmpty(sn)) {
            return cachedAlipayPublicKey.values().iterator().next();
        }

        if (cachedAlipayPublicKey.containsKey(sn)) {
            return cachedAlipayPublicKey.get(sn);
        } else {
            //网关在支付宝公钥证书变更前,一定会确认通知到商户并在商户做出反馈后,才会更新该商户的支付宝公钥证书
            //TODO: 后续可以考虑加入自动升级支付宝公钥证书逻辑,注意并发更新冲突问题
            throw new ApiException("支付宝公钥证书[" + sn + "]已过期,请重新下载最新支付宝公钥证书并替换原证书文件");
        }
    }

    /**
     * AES加密
     *
     * @param plainText    明文
     * @param headerParams The header parameters
     * @return 密文
     */
    public String encrypt(String plainText, Map headerParams) throws ApiException {
        if (Strings.isNullOrEmpty(this.encryptKey)) {
            return plainText;
        }
        if (!"AES".equals(this.encryptType)) {
            throw new ApiException("当前不支持该算法类型:encryptType=" + this.encryptType);
        }

        headerParams.put("alipay-encrypt-type", this.encryptType);
        //除文件上传接口,加密后Content-Type均为"text/plain"
        String contentType = headerParams.get("Content-Type");
        if (!"multipart/form-data".equals(contentType)) {
            headerParams.put("Content-Type", "text/plain");
        }
        if (Strings.isNullOrEmpty(plainText)) {
            return null;
        }
        try {
            Cipher cipher = Cipher.getInstance(AES_CBC_PCK_ALG);

            IvParameterSpec iv = new IvParameterSpec(AES_IV);
            cipher.init(Cipher.ENCRYPT_MODE,
                    new SecretKeySpec(Base64.decode(this.encryptKey.getBytes()), AES_ALG), iv);

            byte[] encryptBytes = cipher.doFinal(plainText.getBytes(this.charset));
            return new String(Base64.encode(encryptBytes));
        } catch (Exception e) {
            throw new ApiException("AES加密失败,plainText=" + plainText +
                    ",keySize=" + this.encryptKey.length() + "。" + e.getMessage());
        }
    }

    /**
     * 密文
     *
     * @param cipherText 密文
     * @return 明文
     */
    public String decrypt(String cipherText) throws ApiException {
        if (Strings.isNullOrEmpty(this.encryptKey)) {
            return cipherText;
        }
        if (!"AES".equals(this.encryptType)) {
            throw new ApiException("当前不支持该算法类型:encrypeType=" + this.encryptType);
        }
        if (Strings.isNullOrEmpty(cipherText)) {
            return null;
        }
        try {
            Cipher cipher = Cipher.getInstance(AES_CBC_PCK_ALG);
            IvParameterSpec iv = new IvParameterSpec(AES_IV);
            cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(Base64.decode(this.encryptKey.getBytes()), AES_ALG), iv);

            byte[] cleanBytes = cipher.doFinal(Base64.decode(cipherText.getBytes()));
            return new String(cleanBytes, this.charset);
        } catch (Exception e) {
            throw new ApiException("AES解密失败,cipherText=" + cipherText +
                    ",keySize=" + this.encryptKey.length() + "。" + e.getMessage());
        }
    }

    /**
     * 私钥解密
     *
     * @param cipherTextBase64
     * @return
     */
    public String doDecrypt(String cipherTextBase64) throws ApiException {
        try {
            byte[] encodedKey = this.privateKey.getBytes();
            encodedKey = Base64.decode(encodedKey);
            PrivateKey privateKey = KeyFactory.getInstance(RSA).generatePrivate(new PKCS8EncodedKeySpec(encodedKey));

            Cipher cipher = Cipher.getInstance(RSA);
            cipher.init(Cipher.DECRYPT_MODE, privateKey);
            byte[] encryptedData = Base64.decode(cipherTextBase64.getBytes(charset));
            int inputLen = encryptedData.length;
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            int offSet = 0;
            byte[] cache;
            int i = 0;
            // 对数据分段解密
            while (inputLen - offSet > 0) {
                if (inputLen - offSet > MAX_DECRYPT_BLOCK_SIZE) {
                    cache = cipher.doFinal(encryptedData, offSet, MAX_DECRYPT_BLOCK_SIZE);
                } else {
                    cache = cipher.doFinal(encryptedData, offSet, inputLen - offSet);
                }
                out.write(cache, 0, cache.length);
                i++;
                offSet = i * MAX_DECRYPT_BLOCK_SIZE;
            }
            byte[] decryptedData = out.toByteArray();
            out.close();

            return new String(decryptedData, charset);
        } catch (Exception e) {
            String errorMessage = "RSA2非对称解密遭遇异常,请检查私钥格式是否正确。cipherTextBase64=" + cipherTextBase64 + " privateKeySize=" + this.privateKey.length() + " reason=" + e.getMessage();
            throw new ApiException(errorMessage);
        }
    }

    /**
     * 公钥加密
     *
     * @param plainText
     * @return
     */
    public String doEncrypt(String plainText) throws ApiException {
        try {
            KeyFactory keyFactory = KeyFactory.getInstance(RSA);
            byte[] encodedKey = alipayPublicKey.getBytes();
            encodedKey = Base64.decode(encodedKey);
            PublicKey publicKey = keyFactory.generatePublic(new X509EncodedKeySpec(encodedKey));

            Cipher cipher = Cipher.getInstance(RSA);
            cipher.init(Cipher.ENCRYPT_MODE, publicKey);
            byte[] data = plainText.getBytes(charset);
            int inputLen = data.length;
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            int offSet = 0;
            byte[] cache;
            int i = 0;
            // 对数据分段加密
            while (inputLen - offSet > 0) {
                if (inputLen - offSet > MAX_ENCRYPT_BLOCK_SIZE) {
                    cache = cipher.doFinal(data, offSet, MAX_ENCRYPT_BLOCK_SIZE);
                } else {
                    cache = cipher.doFinal(data, offSet, inputLen - offSet);
                }
                out.write(cache, 0, cache.length);
                i++;
                offSet = i * MAX_ENCRYPT_BLOCK_SIZE;
            }
            byte[] encryptedData = Base64.encode(out.toByteArray());
            out.close();

            return new String(encryptedData, charset);
        } catch (Exception e) {
            String errorMessage = "RSA2非对称加密遭遇异常,请检查公钥格式是否正确。plainText=" + plainText +
                    " publicKey=" + alipayPublicKey + " reason=" + e.getMessage();
            throw new ApiException(errorMessage);
        }
    }

    /**
     * 初始向量的方法,全部为0
     * 这里的写法适合于其它算法,AES算法IV值一定是128位的(16字节)
     */
    private static byte[] initIV() {
        try {
            Cipher cipher = Cipher.getInstance(AES_CBC_PCK_ALG);
            int blockSize = cipher.getBlockSize();
            byte[] iv = new byte[blockSize];
            for (int i = 0; i < blockSize; ++i) {
                iv[i] = 0;
            }
            return iv;
        } catch (Exception e) {
            int blockSize = 16;
            byte[] iv = new byte[blockSize];
            for (int i = 0; i < blockSize; ++i) {
                iv[i] = 0;
            }
            return iv;
        }
    }

    /**
     * Getter method for property appId.
     *
     * @return property value of appId
     */
    public String getAppId() {
        return appId;
    }

    /**
     * Setter method for property appId.
     *
     * @param appId value to be assigned to property appId
     */
    public void setAppId(String appId) {
        this.appId = appId;
    }

    /**
     * Getter method for property charset.
     *
     * @return property value of charset
     */
    public String getCharset() {
        return charset;
    }

    /**
     * Setter method for property charset.
     *
     * @param charset value to be assigned to property charset
     */
    public void setCharset(String charset) {
        this.charset = charset;
    }

    /**
     * Getter method for property privateKey.
     *
     * @return property value of privateKey
     */
    public String getPrivateKey() {
        return privateKey;
    }

    /**
     * Setter method for property privateKey.
     *
     * @param privateKey value to be assigned to property privateKey
     */
    public void setPrivateKey(String privateKey) {
        this.privateKey = privateKey;
    }

    /**
     * Getter method for property alipayPublicKey.
     *
     * @return property value of alipayPublicKey
     */
    public String getAlipayPublicKey() {
        return alipayPublicKey;
    }

    /**
     * Setter method for property alipayPublicKey.
     *
     * @param alipayPublicKey value to be assigned to property alipayPublicKey
     */
    public void setAlipayPublicKey(String alipayPublicKey) {
        this.alipayPublicKey = alipayPublicKey;
    }

    /**
     * Getter method for property rootCertContent.
     *
     * @return property value of rootCertContent
     */
    public String getRootCertContent() {
        return rootCertContent;
    }

    /**
     * Setter method for property rootCertContent.
     *
     * @param rootCertContent value to be assigned to property rootCertContent
     */
    public void setRootCertContent(String rootCertContent) {
        this.rootCertContent = rootCertContent;
    }

    /**
     * Getter method for property rootCertSN.
     *
     * @return property value of rootCertSN
     */
    public String getRootCertSN() {
        return rootCertSN;
    }

    /**
     * Setter method for property rootCertSN.
     *
     * @param rootCertSN value to be assigned to property rootCertSN
     */
    public void setRootCertSN(String rootCertSN) {
        this.rootCertSN = rootCertSN;
    }

    /**
     * Getter method for property appCertSN.
     *
     * @return property value of appCertSN
     */
    public String getAppCertSN() {
        return appCertSN;
    }

    /**
     * Setter method for property appCertSN.
     *
     * @param appCertSN value to be assigned to property appCertSN
     */
    public void setAppCertSN(String appCertSN) {
        this.appCertSN = appCertSN;
    }

    /**
     * Getter method for property cachedAlipayPublicKey.
     *
     * @return property value of cachedAlipayPublicKey
     */
    public ConcurrentHashMap getCachedAlipayPublicKey() {
        return cachedAlipayPublicKey;
    }

    /**
     * Setter method for property cachedAlipayPublicKey.
     *
     * @param cachedAlipayPublicKey value to be assigned to property cachedAlipayPublicKey
     */
    public void setCachedAlipayPublicKey(ConcurrentHashMap cachedAlipayPublicKey) {
        this.cachedAlipayPublicKey = cachedAlipayPublicKey;
    }

    /**
     * Getter method for property encryptType.
     *
     * @return property value of encryptType
     */
    public String getEncryptType() {
        return encryptType;
    }

    /**
     * Setter method for property encryptType.
     *
     * @param encryptType value to be assigned to property encryptType
     */
    public void setEncryptType(String encryptType) {
        this.encryptType = encryptType;
    }

    /**
     * Getter method for property encryptKey.
     *
     * @return property value of encryptKey
     */
    public String getEncryptKey() {
        return encryptKey;
    }

    /**
     * Setter method for property encryptKey.
     *
     * @param encryptKey value to be assigned to property encryptKey
     */
    public void setEncryptKey(String encryptKey) {
        this.encryptKey = encryptKey;
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy