package com.izouma.nineth.utils;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.crypto.*;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.math.BigInteger;
import java.nio.file.Files;
import java.security.*;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.RSAPrivateKeySpec;
import java.security.spec.RSAPublicKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.Enumeration;

/**
 * 密钥处理工具
 *
 */
public  class CryptoUtil {

    public static Logger logger = LoggerFactory.getLogger(CryptoUtil.class);

    /**
     * 支付平台公钥
     */
    private  PublicKey publicKey;

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

    /**
     * 初始化
     *
     * @param publicKeyPath
     * @param privateKeyPath
     * @param keyPassword
     * @throws Exception
     */
    public  void init(String publicKeyPath, String privateKeyPath, String keyPassword) throws Exception {
        if(StringUtils.isNotEmpty(publicKeyPath)){
            initPulbicKey(publicKeyPath);
        }
        if(StringUtils.isNotEmpty(privateKeyPath)) {
            initPrivateKey(privateKeyPath, keyPassword);
        }
    }


    private  void initPulbicKey(String publicKeyPath) throws Exception {
        if (publicKeyPath != null) {
            try {
                InputStream inputStream = CryptoUtil.class.getClassLoader().getResourceAsStream(publicKeyPath);
                if(inputStream == null){
                    inputStream = new FileInputStream(publicKeyPath);
                }
                if(publicKeyPath.endsWith(".cer")){
                    this.publicKey = getPublicKeyForCer(inputStream);
                }else{
                    this.publicKey = getPublicKeyForPem(inputStream);
                }

            } catch (Exception e) {
                logger.error("无法加载银行公钥[{}]", new Object[]{publicKeyPath});
                logger.error(e.getMessage(), e);
                throw e;
            }
        }
    }

    private  void initPrivateKey(String privateKeyPath, String keyPassword) throws Exception {
        try {
            InputStream inputStream = CryptoUtil.class.getClassLoader().getResourceAsStream(privateKeyPath);
            if(inputStream == null){
                inputStream = new FileInputStream(privateKeyPath);
            }
            if(privateKeyPath.endsWith(".pfx") || privateKeyPath.endsWith(".p12")){
                this.privateKey = getPrivateKeyForPfx(inputStream, keyPassword);
            }else{
                this.privateKey = getPrivateKeyForPkcs8(inputStream, keyPassword);
            }

        } catch (Exception e) {
            logger.error("无法加载本地私钥[{}]", new Object[]{privateKeyPath});
            logger.error(e.getMessage(), e);
            throw e;
        }
    }

    public  PublicKey getPublicKeyForCer(InputStream inputStream) throws Exception {
        try {
            CertificateFactory cf = CertificateFactory.getInstance("X.509");
            X509Certificate oCert = (X509Certificate) cf.generateCertificate(inputStream);
            PublicKey publicKey = oCert.getPublicKey();
            return publicKey;
        } catch (Exception e) {
            throw new Exception("读取公钥异常", e );
        } finally {
            try {
                if (inputStream != null) {
                    inputStream.close();
                }
            } catch (IOException e) {
            }
        }
    }

    public  PublicKey getPublicKeyForPem(InputStream inputStream) throws Exception {
        try {
            String startStr = "-----BEGIN PUBLIC KEY-----";
            String endStr = "-----END PUBLIC KEY-----";
            String rowStr = IOUtils.toString(inputStream);
            String keyData = rowStr.replace(startStr, "");
            keyData = keyData.replace(endStr, "");
            keyData = keyData.replaceAll("(\r?)\n","").trim();
            X509EncodedKeySpec bobPubKeySpec = new X509EncodedKeySpec(Base64.getDecoder().decode(keyData));
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            PublicKey publicKey = keyFactory.generatePublic(bobPubKeySpec);
            return publicKey;
        } catch (Exception e) {
            throw new Exception("读取公钥异常", e);
        } finally {
            try {
                if (inputStream != null) {
                    inputStream.close();
                }
            } catch (IOException e) {
            }
        }
    }


    public  PrivateKey getPrivateKeyForPfx(InputStream inputStream, String password) throws Exception {
        try {
            KeyStore ks = KeyStore.getInstance("PKCS12");
            char[] nPassword = null;
            if ((password == null) || (password.trim().equals(""))) {
                nPassword = null;
            } else {
                nPassword = password.toCharArray();
            }
            ks.load(inputStream, nPassword);
            Enumeration<String> enumas = ks.aliases();
            String keyAlias = null;
            if (enumas.hasMoreElements()) {
                keyAlias = (String) enumas.nextElement();
            }
            PrivateKey privateKey = (PrivateKey) ks.getKey(keyAlias, nPassword);
            return privateKey;
        } catch (FileNotFoundException e) {
            throw new Exception("私钥路径文件不存在", e);
        } catch (IOException e) {
            throw new Exception("读取私钥异常", e);
        } catch (NoSuchAlgorithmException e) {
            throw new Exception("生成私钥对象异常", e);
        } finally {
            try {
                if (inputStream != null) {
                    inputStream.close();
                }
            } catch (IOException e) {
            }
        }
    }


    public  PrivateKey getPrivateKeyForPkcs8(InputStream inputStream, String password) throws Exception {
        try {
            String nocryptStart = "-----BEGIN PRIVATE KEY-----";
            String nocryptEnd = "-----END PRIVATE KEY-----";
            String encryptStart = "-----BEGIN ENCRYPTED PRIVATE KEY-----";
            String encryptEnd = "-----END ENCRYPTED PRIVATE KEY-----";
            String rowStr = IOUtils.toString(inputStream);
            PrivateKey privateKey;
            if(rowStr.startsWith(nocryptStart)){
                String keyData = rowStr.replace(nocryptStart, "");
                keyData = keyData.replace(nocryptEnd, "");
                keyData = keyData.replaceAll("(\r?)\n","").trim();
                PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(Base64.getDecoder().decode(keyData));
                KeyFactory keyFactory = KeyFactory.getInstance("RSA");
                privateKey = keyFactory.generatePrivate(keySpec);
            }else{
                if(password == null){
                    password = "";
                }
                String keyData = rowStr.replace(encryptStart, "");
                keyData = keyData.replace(encryptEnd, "");
                keyData = keyData.replaceAll("(\r?)\n","").trim();
                EncryptedPrivateKeyInfo privKeyInfo = new EncryptedPrivateKeyInfo(Base64.getDecoder().decode(keyData));
                PBEKeySpec pbeKeySpec = new PBEKeySpec(password.toCharArray());
                SecretKeyFactory pbeKeyFactory = SecretKeyFactory.getInstance(privKeyInfo.getAlgName());
                PKCS8EncodedKeySpec keySpec = privKeyInfo.getKeySpec(pbeKeyFactory.generateSecret(pbeKeySpec));
                KeyFactory keyFactory = KeyFactory.getInstance("RSA");
                privateKey = keyFactory.generatePrivate(keySpec);
            }

            return privateKey;
        } catch (FileNotFoundException e) {
            throw new Exception("私钥路径文件不存在", e);
        } catch (IOException e) {
            throw new Exception("读取私钥异常", e);
        } catch (NoSuchAlgorithmException e) {
            throw new Exception("生成私钥对象异常", e);
        } finally {
            try {
                if (inputStream != null) {
                    inputStream.close();
                }
            } catch (IOException e) {
            }
        }
    }


    public  byte[] digitalSign(final byte[] plainBytes, final String signAlgorithm) throws Exception {
        try {
            final Signature signature = Signature.getInstance(signAlgorithm);
            signature.initSign(this.privateKey);
            signature.update(plainBytes);
            final byte[] signBytes = signature.sign();
            return signBytes;
        } catch (NoSuchAlgorithmException e3) {
            throw new Exception(String.format("数字签名时没有[%s]此类算法", signAlgorithm));
        } catch (InvalidKeyException e) {
            throw new Exception("数字签名时私钥无效", e);
        } catch (SignatureException e2) {
            throw new Exception("数字签名时出现异常", e2);
        }
    }

    public  boolean verifyDigitalSign(final byte[] plainBytes, final byte[] signBytes,
                                            final String signAlgorithm) throws Exception {
        boolean isValid = false;
        try {
            final Signature signature = Signature.getInstance(signAlgorithm);
            signature.initVerify(this.publicKey);
            signature.update(plainBytes);
            isValid = signature.verify(signBytes);
            return isValid;
        } catch (NoSuchAlgorithmException e) {
            throw new Exception(String.format("验证数字签名时没有[%s]此类算法", signAlgorithm), e);
        } catch (InvalidKeyException e2) {
            throw new Exception("验证数字签名时私钥无效", e2);
        } catch (SignatureException e3) {
            throw new Exception("验证数字签名时出现异常", e3);
        }
    }

    public  boolean verifyDigitalSign(final byte[] plainBytes, final byte[] signBytes, final X509Certificate cert,
                                            final String signAlgorithm) throws Exception {
        boolean isValid = false;
        try {
            final Signature signature = Signature.getInstance(signAlgorithm);
            signature.initVerify(cert);
            signature.update(plainBytes);
            isValid = signature.verify(signBytes);
            return isValid;
        } catch (NoSuchAlgorithmException e3) {
            throw new Exception(String.format("验证数字签名时没有[%s]此类算法", signAlgorithm));
        } catch (InvalidKeyException e) {
            throw new Exception("验证数字签名时公钥无效", e);
        } catch (SignatureException e2) {
            throw new Exception("验证数字签名时出现异常", e2);
        }
    }

    public  byte[] RSAEncrypt(final byte[] plainBytes, final int keyLength, final int reserveSize,
                                    final String cipherAlgorithm) throws Exception {
        final int keyByteSize = keyLength / 8;
        final int encryptBlockSize = keyByteSize - reserveSize;
        int nBlock = plainBytes.length / encryptBlockSize;
        if (plainBytes.length % encryptBlockSize != 0) {
            ++nBlock;
        }
        try {
            final Cipher cipher = Cipher.getInstance(cipherAlgorithm);
            cipher.init(1, this.publicKey);
            final ByteArrayOutputStream outbuf = new ByteArrayOutputStream(nBlock * keyByteSize);
            for (int offset = 0; offset < plainBytes.length; offset += encryptBlockSize) {
                int inputLen = plainBytes.length - offset;
                if (inputLen > encryptBlockSize) {
                    inputLen = encryptBlockSize;
                }
                final byte[] encryptedBlock = cipher.doFinal(plainBytes, offset, inputLen);
                outbuf.write(encryptedBlock);
            }
            outbuf.flush();
            outbuf.close();
            return outbuf.toByteArray();
        } catch (NoSuchAlgorithmException e5) {
            throw new Exception(String.format("没有[%s]此类加密算法", cipherAlgorithm));
        } catch (NoSuchPaddingException e6) {
            throw new Exception(String.format("没有[%s]此类填充模式", cipherAlgorithm));
        } catch (InvalidKeyException e) {
            throw new Exception("无效密钥", e);
        } catch (IllegalBlockSizeException e2) {
            throw new Exception("加密块大小不合适", e2);
        } catch (BadPaddingException e3) {
            throw new Exception("错误填充模式", e3);
        } catch (IOException e4) {
            throw new Exception("字节输出流异常", e4);
        }
    }

    public  byte[] RSADecrypt(final byte[] encryptedBytes, final int keyLength, final int reserveSize,
                                    final String cipherAlgorithm) throws Exception {
        final int keyByteSize = keyLength / 8;
        final int decryptBlockSize = keyByteSize - reserveSize;
        final int nBlock = encryptedBytes.length / keyByteSize;
        try {
            final Cipher cipher = Cipher.getInstance(cipherAlgorithm);
            cipher.init(Cipher.DECRYPT_MODE, this.privateKey);
            final ByteArrayOutputStream outbuf = new ByteArrayOutputStream(nBlock * decryptBlockSize);
            for (int offset = 0; offset < encryptedBytes.length; offset += keyByteSize) {
                int inputLen = encryptedBytes.length - offset;
                if (inputLen > keyByteSize) {
                    inputLen = keyByteSize;
                }
                final byte[] decryptedBlock = cipher.doFinal(encryptedBytes, offset, inputLen);
                outbuf.write(decryptedBlock);
            }
            outbuf.flush();
            outbuf.close();
            return outbuf.toByteArray();
        } catch (NoSuchAlgorithmException e5) {
            throw new Exception(String.format("没有[%s]此类解密算法", cipherAlgorithm));
        } catch (NoSuchPaddingException e6) {
            throw new Exception(String.format("没有[%s]此类填充模式", cipherAlgorithm));
        } catch (InvalidKeyException e) {
            throw new Exception("无效密钥", e);
        } catch (IllegalBlockSizeException e2) {
            throw new Exception("加密块大小不合适", e2);
        } catch (BadPaddingException e3) {
            throw new Exception("错误填充模式", e3);
        } catch (IOException e4) {
            throw new Exception("字节输出流异常", e4);
        }
    }

    public  PublicKey toPublicKey(final BigInteger exponent, final BigInteger modulus) throws Exception {
        final KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        final RSAPublicKeySpec pubSpec = new RSAPublicKeySpec(modulus, exponent);
        final PublicKey key = keyFactory.generatePublic(pubSpec);
        return key;
    }

    public  PrivateKey toPrivateKey(final BigInteger exponent, final BigInteger modulus) throws Exception {
        final KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        final RSAPrivateKeySpec prispec = new RSAPrivateKeySpec(modulus, exponent);
        final PrivateKey key = keyFactory.generatePrivate(prispec);
        return key;
    }

    public  byte[] AESEncrypt(final byte[] plainBytes, final byte[] keyBytes, final String keyAlgorithm,
                                    final String cipherAlgorithm, final String IV) throws Exception {
        try {
            if (keyBytes.length % 8 != 0 || keyBytes.length < 16 || keyBytes.length > 32) {
                throw new Exception("AES密钥长度不合法");
            }
            final Cipher cipher = Cipher.getInstance(cipherAlgorithm);
            final SecretKey secretKey = new SecretKeySpec(keyBytes, keyAlgorithm);
            if (StringUtils.trimToNull(IV) != null) {
                final IvParameterSpec ivspec = new IvParameterSpec(IV.getBytes());
                cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivspec);
            } else {
                cipher.init(Cipher.ENCRYPT_MODE, secretKey);
            }
            final byte[] encryptedBytes = cipher.doFinal(plainBytes);
            return encryptedBytes;
        } catch (NoSuchAlgorithmException e5) {
            throw new Exception(String.format("没有[%s]此类加密算法", cipherAlgorithm));
        } catch (NoSuchPaddingException e6) {
            throw new Exception(String.format("没有[%s]此类填充模式", cipherAlgorithm));
        } catch (InvalidKeyException e) {
            throw new Exception("无效密钥", e);
        } catch (InvalidAlgorithmParameterException e2) {
            throw new Exception("加密块大小不合适", e2);
        } catch (BadPaddingException e3) {
            throw new Exception("错误填充模式", e3);
        } catch (IllegalBlockSizeException e4) {
            throw new Exception("字节输出流异常", e4);
        }
    }

    public  byte[] AESDecrypt(final byte[] encryptedBytes, final byte[] keyBytes, final String keyAlgorithm,
                                    final String cipherAlgorithm, final String IV) throws Exception {
        try {
            if (keyBytes.length % 8 != 0 || keyBytes.length < 16 || keyBytes.length > 32) {
                throw new Exception("AES密钥长度不合法");
            }
            final Cipher cipher = Cipher.getInstance(cipherAlgorithm);
            final SecretKey secretKey = new SecretKeySpec(keyBytes, keyAlgorithm);
            if (IV != null && StringUtils.trimToNull(IV) != null) {
                final IvParameterSpec ivspec = new IvParameterSpec(IV.getBytes());
                cipher.init(Cipher.DECRYPT_MODE, secretKey, ivspec);
            } else {
                cipher.init(Cipher.DECRYPT_MODE, secretKey);
            }
            final byte[] decryptedBytes = cipher.doFinal(encryptedBytes);
            return decryptedBytes;
        } catch (NoSuchAlgorithmException e5) {
            throw new Exception(String.format("没有[%s]此类加密算法", cipherAlgorithm));
        } catch (NoSuchPaddingException e6) {
            throw new Exception(String.format("没有[%s]此类填充模式", cipherAlgorithm));
        } catch (InvalidKeyException e) {
            throw new Exception("无效密钥", e);
        } catch (InvalidAlgorithmParameterException e2) {
            throw new Exception("无效密钥参数", e2);
        } catch (BadPaddingException e3) {
            throw new Exception("错误填充模式", e3);
        } catch (IllegalBlockSizeException e4) {
            throw new Exception("解密块大小不合法", e4);
        }
    }


}
