package com.yuchen.saas.payment.api.util;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Arrays;
import java.util.Base64;
import java.util.Map;
import java.util.Set;

/**
 * @Author: 张逸飞
 * @Date: 2021/4/10 17:57
 * @Description: MD5WithRSA 工具类
 */
public class MD5WithRSAUtil {

    /**
     * 签名类型
     */
    private static final String SIGN_TYPE = "RSA";

    /**
     * 算法类型
     */
    private static final String ALGORITHM_TYPE = "MD5withRSA";

    /**
     * 根据参数内容获得需要签名的字符串. 注意，若含有sign_type字段，必须和signType参数保持一致。
     * @param data 待签名数据
     * @return 待签名的字符串
     */
    public static String getSignatureContent(final Map<String, String> data) throws Exception {
        Set<String> keySet = data.keySet();
        String[] keyArray = keySet.toArray(new String[keySet.size()]);
        Arrays.sort(keyArray);
        StringBuilder sb = new StringBuilder();
        for (String k : keyArray) {
            // 参数key为 sign 则跳过，（sign 不参与签名）
            if ("sign".equals(k)) {
                continue;
            }
            // 参数值为空，则不参与签名
            if (data.get(k).trim().length() > 0){
                if (sb.length() > 0){
                    sb.append("&");
                }
                sb.append(k).append("=").append(data.get(k).trim());
            }
        }
        return sb.toString();
    }

    /**
     * 用md5生成内容摘要，再用RSA的私钥加密，进而生成数字签名
     * @param privateKeyUrl 私钥路径
     * @param content 需要加密的字符串
     * @return 加密后的字符串内容
     * @throws Exception
     */
    public static String getMd5Sign(String privateKeyUrl, String content) throws Exception {
        byte[] contentBytes = content.getBytes("utf-8");
        Signature signature = Signature.getInstance(ALGORITHM_TYPE);
        signature.initSign(getRSAPrivateKey(privateKeyUrl));
        signature.update(contentBytes);
        byte[] signs = signature.sign();
        return org.apache.commons.codec.binary.Base64.encodeBase64String(signs);
    }

    /**
     * 对用md5和RSA私钥生成的数字签名进行验证
     * @param publicKeyStr 公钥字符串
     * @param content 加密的字符串内容
     * @param sign 加密的数字签名
     * @return 是否验证成功
     * @throws Exception
     */
    public static boolean verifyWhenMd5Sign(String publicKeyStr, String content, String sign) throws Exception {
        byte[] contentBytes = content.getBytes("utf-8");
        // PublicKey publicKey = getPublicKey(publicKeyStr);
        publicKeyStr = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCSS/DiwdCf/aZsxxcacDnooGph3d2JOj5GXWi+q3gznZauZjkNP8SKl3J2liP0O6rU/Y/29+IUe+GTMhMOFJuZm1htAtKiu5ekW0GlBMWxf4FPkYlQkPE0FtaoMP3gYfh+OwI+fIRrpW3ySn3mScnc6Z700nU/VYrRkfcSCbSnRwIDAQAB";
        PublicKey publicKey = getPubKey(publicKeyStr);
        Signature signature = Signature.getInstance(ALGORITHM_TYPE);
        signature.initVerify(publicKey);
        signature.update(contentBytes);
        return signature.verify(Base64.getDecoder().decode(sign));
    }

    //对用md5和RSA私钥生成的数字签名进行验证
    public static boolean verifyWhenMd5Sign(String content, String sign, PublicKey publicKey) throws Exception {
        byte[] contentBytes = content.getBytes("utf-8");
        Signature signature = Signature.getInstance("MD5withRSA");
        signature.initVerify(publicKey);
        signature.update(contentBytes);
        return signature.verify(Base64.getDecoder().decode(sign));
    }

    /**
     * 根据私钥字符串生成 PrivateKey
     * @param privateKeyStr 私钥字符串
     * @return PrivateKey
     */
    private static PrivateKey getPrivateKey(String privateKeyStr) {
        PrivateKey privateKey = null;
        try {
            KeyFactory keyFactory = KeyFactory.getInstance(SIGN_TYPE);
            PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKeyStr));
            privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (InvalidKeySpecException e) {
            e.printStackTrace();
        }
        return privateKey;
    }

    /**
     * 根据公钥字符串生成 PublicKey
     * @param publicKeyStr 公钥字符串
     * @return PublicKey
     */
    private static PublicKey getPubKey(String publicKeyStr) {
        PublicKey publicKey = null;
        try {
            X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(Base64.getDecoder().decode(publicKeyStr));
            KeyFactory keyFactory = KeyFactory.getInstance(SIGN_TYPE);
            publicKey = keyFactory.generatePublic(x509EncodedKeySpec);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (InvalidKeySpecException e) {
            e.printStackTrace();
        }
        return publicKey;
    }

    /**
     * 获取商户私钥。
     *
     * @param filename 私钥文件路径  (required)
     * @return 私钥对象
     */
    private static PrivateKey getRSAPrivateKey(String filename) throws IOException {
        String content = new String(Files.readAllBytes(Paths.get(filename)), "utf-8");
        try {
            String privateKey = content.replace("-----BEGIN PRIVATE KEY-----", "")
                    .replace("-----END PRIVATE KEY-----", "")
                    .replaceAll("\\s+", "");

            KeyFactory keyFactory = KeyFactory.getInstance(SIGN_TYPE);
            return keyFactory.generatePrivate(
                    new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKey))
            );
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("当前Java环境不支持RSA", e);
        } catch (InvalidKeySpecException e) {
            throw new RuntimeException("无效的密钥格式");
        }
    }

    /**
     * 获取平台公钥。
     *
     * @param filename 公钥文件路径  (required)
     * @return 私钥对象
     */
    private static PublicKey getPublicKey(String filename) throws IOException {
        String content = new String(Files.readAllBytes(Paths.get(filename)), "utf-8");
        try {
            String publicKey = content.replace("-----BEGIN PUBLIC KEY-----", "")
                    .replace("-----END PUBLIC KEY-----", "")
                    .replaceAll("\\s+", "");

            KeyFactory kf = KeyFactory.getInstance(SIGN_TYPE);
            return kf.generatePublic(new X509EncodedKeySpec(Base64.getDecoder().decode(publicKey)));
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("当前Java环境不支持RSA", e);
        } catch (InvalidKeySpecException e) {
            throw new RuntimeException("无效的密钥格式");
        }
    }


    /**
     * 从文件中获取公钥(公钥的比特编码是X.509格式)
     *
     * @return
     * @throws Exception
     */
    public PublicKey getPublicKeyByFile(String signaturePublic) throws Exception {
        //建立文件对象
        File file = new File(signaturePublic);
        FileInputStream fis = new FileInputStream(file);
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024];
        int len;
        while ((len = fis.read(buffer)) > 0) {
            bos.write(buffer, 0, len);
        }
        byte[] pbks = Base64.getDecoder().decode(bos.toByteArray());
        X509EncodedKeySpec encodedKeySpec = new X509EncodedKeySpec(pbks);
        PublicKey newPbk = KeyFactory.getInstance("RSA").generatePublic(encodedKeySpec);
        return newPbk;
    }

}
