package cn.codesensi.util;

import org.bouncycastle.jce.provider.BouncyCastleProvider;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.security.Security;
import java.util.Base64;

public class AESUtil {

    private static final String ALGORITHM = "AES";
    // private static final String AES_CBC_PADDING = "AES/CBC/PKCS5PADDING";
    private static final String AES_CBC_PADDING = "AES/CBC/PKCS7PADDING";
    // private static final String AES_ECB_PADDING = "AES/ECB/PKCS5PADDING";
    private static final String AES_ECB_PADDING = "AES/ECB/PKCS7PADDING";

    // PKCS5PADDING必需
    static {
        Security.addProvider(new BouncyCastleProvider());
    }

    /**
     * 生成密钥
     *
     * @param size 密钥位数,可选128/192/256,越大越安全,但效率越低
     * @param seed 加密强随机数种子,同一个种子生成的密钥相同
     * @return 密钥
     */
    public static byte[] getKey(Integer size, byte[] seed) throws Exception {
        KeyGenerator kg = KeyGenerator.getInstance(ALGORITHM);
        // 加密强随机数
        SecureRandom sr = new SecureRandom();
        sr.setSeed(seed);
        kg.init(size, sr);
        SecretKey sk = kg.generateKey();
        return sk.getEncoded();
    }

    /**
     * Aes加密(CBC工作模式)
     *
     * @param key  密钥,长度可选16/24/32
     * @param iv   初始化向量,长度必须等于16
     * @param data 明文
     * @return 密文
     */
    public static byte[] encodeCBC(byte[] key, byte[] iv, byte[] data) throws Exception {
        // 获取SecretKey对象
        SecretKeySpec sks = new SecretKeySpec(key, ALGORITHM);
        // 获取指定转换的密码对象Cipher(参数:算法/工作模式/填充模式)
        Cipher cipher = Cipher.getInstance(AES_CBC_PADDING);
        // 初始化向量
        IvParameterSpec ips = new IvParameterSpec(iv);
        // 用密钥和一组算法参数规范初始化此Cipher对象(加密模式)
        cipher.init(Cipher.ENCRYPT_MODE, sks, ips);
        // 执行加密操作
        return cipher.doFinal(data);
    }

    /**
     * Aes解密(ECB工作模式)
     *
     * @param key  密钥,长度可选16/24/32
     * @param iv   初始化向量,长度必须等于16
     * @param data 密文
     * @return 明文
     */
    public static byte[] decodeCBC(byte[] key, byte[] iv, byte[] data) throws Exception {
        // 获取SecretKey对象
        SecretKeySpec sks = new SecretKeySpec(key, ALGORITHM);
        // 获取指定转换的密码对象Cipher(参数:算法/工作模式/填充模式)
        Cipher cipher = Cipher.getInstance(AES_CBC_PADDING);
        // 初始化向量
        IvParameterSpec ips = new IvParameterSpec(iv);
        // 用密钥和一组算法参数规范初始化此Cipher对象(解密模式)
        cipher.init(Cipher.DECRYPT_MODE, sks, ips);
        // 执行解密操作
        return cipher.doFinal(data);
    }

    /**
     * Aes加密(ECB工作模式),不要IV
     *
     * @param key  密钥,长度可选16/24/32
     * @param data 明文
     * @return 密文
     */
    public static byte[] encodeECB(byte[] key, byte[] data) throws Exception {
        // 获取SecretKey对象
        SecretKeySpec sks = new SecretKeySpec(key, ALGORITHM);
        // 获取指定转换的密码对象Cipher(参数:算法/工作模式/填充模式)
        Cipher cipher = Cipher.getInstance(AES_ECB_PADDING);
        // 用密钥和一组算法参数规范初始化此Cipher对象(加密模式)
        cipher.init(Cipher.ENCRYPT_MODE, sks);
        // 执行加密操作
        return cipher.doFinal(data);
    }

    /**
     * Aes解密(ECB工作模式),不要IV
     *
     * @param key  密钥,长度可选16/24/32
     * @param data 密文
     * @return 明文
     */
    public static byte[] decodeECB(byte[] key, byte[] data) throws Exception {
        // 获取SecretKey对象
        SecretKeySpec sks = new SecretKeySpec(key, ALGORITHM);
        // 获取指定转换的密码对象Cipher(参数:算法/工作模式/填充模式)
        Cipher cipher = Cipher.getInstance(AES_ECB_PADDING);
        // 用密钥和一组算法参数规范初始化此Cipher对象(解密模式)
        cipher.init(Cipher.DECRYPT_MODE, sks);
        // 执行解密操作
        return cipher.doFinal(data);
    }

    public static void main(String[] args) throws Exception {

        // 定义原始数据
        String seed = "abcdefg123";// 加密强随机数种子,同一个种子生成的密钥相同
        byte[] seedBytes = seed.getBytes(StandardCharsets.UTF_8);
        System.out.println("用于生成密钥的种子:" + seed);

        Integer size = 256;// 密钥位数 可选128、192、256 越大越安全,但效率越低
        System.out.println("密钥位数:" + size);
        String iv = "12345678abcdefgh";// 初始化向量,长度必须等于16
        byte[] ivBytes = iv.getBytes(StandardCharsets.UTF_8);
        System.out.println("CBC加密偏移量:" + iv);

        String original = "让我们测试一下叭";
        byte[] data = original.getBytes(StandardCharsets.UTF_8);
        System.out.println("待加密的数据:" + original);
        System.out.println();

        System.out.println("------------------生成密钥------------------");
        byte[] keyBytes = getKey(size, seedBytes);
        System.out.println("生成的密钥转Base64字符串:" + Base64.getEncoder().encodeToString(keyBytes));
        System.out.println();

        System.out.println("------------------AES之ECB加密转base64------------------");
        byte[] encodeECBBytes = encodeECB(keyBytes, data);
        String encodeECB = Base64.getEncoder().encodeToString(encodeECBBytes);
        System.out.println("ECB加密后转Base64的数据:" + encodeECB);
        System.out.println();

        System.out.println("------------------AES之ECB解密------------------");
        // 将Base64编码后的解密结果转换回去
        byte[] decodeECBBytes = Base64.getDecoder().decode(encodeECB);
        byte[] decodeECB = decodeECB(keyBytes, decodeECBBytes);
        System.out.println("ECB解密后转字符串数据:" + new String(decodeECB, StandardCharsets.UTF_8));
        System.out.println();

        System.out.println("------------------AES之CBC加密转base64------------------");

        byte[] encodeCBCByte = encodeCBC(keyBytes, ivBytes, data);
        String encodeCBC = Base64.getEncoder().encodeToString(encodeCBCByte);
        System.out.println("CBC加密后转Base64的数据:" + encodeCBC);
        System.out.println();

        System.out.println("------------------AES之CBC解密------------------");
        // 将Base64编码后的解密结果转换回去
        byte[] decodeCCBBytes = Base64.getDecoder().decode(encodeCBC);
        byte[] decodeStr = decodeCBC(keyBytes, ivBytes, decodeCCBBytes);
        System.out.println("CBC解密后转字符串数据:" + new String(decodeStr, StandardCharsets.UTF_8));
    }
}