package com.cusc.nirvana.user.rnr.fp.util;

import com.cusc.nirvana.user.util.CuscStringUtils;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.pqc.math.linearalgebra.ByteUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import java.security.Security;
import java.util.Base64;

/**
 * Description: 国密SM4
 * <br />
 * BC库从1.59版本开始已经基本实现了国密算法（SM2、SM3、SM4）
 * CreateDate 2021-11-17 14:37
 *
 * @author yuyi
 **/
public class RnrDataSm4Util {

    private static final Logger LOGGER = LoggerFactory.getLogger(RnrDataSm4Util.class);

    //算法名称
    public static final String ALGORITHM_NAME = "SM4";
    //ECB P5填充
    public static final String ALGORITHM_NAME_ECB_PADDING = "SM4/ECB/PKCS5Padding";
    //CBC P5填充
    public static final String ALGORITHM_NAME_CBC_PADDING = "SM4/CBC/PKCS5Padding";
    //密钥长度
    public static final int DEFAULT_KEY_SIZE = 128;

    static {
        Security.addProvider(new BouncyCastleProvider());
    }

    private RnrDataSm4Util() {
    }

    /**
     * 获取密钥
     *
     * @return byte
     * @throws NoSuchAlgorithmException
     * @throws NoSuchProviderException
     */
    public static byte[] generateKey() throws NoSuchAlgorithmException, NoSuchProviderException {
        return generateKey(DEFAULT_KEY_SIZE);
    }

    /**
     * 获取指定长度密钥
     *
     * @param keySize 密钥的长度
     * @return byte
     * @throws NoSuchAlgorithmException
     * @throws NoSuchProviderException
     */
    public static byte[] generateKey(int keySize) throws NoSuchAlgorithmException, NoSuchProviderException {
        KeyGenerator kg = KeyGenerator.getInstance(ALGORITHM_NAME, BouncyCastleProvider.PROVIDER_NAME);
        kg.init(keySize, new SecureRandom());
        return kg.generateKey().getEncoded();
    }

    /**
     * ECB P5填充加密
     *
     * @param key  密钥
     * @param data 明文数据
     * @return byte
     * @throws Exception
     */
    public static byte[] encryptEcbPaddingByte(byte[] key, byte[] data)
            throws Exception {
        Cipher cipher = generateEcbCipher(ALGORITHM_NAME_ECB_PADDING, Cipher.ENCRYPT_MODE, key);
        return cipher.doFinal(data);
    }

    /**
     * Description: ecb加密
     * <br />
     * CreateDate 2021-11-17 15:53:34
     *
     * @author yuyi
     **/
    public static String encryptEcbPadding(String key, String data) {
        return encryptEcbPadding(key, data, false, null);
    }

    /**
     * Description: ecb加密，可以指定混淆
     * <br />
     * CreateDate 2021-11-17 15:53:34
     *
     * @author yuyi
     **/
    public static String encryptEcbPadding(String key, String data, boolean isHex, String charset) {
        if (CuscStringUtils.isEmpty(data)) {
            return null;
        }
        try {
            byte[] keyBytes = getKeyBytes(key, isHex);
            byte[] dataBytes = getDataBytes(data, charset);
            return ByteUtils.toHexString(encryptEcbPaddingByte(keyBytes, dataBytes));
        } catch (Exception e) {
            LOGGER.error("Sm4Util.encryptEcbPadding error ! ", e);
            return null;
        }
    }

    /**
     * Description: ecb加密，可以指定混淆
     * <br />
     * CreateDate 2021-11-17 15:53:34
     *
     * @author yuyi
     **/
    public static byte[] encryptEcbPaddingBytes(String key, byte[] dataBytes, boolean isHex) {
        if (dataBytes == null) {
            return null;
        }
        try {
            byte[] keyBytes = getKeyBytes(key, isHex);
            return encryptEcbPaddingByte(keyBytes, dataBytes);
        } catch (Exception e) {
            LOGGER.error("Sm4Util.encryptEcbPaddingBytes error ! ", e);
            return null;
        }
    }

    /**
     * Description: ecb加密
     * <br />
     * CreateDate 2021-11-17 15:53:34
     *
     * @author yuyi
     **/
    public static String encryptEcbPaddingBase64(String key, String data) {
        return encryptEcbPaddingBase64(key, data, false, null);
    }

    /**
     * Description: ecb加密，可以指定混淆
     * <br />
     * CreateDate 2021-11-17 15:53:34
     *
     * @author yuyi
     **/
    public static String encryptEcbPaddingBase64(String key, String data, boolean isHex, String charset) {
        if (CuscStringUtils.isEmpty(data)) {
            return null;
        }
        try {
            byte[] keyBytes = getKeyBytes(key, isHex);
            byte[] dataBytes = getDataBytes(data, charset);
            return base64Encoder(encryptEcbPaddingByte(keyBytes, dataBytes));
        } catch (Exception e) {
            LOGGER.error("Sm4Util.encryptEcbPaddingBase64 error ! ", e);
            return null;
        }
    }

    /**
     * ECB P5填充解密
     *
     * @param key        密钥
     * @param cipherText 加密后的数据
     * @return byte
     * @throws Exception
     */
    public static byte[] decryptEcbPaddingByte(byte[] key, byte[] cipherText)
            throws Exception {
        Cipher cipher = generateEcbCipher(ALGORITHM_NAME_ECB_PADDING, Cipher.DECRYPT_MODE, key);
        return cipher.doFinal(cipherText);
    }

    /**
     * Description: ecb解密
     * <br />
     * CreateDate 2021-11-17 15:53:34
     *
     * @author yuyi
     **/
    public static String decryptEcbPadding(String key, String data) {
        return decryptEcbPadding(key, data, false, null);
    }

    /**
     * Description: ecb解密，可以指定混淆
     * <br />
     * CreateDate 2021-11-17 15:53:34
     *
     * @author yuyi
     **/
    public static String decryptEcbPadding(String key, String data, boolean isHex, String charset) {
        if (CuscStringUtils.isEmpty(data)) {
            return null;
        }
        try {
            byte[] keyBytes = getKeyBytes(key, isHex);
            byte[] decrypted = decryptEcbPaddingByte(keyBytes, ByteUtils.fromHexString(data));
            if (CuscStringUtils.isNotEmpty(charset)) {
                return new String(decrypted, charset);
            }
            return new String(decrypted, StandardCharsets.UTF_8);
        } catch (Exception e) {
            LOGGER.error("Sm4Util.decryptEcbPaddingBase64 error ! ", e);
            return null;
        }
    }

    /**
     * Description: ecb解密，可以指定混淆
     * <br />
     * CreateDate 2021-11-17 15:53:34
     *
     * @author yuyi
     **/
    public static byte[] decryptEcbPaddingBytes(String key, byte[] dataBytes, boolean isHex) {
        if (dataBytes == null) {
            return null;
        }
        try {
            byte[] keyBytes = getKeyBytes(key, isHex);
            return decryptEcbPaddingByte(keyBytes, dataBytes);
        } catch (Exception e) {
            LOGGER.error("Sm4Util.decryptEcbPaddingBytes error ! ", e);
            return null;
        }
    }

    /**
     * Description: ecb解密
     * <br />
     * CreateDate 2021-11-17 15:53:34
     *
     * @author yuyi
     **/
    public static String decryptEcbPaddingBase64(String key, String data) {
        return decryptEcbPaddingBase64(key, data, false, null);
    }

    /**
     * Description: ecb解密，可以指定混淆
     * <br />
     * CreateDate 2021-11-17 15:53:34
     *
     * @author yuyi
     **/
    public static String decryptEcbPaddingBase64(String key, String data, boolean isHex, String charset) {
        if (CuscStringUtils.isEmpty(data)) {
            return null;
        }
        try {
            byte[] keyBytes = getKeyBytes(key, isHex);
            byte[] decrypted = decryptEcbPaddingByte(keyBytes, base64Decoder(data));
            if (CuscStringUtils.isNotEmpty(charset)) {
                return new String(decrypted, charset);
            }
            return new String(decrypted, StandardCharsets.UTF_8);
        } catch (Exception e) {
            LOGGER.error("Sm4Util.decryptEcbPaddingBase64 error ! ", e);
            return null;
        }
    }

    /**
     * CBC P5填充加密
     *
     * @param key  密钥
     * @param iv   偏移量
     * @param data 明文数据
     * @return byte
     * @throws InvalidKeyException
     * @throws NoSuchAlgorithmException
     * @throws NoSuchProviderException
     * @throws NoSuchPaddingException
     * @throws IllegalBlockSizeException
     * @throws BadPaddingException
     * @throws InvalidAlgorithmParameterException
     */
    public static byte[] encryptCbcPaddingByte(byte[] key, byte[] iv, byte[] data)
            throws InvalidKeyException, NoSuchAlgorithmException, NoSuchProviderException, NoSuchPaddingException,
            IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException {
        Cipher cipher = generateCbcCipher(ALGORITHM_NAME_CBC_PADDING, Cipher.ENCRYPT_MODE, key, iv);
        return cipher.doFinal(data);
    }

    /**
     * Description: cbc加密
     * <br />
     * CreateDate 2021-11-17 15:53:34
     *
     * @author yuyi
     **/
    public static String encryptCbcPaddingBase64(String key, String data, String iv) {
        return encryptCbcPaddingBase64(key, data, iv, false, null);
    }

    /**
     * Description: cbc加密，可以指定混淆
     * <br />
     * CreateDate 2021-11-17 15:53:34
     *
     * @author yuyi
     **/
    public static String encryptCbcPaddingBase64(String key, String data, String iv, boolean isHex, String charset) {
        if (CuscStringUtils.isEmpty(data)) {
            return null;
        }
        try {
            byte[] keyBytes = getKeyBytes(key, isHex);
            byte[] ivBytes = getKeyBytes(iv, isHex);
            byte[] dataBytes = getDataBytes(data, charset);
            byte[] encrypted = encryptCbcPaddingByte(keyBytes, ivBytes, dataBytes);
            return base64Encoder(encrypted);
        } catch (Exception e) {
            LOGGER.error("Sm4Util.encryptCbcPaddingBase64 error ! ", e);
            return null;
        }
    }

    /**
     * CBC P5填充解密
     *
     * @param key        密钥
     * @param iv         偏移量
     * @param cipherText 加密数据
     * @return byte
     * @throws IllegalBlockSizeException
     * @throws BadPaddingException
     * @throws InvalidKeyException
     * @throws NoSuchAlgorithmException
     * @throws NoSuchProviderException
     * @throws NoSuchPaddingException
     * @throws InvalidAlgorithmParameterException
     */
    public static byte[] decryptCbcPaddingByte(byte[] key, byte[] iv, byte[] cipherText)
            throws IllegalBlockSizeException, BadPaddingException, InvalidKeyException, NoSuchAlgorithmException,
            NoSuchProviderException, NoSuchPaddingException, InvalidAlgorithmParameterException {
        Cipher cipher = generateCbcCipher(ALGORITHM_NAME_CBC_PADDING, Cipher.DECRYPT_MODE, key, iv);
        return cipher.doFinal(cipherText);
    }

    /**
     * Description: cbc解密
     * <br />
     * CreateDate 2021-11-17 15:53:34
     *
     * @author yuyi
     **/
    public static String decryptCbcPaddingBase64(String key, String data, String iv) {
        return decryptCbcPaddingBase64(key, data, iv, false, null);
    }

    /**
     * Description: cbc解密，可以指定混淆
     * <br />
     * CreateDate 2021-11-17 15:53:34
     *
     * @author yuyi
     **/
    public static String decryptCbcPaddingBase64(String key, String data, String iv, boolean isHex,
                                                 String charset) {
        if (CuscStringUtils.isEmpty(data)) {
            return null;
        }
        try {
            byte[] keyBytes = getKeyBytes(key, isHex);
            byte[] ivBytes = getKeyBytes(iv, isHex);
            byte[] decrypted = decryptCbcPaddingByte(keyBytes, ivBytes, base64Decoder(data));
            if (CuscStringUtils.isNotEmpty(charset)) {
                return new String(decrypted, charset);
            }
            return new String(decrypted, StandardCharsets.UTF_8);
        } catch (Exception e) {
            LOGGER.error("Sm4Util.decryptCbcPaddingHex error ! ", e);
            return null;
        }
    }

    /**
     * ECB P5填充加解密Cipher初始化
     *
     * @param algorithmName 算法名称
     * @param mode          1 加密  2解密
     * @param key           密钥
     * @return Cipher
     * @throws NoSuchAlgorithmException
     * @throws NoSuchProviderException
     * @throws NoSuchPaddingException
     * @throws InvalidKeyException
     */
    private static Cipher generateEcbCipher(String algorithmName, int mode, byte[] key)
            throws NoSuchAlgorithmException, NoSuchProviderException, NoSuchPaddingException, InvalidKeyException {
        Cipher cipher = Cipher.getInstance(algorithmName, BouncyCastleProvider.PROVIDER_NAME);
        Key sm4Key = new SecretKeySpec(key, ALGORITHM_NAME);
        cipher.init(mode, sm4Key);
        return cipher;
    }

    /**
     * CBC P5填充加解密Cipher初始化
     *
     * @param algorithmName 算法名称
     * @param mode          1 加密  2解密
     * @param key           密钥
     * @param iv            偏移量
     * @return Cipher
     * @throws InvalidKeyException
     * @throws InvalidAlgorithmParameterException
     * @throws NoSuchAlgorithmException
     * @throws NoSuchProviderException
     * @throws NoSuchPaddingException
     */
    private static Cipher generateCbcCipher(String algorithmName, int mode, byte[] key, byte[] iv)
            throws InvalidKeyException, InvalidAlgorithmParameterException, NoSuchAlgorithmException,
            NoSuchProviderException, NoSuchPaddingException {
        Cipher cipher = Cipher.getInstance(algorithmName, BouncyCastleProvider.PROVIDER_NAME);
        Key sm4Key = new SecretKeySpec(key, ALGORITHM_NAME);
        IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
        cipher.init(mode, sm4Key, ivParameterSpec);
        return cipher;
    }

    //-------------私有方法区----------------------

    /**
     * Description: base64编码
     * <br />
     * CreateDate 2021-11-17 15:54:52
     *
     * @author yuyi
     **/
    private static String base64Encoder(byte[] encrypted) {
        return Base64.getEncoder().encodeToString(encrypted);
    }

    /**
     * Description: base64解码
     * <br />
     * CreateDate 2021-11-17 17:44:13
     *
     * @author yuyi
     **/
    private static byte[] base64Decoder(String encrypted) {
        return Base64.getDecoder().decode(encrypted);
    }

    /**
     * Description: 获取密钥的byte数组
     * <br />
     * CreateDate 2021-11-17 17:44:13
     *
     * @author yuyi
     **/
    private static byte[] getKeyBytes(String key, boolean isHex) {
        byte[] keyBytes;
        if (isHex) {
            keyBytes = ByteUtils.fromHexString(key);
        } else {
            keyBytes = key.getBytes();
        }
        System.out.println(keyBytes.length);
        return keyBytes;
    }

    /**
     * Description: 获取数据的byte数组
     * <br />
     * CreateDate 2021-11-17 17:44:13
     *
     * @author yuyi
     **/
    private static byte[] getDataBytes(String data, String charset) throws UnsupportedEncodingException {
        byte[] dataBytes;
        if (CuscStringUtils.isNotEmpty(charset)) {
            dataBytes = data.getBytes(charset);
        } else {
            dataBytes = data.getBytes(StandardCharsets.UTF_8);
        }
        return dataBytes;
    }


    public static void main(String[] args) {
        // 自定义的32位16进制密钥
        //String key = "DBBAA833E6A371D1B26820D2C763E882";
        //String test="广西灌阳县文市镇会湘村湖坪丘屯011号";

        String sm4Key = "7faf0abf9e6c4aa0aa04eeccf3110e37";
        //String sm4Key = "9f14c36b0af06878";
        String content = "广西灌阳县文市镇会湘村湖坪丘屯011号";
        //System.out.println("base64:"+RnrDataSm4Util.encryptEcbPaddingBase64(sm4Key, content));

        String mw = RnrDataSm4Util.encryptEcbPadding(sm4Key, content, true, null);
        System.out.println("hex:" + mw);

        System.out.println("hex mw:" + RnrDataSm4Util.decryptEcbPadding(sm4Key, mw, true, null));
        System.out.println("313e527d850548249f14c36b0af06878".substring(16));
    }
}
