package com.zcxk.util;

import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import java.io.*;
import java.security.SecureRandom;
import java.util.Date;

/**
 * AES是一个迭代的、对称密钥分组的密码，它可以使用128、192和256位密钥长度，并且用128位分组长度加密和解密数据。
 * AES的加密过程：对数据的加密过程是通过把输入的明文和密钥由轮函数经N轮迭代来实现的，结尾轮与前N-1轮不同。
 * 前N-1轮依次进行S盒变换、行移位变换、列混淆变换和轮密钥加变换；第N轮与前N-1轮相比去掉了列混淆变换
 *  @author lihui
 *  @since 2021-03-23
 */
public class AESUtils {

    /** 密钥长度: 128, 192 or 256 */
    private static final int KEY_SIZE = 128;

    /** 加密/解密算法名称 */
    private static final String ALGORITHM = "AES";

    /** 随机数生成器（RNG）算法名称 */
    private static final String RNG_ALGORITHM = "SHA1PRNG";

    /** 允许最大的时间差 */
    private static final int MAX_TIME = 1;

    /**
     * 生成密钥对象
     */
    private static SecretKey generateKey(byte[] key) throws Exception {
        // 创建安全随机数生成器
        SecureRandom random = SecureRandom.getInstance(RNG_ALGORITHM);
        // 设置 密钥key的字节数组 作为安全随机数生成器的种子
        random.setSeed(key);

        // 创建 AES算法生成器
        KeyGenerator gen = KeyGenerator.getInstance(ALGORITHM);
        // 初始化算法生成器
        gen.init(KEY_SIZE, random);

        // 生成 AES密钥对象, 也可以直接创建密钥对象: return new SecretKeySpec(key, ALGORITHM);
        return gen.generateKey();
    }

    /**
     * 数据加密: 明文 -> 密文
     */
    public static byte[] encryptBye(byte[] plainBytes, byte[] key) throws Exception {
        // 生成密钥对象
        SecretKey secKey = generateKey(key);

        // 获取 AES 密码器
        Cipher cipher = Cipher.getInstance(ALGORITHM);
        // 初始化密码器（加密模型）
        cipher.init(Cipher.ENCRYPT_MODE, secKey);

        // 加密数据, 返回密文
        byte[] cipherBytes = cipher.doFinal(plainBytes);

        return cipherBytes;
    }

    /**
     * 数据加密: 明文 -> 密文
     */
    public static String encryptString(byte[] plainBytes, byte[] key) throws Exception {
        return new BASE64Encoder().encode(encryptBye(plainBytes, key));
    }


    /**
     * 数据解密: 密文 -> 明文
     */
    public static byte[] decryptByte(byte[] cipherBytes, byte[] key) throws Exception {
        // 生成密钥对象
        SecretKey secKey = generateKey(key);

        // 获取 AES 密码器
        Cipher cipher = Cipher.getInstance(ALGORITHM);
        // 初始化密码器（解密模型）
        cipher.init(Cipher.DECRYPT_MODE, secKey);

        // 解密数据, 返回明文
        byte[] plainBytes = cipher.doFinal(cipherBytes);

        return plainBytes;
    }

    /**
     * 数据解密: 密文 -> 明文
     */
    public static String decryptString(String cipherString, String key) throws Exception {
        return new String(decryptByte(new BASE64Decoder().decodeBuffer(cipherString), key.getBytes()), "utf-8");
    }

    /**
     * 数据验证:
     * 解密后的值再根据当前的时间进行加密
     * 是否等同于
     * 传过来的加密
     * @param  date 加密的时间
     * @param  text 明文
     * @param  encryptValue 加密后内容
     * @param  appSecret 秘钥
     */
    public static boolean verifyEncrypt(String encryptValue, String text, Long date, String appSecret) {
        try {
            // 获取当前时间
            long differ   = Long.parseLong(DatesUtil.formatDate(new Date(), "yyyyMMddHHmm")) - date;
            if (differ > MAX_TIME || differ < 0) {
                return false;
            }
            // 重新组装key，验证加密是否相同
            String encryptValue2 = encryptString((text + "_" + date).getBytes(), appSecret.getBytes());
            return encryptValue2.equals(encryptValue);
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * 加密文件: 明文输入 -> 密文输出
     */
    public static void encryptFile(File plainIn, File cipherOut, byte[] key) throws Exception {
        aesFile(plainIn, cipherOut, key, true);
    }

    /**
     * 解密文件: 密文输入 -> 明文输出
     */
    public static void decryptFile(File cipherIn, File plainOut, byte[] key) throws Exception {
        aesFile(plainOut, cipherIn, key, false);
    }

    /**
     * AES 加密/解密文件
     */
    private static void aesFile(File plainFile, File cipherFile, byte[] key, boolean isEncrypt) throws Exception {
        // 获取 AES 密码器
        Cipher cipher = Cipher.getInstance(ALGORITHM);
        // 生成密钥对象
        SecretKey secKey = generateKey(key);
        // 初始化密码器
        cipher.init(isEncrypt ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE, secKey);

        // 加密/解密数据
        InputStream in = null;
        OutputStream out = null;

        try {
            if (isEncrypt) {
                // 加密: 明文文件为输入, 密文文件为输出
                in = new FileInputStream(plainFile);
                out = new FileOutputStream(cipherFile);
            } else {
                // 解密: 密文文件为输入, 明文文件为输出
                in = new FileInputStream(cipherFile);
                out = new FileOutputStream(plainFile);
            }

            byte[] buf = new byte[1024];
            int len = -1;

            // 循环读取数据 加密/解密
            while ((len = in.read(buf)) != -1) {
                out.write(cipher.update(buf, 0, len));
            }
            out.write(cipher.doFinal());    // 最后需要收尾

            out.flush();

        } finally {
            close(in);
            close(out);
        }
    }

    private static void close(Closeable c) {
        if (c != null) {
            try {
                c.close();
            } catch (IOException e) {
                // nothing
            }
        }
    }

    public static void main(String[] args) throws  Exception{
        String appId      = "868083_1001_123456_202103241637";        // 原文内容
        String appSecret  = "123456";                    // AES加密/解密用的原始密码
        // 加密数据, 返回密文
        String cipherString = AESUtils.encryptString(appId.getBytes(), appSecret.getBytes());

        // 解密数据, 返回明文
        String plainString  = AESUtils.decryptString(cipherString, appSecret);

        System.out.println(cipherString);
        System.out.println(plainString);
        //System.out.println(AESUtils.verifyEncrypt(cipherString, plainString, appSecret));
    }
}
