package com.fengwenyi.javalib.util;

import com.fengwenyi.javalib.constant.CharsetConstant;
import com.fengwenyi.javalib.third.Base64;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.io.UnsupportedEncodingException;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

/**
 *
 *
 *
 * @author Wenyi Feng([email protected])
 */
public class RSAUtils {

    /*
    // 这里面有很多异常,都交给使用者自行处理,
    // 可能你并不了解这么多的异常,那么这里可能会帮助到你
    NoSuchAlgorithmException:无此加密算法
    InvalidKeyException:非法密钥
    NoSuchPaddingException:无明文数据
    BadPaddingException:明文数据损坏
    UnsupportedEncodingException:不支持的编码
    InvalidKeySpecException:密文格式不正确
    IllegalBlockSizeException:密文长度非法
    */

    private static final String RSA = "RSA";
    private static int LENGTH = 512;

    /**
     * 获取密钥
     * @param keySize 64的整数倍
     * @throws NoSuchAlgorithmException [ellipsis]
     * @return 默认先私钥后公钥
     */
    public static String[] getKey(int keySize) throws NoSuchAlgorithmException {
        LENGTH = keySize;
        return commonKey(LENGTH);
    }

    /**
     * 获取密钥
     * @throws NoSuchAlgorithmException [ellipsis]
     * @return 默认先私钥后公钥
     */
    public static String[] getKey() throws NoSuchAlgorithmException {
       return commonKey(LENGTH);
    }

    /**
     * 私钥加密
     * @param key [ellipsis]
     * @param plainText [ellipsis]
     * @return [ellipsis]
     * @throws NoSuchAlgorithmException [ellipsis]
     * @throws InvalidKeySpecException [ellipsis]
     * @throws NoSuchPaddingException [ellipsis]
     * @throws UnsupportedEncodingException [ellipsis]
     * @throws BadPaddingException [ellipsis]
     * @throws IllegalBlockSizeException [ellipsis]
     * @throws InvalidKeyException [ellipsis]
     */
    public static String privateKeyEncrypt(String key, String plainText) throws NoSuchAlgorithmException,
            InvalidKeySpecException, NoSuchPaddingException, UnsupportedEncodingException, BadPaddingException,
            IllegalBlockSizeException, InvalidKeyException {
        PrivateKey privateKey = commonGetPrivatekeyByText(key);
        Cipher cipher = Cipher.getInstance(RSA);
        cipher.init(Cipher.ENCRYPT_MODE, privateKey);
        byte [] result = cipher.doFinal(plainText.getBytes(CharsetConstant.UTF_8));

        return Base64.byteArrayToBase64(result);
    }

    /**
     * 公钥解密
     * @param key [ellipsis]
     * @param cipherText [ellipsis]
     * @return [ellipsis]
     * @throws NoSuchAlgorithmException [ellipsis]
     * @throws InvalidKeySpecException [ellipsis]
     * @throws NoSuchPaddingException [ellipsis]
     * @throws InvalidKeyException [ellipsis]
     * @throws BadPaddingException [ellipsis]
     * @throws IllegalBlockSizeException [ellipsis]
     */
    public static String publicKeyDecrypt(String key, String cipherText) throws NoSuchAlgorithmException,
            InvalidKeySpecException, NoSuchPaddingException, InvalidKeyException, BadPaddingException,
            IllegalBlockSizeException {
        PublicKey publicKey = commonGetPublickeyByText(key);
        Cipher cipher = Cipher.getInstance(RSA);
        cipher.init(Cipher.DECRYPT_MODE, publicKey);
        byte[] result = cipher.doFinal(Base64.base64ToByteArray(cipherText));

        return new String(result);
    }

    /**
     * 公钥加密
     * @param key [ellipsis]
     * @param plainText [ellipsis]
     * @return [ellipsis]
     * @throws NoSuchAlgorithmException [ellipsis]
     * @throws InvalidKeySpecException [ellipsis]
     * @throws NoSuchPaddingException [ellipsis]
     * @throws InvalidKeyException [ellipsis]
     * @throws BadPaddingException [ellipsis]
     * @throws IllegalBlockSizeException [ellipsis]
     * @throws UnsupportedEncodingException [ellipsis]
     */
    public static String publicKeyEncrypt(String key, String plainText) throws NoSuchAlgorithmException,
            InvalidKeySpecException, NoSuchPaddingException, InvalidKeyException, BadPaddingException,
            IllegalBlockSizeException, UnsupportedEncodingException {
        PublicKey publicKey = commonGetPublickeyByText(key);
        Cipher cipher = Cipher.getInstance(RSA);
        cipher.init(Cipher.ENCRYPT_MODE, publicKey);

        byte [] result = cipher.doFinal(plainText.getBytes(CharsetConstant.UTF_8));

        return Base64.byteArrayToBase64(result);
    }

    /**
     * 私钥解密
     * @param key [ellipsis]
     * @param cipherText [ellipsis]
     * @return [ellipsis]
     * @throws NoSuchAlgorithmException [ellipsis]
     * @throws InvalidKeySpecException [ellipsis]
     * @throws NoSuchPaddingException [ellipsis]
     * @throws InvalidKeyException [ellipsis]
     * @throws BadPaddingException [ellipsis]
     * @throws IllegalBlockSizeException [ellipsis]
     */
    public static String privateKeyDecrypt(String key, String cipherText) throws NoSuchAlgorithmException,
            InvalidKeySpecException, NoSuchPaddingException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
        PrivateKey privateKey = commonGetPrivatekeyByText(key);
        Cipher cipher = Cipher.getInstance(RSA);
        cipher.init(Cipher.DECRYPT_MODE, privateKey);
        byte[] result = cipher.doFinal(Base64.base64ToByteArray(cipherText));

        return new String(result);
    }

    // (common)获取密钥
    private static String[] commonKey(int size) throws NoSuchAlgorithmException {
        String [] keys = new String[2];

        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(RSA);
        keyPairGenerator.initialize(size);
        KeyPair keyPair = keyPairGenerator.generateKeyPair();
        RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) keyPair.getPrivate();
        RSAPublicKey rsaPublicKey = (RSAPublicKey) keyPair.getPublic();

        // 私钥
        keys[0] = Base64.byteArrayToBase64(rsaPrivateKey.getEncoded());
        // 公钥
        keys[1] = Base64.byteArrayToBase64(rsaPublicKey.getEncoded());

        return keys;
    }

    // (common)通过公钥文本获取公钥
    private static PublicKey commonGetPublickeyByText(String keyText)
            throws NoSuchAlgorithmException, InvalidKeySpecException {
        byte[] keyBytes = Base64.base64ToByteArray(keyText);
        X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance(RSA);
        return keyFactory.generatePublic(x509KeySpec);
    }

    // (common)通过私钥文本获取私钥
    private static PrivateKey commonGetPrivatekeyByText(String keyText)
            throws NoSuchAlgorithmException, InvalidKeySpecException {
        byte[] keyBytes = Base64.base64ToByteArray(keyText);
        PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance(RSA);
        return keyFactory.generatePrivate(pkcs8EncodedKeySpec);
    }

    /**
     * 签名
     * @param content 待签名内容
     * @param algorithm 签名算法
     * @param privateKey 私钥
     * @param charset 签名内容为中文时,你可能需要指定编码
     * @return 签名(String)
     * @throws NoSuchAlgorithmException 签名算法异常
     * @throws InvalidKeySpecException 密钥格式不正确
     * @throws InvalidKeyException 密钥异常
     * @throws SignatureException 签名异常
     * @throws UnsupportedEncodingException 可能你指定的编码不能正确读取你要签名的内容
     */
    public static String sign(String content, String algorithm, String privateKey, String charset)
            throws NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException, SignatureException,
            UnsupportedEncodingException {
        Signature signTool = Signature.getInstance(algorithm);
        PrivateKey key = commonGetPrivatekeyByText(privateKey);
        signTool.initSign(key);
        if (StringUtils.isEmpty(charset)) {
            signTool.update(content.getBytes());
        } else {
            signTool.update(content.getBytes(charset));
        }
        byte[] signBytes = signTool.sign();
        //二进制转十六进制
        return HexUtils._2_16(signBytes);
    }

    /**
     * 验证签名
     * @param content 签名的内容
     * @param signature 签名
     * @param algorithm 签名算法
     * @param publicKey 公钥
     * @param charset 当签名内容为中文时,你可能需要指定编码
     * @return 该签名是否和内容匹配
     * @throws NoSuchAlgorithmException 签名算法异常
     * @throws InvalidKeySpecException 密钥格式不正确
     * @throws InvalidKeyException 密钥异常
     * @throws SignatureException 签名异常
     * @throws UnsupportedEncodingException 可能你指定的编码不能正确读取你要签名的内容
     */
    public static boolean verify(String content, String signature, String algorithm, String publicKey, String charset)
            throws NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException, SignatureException,
            UnsupportedEncodingException {
        Signature signTool = Signature.getInstance(algorithm);
        PublicKey key = commonGetPublickeyByText(publicKey);
        signTool.initVerify(key);
        if (StringUtils.isEmpty(charset)) {
            signTool.update(content.getBytes());
        } else {
            signTool.update(content.getBytes(charset));
        }
        //十六进制转二进制
        return signTool.verify(HexUtils._16_2(signature));
    }
}