/*
 * *************************************************
 * Copyright (c) 2018 QMTH. All Rights Reserved.
 * Created by Deason on 2018-07-12 15:31:10.
 * *************************************************
 */

package cn.com.qmth.examcloud.core.questions.base.converter.utils;

import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.GeneralSecurityException;
import java.security.SecureRandom;

/**
 * 支持DES/AES对称加密的工具类
 */
public class Cryptogram {
    private static final Logger log = LoggerFactory.getLogger(Cryptogram.class);
    private static final String AES = "AES";
    private static final String AES_CBC = "AES/CBC/PKCS5Padding";
    private static final String IV_SPEC = "0102030405060708";
    private static final int DEFAULT_AES_KEY_SIZE = 128;
    private static final int DEFAULT_IV_SIZE = 16;
    private static SecureRandom random = new SecureRandom();

    /**
     * 使用AES加密字符串
     *
     * @param str 待加密字符串
     * @param key 符合AES要求的密钥
     * @return
     */
    public static String aesEncrypt(String str, String key) {
        if (str == null || key == null) {
            return null;
        }
        try {
            byte[] strBytes = str.getBytes("UTF-8");
            byte[] keyBytes = key.getBytes("ASCII");
//            byte[] ivBytes = IV_SPEC.getBytes();//默认向量值
//            byte[] bytes = aes(strBytes, keyBytes, ivBytes, Cipher.ENCRYPT_MODE);
            byte[] bytes = aes(strBytes, keyBytes, Cipher.ENCRYPT_MODE);
            return byteToHex(bytes);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return null;
        }
    }

    /**
     * 使用AES解密字符串
     *
     * @param str 待解密字符串
     * @param key 符合AES要求的密钥
     * @return
     */
    public static String aesDecrypt(String str, String key) {
        if (str == null || key == null) {
            return null;
        }
        try {
            byte[] strBytes = hexToByte(str);
            byte[] keyBytes = key.getBytes("ASCII");
//            byte[] ivBytes = IV_SPEC.getBytes();//默认向量值
//            byte[] decryptResult = aes(strBytes, keyBytes, ivBytes, Cipher.DECRYPT_MODE);
            byte[] decryptResult = aes(strBytes, keyBytes, Cipher.DECRYPT_MODE);
            return new String(decryptResult);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return null;
        }
    }

    /**
     * 使用AES加密或解密无编码的原始字节数组, 返回无编码的字节数组结果
     *
     * @param input    原始字节数组
     * @param keyBytes 符合AES要求的密钥
     * @param mode     Cipher.ENCRYPT_MODE 或 Cipher.DECRYPT_MODE
     */
    public static byte[] aes(byte[] input, byte[] keyBytes, int mode) {
        try {
            SecretKey secretKey = new SecretKeySpec(keyBytes, AES);
            Cipher cipher = Cipher.getInstance(AES);
            cipher.init(mode, secretKey);
            return cipher.doFinal(input);
        } catch (BadPaddingException e) {
            log.error("加解密密匙无效：" + e.getMessage());
            return null;
        } catch (Exception e) {
            log.error("加解密错误：" + e.getMessage());
            return null;
        }
    }

    /**
     * 使用AES加密或解密无编码的原始字节数组, 返回无编码的字节数组结果
     *
     * @param input    原始字节数组
     * @param keyBytes 符合AES要求的密钥
     * @param ivBytes  初始向量
     * @param mode     Cipher.ENCRYPT_MODE 或 Cipher.DECRYPT_MODE
     */
    public static byte[] aes(byte[] input, byte[] keyBytes, byte[] ivBytes, int mode) {
        try {
            SecretKey secretKey = new SecretKeySpec(keyBytes, AES);
            Cipher cipher = Cipher.getInstance(AES_CBC);
            IvParameterSpec ivSpec = new IvParameterSpec(ivBytes);
            cipher.init(mode, secretKey, ivSpec);
            return cipher.doFinal(input);
        } catch (BadPaddingException e) {
            log.error("加解密密匙无效：" + e.getMessage());
            return null;
        } catch (Exception e) {
            log.error("加解密错误：" + e.getMessage());
            return null;
        }
    }

    /**
     * 生成AES密钥,返回字节数组, 默认长度为128位(16字节)
     */
    public static byte[] generateAesKey() {
        try {
            KeyGenerator keyGenerator = KeyGenerator.getInstance(AES);
            keyGenerator.init(DEFAULT_AES_KEY_SIZE);
            SecretKey secretKey = keyGenerator.generateKey();
            return secretKey.getEncoded();
        } catch (GeneralSecurityException e) {
            log.error(e.getMessage(), e);
            return null;
        }
    }

    /**
     * 生成随机向量,默认大小为cipher.getBlockSize(), 16字节
     */
    public static byte[] generateIV() {
        byte[] bytes = new byte[DEFAULT_IV_SIZE];
        random.nextBytes(bytes);
        return bytes;
    }

    public static String byteToHex(byte[] bytes) {
        StringBuilder hex = new StringBuilder();
        for (int n = 0; n < bytes.length; n++) {
            String tmp = (Integer.toHexString(bytes[n] & 0XFF));
            if (tmp.length() == 1) {
                hex.append("0").append(tmp);
            } else {
                hex.append(tmp);
            }
        }
        return hex.toString().toUpperCase();
    }

    public static byte[] hexToByte(String strHex) {
        if (strHex == null) {
            return null;
        }
        int size = strHex.length();
        if (size % 2 == 1) {
            return null;
        }
        byte[] bytes = new byte[size / 2];
        for (int i = 0; i != size / 2; i++) {
            String s = strHex.substring(i * 2, i * 2 + 2);
            bytes[i] = (byte) Integer.parseInt(s, 16);
        }
        return bytes;
    }

    /**
     * 将字符串MD5后，再从MD5后的字符串里截取前后8位，合成16位KEY(转大写)
     */
    public static String convertKey(String password) {
        if (password == null) {
            return null;
        }
        try {
            String md5 = md5Encrypt(password);
            //MD5后字符串长度默认32位
            String start = md5.substring(0, 8);
            String end = md5.substring(md5.length() - 8, md5.length());
            return (start + end).toUpperCase();
        } catch (Exception e) {
            log.error(e.getMessage());
            return null;
        }
    }

    /**
     * 将密码MD5两次后返回
     */
    public static String md5Key(String password) {
        if (password == null) {
            return null;
        }
        try {
            String md5 = md5Encrypt(password);
            return md5Encrypt(md5);
        } catch (Exception e) {
            log.error(e.getMessage());
            return null;
        }
    }

    public static String md5Encrypt(String str) {
        if (StringUtils.isEmpty(str)) {
            return str;
        }
        return DigestUtils.md5Hex(str);
    }

}