package cn.pluss.platform.util;

import cn.pluss.platform.exception.MsgException;
import com.aliyun.oss.ServiceException;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;

/**
 * @author DJH
 * rsa加密、解密，签名、验签工具类
 */
public class RSAUtils {
    
    private RSAUtils() {}

    public static final String RSA = "RSA";

    public static final String CIPHERALGORITHM = "RSA/ECB/PKCS1Padding";

    public static String sign(String privateKey, String src) throws Exception {
        RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) getPrivateKey(privateKey);
        return sign(rsaPrivateKey, src);
    }

    public static boolean checkSign(String publicKey, String src, String sign) throws Exception {
        RSAPublicKey rsaPublicKey = (RSAPublicKey) getPublicKey(publicKey);
        return checkSign(rsaPublicKey, src, sign);
    }

    public static String signSHA256(String privateKey, String src) throws Exception {
        RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) getPrivateKey(privateKey);
        return signSHA256WithRSA(rsaPrivateKey, src);
    }

    public static boolean checkSignSHA256(String publicKey, String src, String sign) throws Exception {
        RSAPublicKey rsaPublicKey = (RSAPublicKey) getPublicKey(publicKey);
        return checkSignSHA256WithRSA(rsaPublicKey, src, sign);
    }

    /**
     * 私钥签名过程
     *
     * @param rsaPrivateKey 私钥
     * @param plainTextData 明文数据
     * @return
     * @throws Exception 加密过程中的异常信息
     */
    public static String sign(RSAPrivateKey rsaPrivateKey, String plainTextData) throws Exception {
        if (rsaPrivateKey == null) {
            throw new MsgException("签名私钥为空, 请设置");
        }

        Signature signature = Signature.getInstance("SHA1withRSA");
        signature.initSign(rsaPrivateKey);
        signature.update(plainTextData.getBytes());
        byte[] result = signature.sign();

        return Base64.getEncoder().encodeToString(result);
    }

    /**
     * 私钥签名过程
     *
     * @param rsaPrivateKey 私钥
     * @param plainTextData 明文数据
     * @return
     * @throws Exception 加密过程中的异常信息
     */
    public static String signSHA256WithRSA(RSAPrivateKey rsaPrivateKey, String plainTextData) throws Exception {
        if (rsaPrivateKey == null) {
            throw new MsgException("签名私钥为空, 请设置");
        }

        Signature signature = Signature.getInstance("SHA256withRSA");
        signature.initSign(rsaPrivateKey);
        signature.update(plainTextData.getBytes());
        byte[] result = signature.sign();

        return Base64.getEncoder().encodeToString(result);
    }

    public static boolean checkSign(RSAPublicKey rsaPublicKey, String src, String sign) throws Exception {
        Signature signature = Signature.getInstance("SHA1withRSA");
        signature.initVerify(rsaPublicKey);
        signature.update(src.getBytes(StandardCharsets.UTF_8));
        return signature.verify(Base64.getDecoder().decode(sign));
    }

    public static boolean checkSignSHA256WithRSA(RSAPublicKey rsaPublicKey, String src, String sign) throws Exception {
        Signature signature = Signature.getInstance("SHA256withRSA");
        signature.initVerify(rsaPublicKey);
        signature.update(src.getBytes(StandardCharsets.UTF_8));
        return signature.verify(Base64.getDecoder().decode(sign));
    }

    public static PublicKey getPublicKey(String key) throws Exception {
        byte[] keyBytes;
        keyBytes = Base64.getDecoder().decode(key);
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance(RSA);
        return keyFactory.generatePublic(keySpec);
    }

    public static PrivateKey getPrivateKey(String key) throws Exception {
        byte[] keyBytes;
        keyBytes = Base64.getDecoder().decode(key);
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance(RSA);
        return keyFactory.generatePrivate(keySpec);
    }

    /**
     * 公钥加密过程
     *
     * @param rsaPublicKey  公钥
     * @param plainTextData 明文数据
     * @return
     * @throws Exception 加密过程中的异常信息
     */
    public static String encrypt(RSAPublicKey rsaPublicKey, String plainTextData) throws Exception {
        if (rsaPublicKey == null) {
            throw new MsgException("加密公钥为空, 请设置");
        }

        Base64.Encoder encoder = Base64.getEncoder();

        Cipher cipher = Cipher.getInstance(RSA);
        cipher.init(Cipher.ENCRYPT_MODE, rsaPublicKey);
        byte[] output = cipher.doFinal(plainTextData.getBytes(StandardCharsets.UTF_8));
        return encoder.encodeToString(output);
    }

    /**
     * 使用私钥解密
     * @param rsaPrivateKey  私钥
     * @param encryptParam  公钥加密的字符串
     * @return  解密后的字符串
     * */
    public static String decrypt(RSAPrivateKey rsaPrivateKey,String encryptParam) throws Exception {
        Base64.Decoder decoder = Base64.getDecoder();
        Base64.Encoder encoder = Base64.getEncoder();

        Cipher cipher;
        try {
            cipher = Cipher.getInstance(RSA);
            cipher.init(Cipher.DECRYPT_MODE, rsaPrivateKey);
            byte[] output = cipher.doFinal(decoder.decode(encryptParam));
            return new String(output, StandardCharsets.UTF_8);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * Ecb解密
     * @param encryptedBytes
     * @param privateKey
     * @param keyLength
     * @param reserveSize
     * @return
     */
    public static byte[] decryptEcb(byte[] encryptedBytes, PrivateKey privateKey, int keyLength, int reserveSize) throws ServiceException{
        int keyByteSize = keyLength / 8; // 密钥字节数
        int decryptBlockSize = keyByteSize - reserveSize; // 解密块大小=密钥字节数-padding填充字节数
        int nBlock = encryptedBytes.length / keyByteSize;// 计算分段解密的block数，理论上能整除

        try {
            Cipher cipher = Cipher.getInstance(CIPHERALGORITHM);
            cipher.init(Cipher.DECRYPT_MODE, privateKey);

            // 输出buffer，大小为nBlock个decryptBlockSize
            ByteArrayOutputStream outbuf = new ByteArrayOutputStream(nBlock * decryptBlockSize);
            // 分段解密
            for (int offset = 0; offset < encryptedBytes.length; offset += keyByteSize) {
                // block大小: decryptBlock 或 剩余字节数
                int inputLen = encryptedBytes.length - offset;
                if (inputLen > keyByteSize) {
                    inputLen = keyByteSize;
                }
                // 得到分段解密结果
                byte[] decryptedBlock = cipher.doFinal(encryptedBytes, offset, inputLen);
                // 追加结果到输出buffer中
                outbuf.write(decryptedBlock);
            }
            outbuf.flush();
            outbuf.close();
            return outbuf.toByteArray();
        } catch (NoSuchAlgorithmException e) {
            throw new ServiceException(String.format("没有[%s]此类解密算法", CIPHERALGORITHM));
        } catch (NoSuchPaddingException e) {
            throw new ServiceException(String.format("没有[%s]此类填充模式", CIPHERALGORITHM));
        } catch (InvalidKeyException e) {
            throw new ServiceException("无效密钥");
        } catch (IllegalBlockSizeException e) {
            throw new ServiceException("解密块大小不合法");
        } catch (BadPaddingException e) {
            throw new ServiceException("错误填充模式");
        } catch (IOException e) {
            throw new ServiceException("字节输出流异常");
        }
    }
}
