package com.bcxin.ins.third.tyx.pingan.util;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import javax.crypto.*;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.nio.file.Files;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;

/**
 * 
 * <strong>注意：</strong>
 * <p>
 * 当密钥长度（KEY_SIZE）大于128时，代码会抛出Java.security. InvalidKeyException: Illegal key
 * size or default parameters
 * </p>
 * 
 * <p>
 * 原因：这种限制是因为美国对软件出口的控制
 * </p>
 * 
 * <p>
 * 解决办法：替换${java_home}/jre/lib/security/
 * 下面的local_policy.jar和US_export_policy.jar
 * </p>
 * 
 * <p>
 * jar包下载地址：
 * "http://www.oracle.com/technetwork/java/javase/downloads/jce-6-download-429243.html"
 * </p>
 * 
 * @author LIUXING583 2016年8月31日
 * 
 */
public class PaAesUtils {

    private static final String ALGORITHM = "AES";
    private static final String ALGORITHM_FOR_CIPHER = "AES";// AES/ECB/NoPadding

    private static final int KEY_SIZE = 256;
    private static final int CACHE_SIZE = 1024;

    private static final Log logger = LogFactory.getLog(PaAesUtils.class);

    public static void main(String[] args) throws Exception {
        String content = "AES加密、解密测试验证"
                + "{长文本数据验证、长文本数据验证、长文本数据验证、长文本数据验证、长文本数据验证、长文本数据验证、长文本数据验证、"
                + "长文本数据验证、长文本数据验证、长文本数据验证、长文本数据验证、长文本数据验证、长文本数据验证、长文本数据验证、"
                + "长文本数据验证、长文本数据验证、长文本数据验证、长文本数据验证、长文本数据验证、长文本数据验证、长文本数据验证}";
        String key = getSecretKey(PaMD5Util.MD5(content)
                + System.currentTimeMillis());
        System.out.println("密钥：" + key);
        // 加密
        System.out.println("加密前：" + content);
        byte[] encryptResult = encrypt(content.getBytes(), key);
        // base64编码后的密文用于传输
        String encryptStr = Base64Utils.encode(encryptResult);
        System.out.println("密文：" + encryptStr);
        // 解密
        byte[] decryptResult = decrypt(Base64Utils.decode(encryptStr), key);
        System.out.println("解密后：" + new String(decryptResult));

        // 文件加密
        // 文件加密
//        String sourceFilePath = "E:\\com_region_mapping.xls";
//        String encryptFilePath = "E:\\encryptFile.xls";
//        String decryptFilePath = "E:\\decryptFile.xls";
//        // 加密
//        encryptFile(key, sourceFilePath, encryptFilePath);
//        // 解密
//        decryptFile(key, encryptFilePath, decryptFilePath);
//        System.out.println("加密前：" + sourceFilePath);
//        System.out.println("加密后：" + encryptFilePath);
//        // System.out.println("加密后文件md5摘要：" + MD5Util.MD5(new
//        // File(encryptFilePath)));
//        System.out.println("解密后：" + decryptFilePath);
    }

    /**
     * <p>
     * 生成随机密钥
     * </p>
     * 
     * @return
     * @throws Exception
     */
    public static String getSecretKey() throws Exception {
        return getSecretKey(null);
    }

    /**
     * <p>
     * 生成密钥
     * </p>
     * 
     * @param seed
     *            密钥种子
     * @return
     * @throws Exception
     */
    public static String getSecretKey(String seed) throws Exception {
        KeyGenerator keyGenerator = KeyGenerator.getInstance(ALGORITHM);
        SecureRandom secureRandom;
        if (seed != null && !"".equals(seed)) {
            secureRandom = new SecureRandom(seed.getBytes());
        } else {
            secureRandom = new SecureRandom();
        }
        keyGenerator.init(KEY_SIZE, secureRandom);
        SecretKey secretKey = keyGenerator.generateKey();
        return Base64Utils.encode(secretKey.getEncoded());
    }

    /**
     * 将16进制转换为二进制
     * 
     * @param hexStr
     * @return
     */
    public static byte[] parseHexStr2Byte(String hexStr) {
        if (hexStr.length() < 1)
            return null;
        byte[] result = new byte[hexStr.length() / 2];
        for (int i = 0; i < hexStr.length() / 2; i++) {
            int high = Integer.parseInt(hexStr.substring(i * 2, i * 2 + 1), 16);
            int low = Integer.parseInt(hexStr.substring(i * 2 + 1, i * 2 + 2),
                    16);
            result[i] = (byte) (high * 16 + low);
        }
        return result;
    }

    /**
     * 将二进制转换成16进制
     * 
     * @param buf
     * @return
     */
    public static String parseByte2HexStr(byte buf[]) {
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < buf.length; i++) {
            String hex = Integer.toHexString(buf[i] & 0xFF);
            if (hex.length() == 1) {
                hex = '0' + hex;
            }
            sb.append(hex.toUpperCase());
        }
        return sb.toString();
    }

    /**
     * 加密
     * 
     * @param content
     *            需要加密的内容
     * @param key
     *            加密密码
     * @return
     */
    public static byte[] encrypt(byte[] content, String key) {
        try {
            Key k = toKey(Base64Utils.decode(key));
            byte[] raw = k.getEncoded();
            SecretKeySpec secretKeySpec = new SecretKeySpec(raw, ALGORITHM);
            Cipher cipher = Cipher.getInstance(ALGORITHM_FOR_CIPHER);
            cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
            return cipher.doFinal(content);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (NoSuchPaddingException e) {
            e.printStackTrace();
        } catch (InvalidKeyException e) {
            e.printStackTrace();
        } catch (IllegalBlockSizeException e) {
            e.printStackTrace();
        } catch (BadPaddingException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * AES 加密
     * 
     * @param sKey
     * @param sourceFilePath
     * @param destFilePath
     * @return

     */
    public static boolean encryptFile(String sKey, String sourceFilePath,
            String destFilePath)  {
        File encryptFile = null;
        File decryptfile = null;
        encryptFile = new File(sourceFilePath);
        if (!encryptFile.exists()) {
            throw new NullPointerException("Encrypt file is empty");
        }
        decryptfile = new File(destFilePath);
        if (decryptfile.exists()) {
            try {
                Files.delete(decryptfile.toPath());
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        Cipher cipher = null;
        try {
            decryptfile.createNewFile();
            Key k = toKey(Base64Utils.decode(sKey));
            byte[] raw = k.getEncoded();
            SecretKeySpec secretKeySpec = new SecretKeySpec(raw, ALGORITHM);
            cipher = Cipher.getInstance(ALGORITHM_FOR_CIPHER);
            cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
        } catch (NoSuchAlgorithmException e) {
            logger.error("AES 加密",e);
        } catch (NoSuchPaddingException e) {
            logger.error("AES 加密",e);
        } catch (InvalidKeyException e) {
            logger.error("AES 加密",e);
        } catch (IOException e) {
            logger.error("AES 加密",e);
        }
        // 以加密流写入文件
        try(InputStream inputStream = new FileInputStream(sourceFilePath);
            OutputStream outputStream = new FileOutputStream(destFilePath);
            CipherInputStream cipherInputStream = new CipherInputStream(inputStream, cipher);) {
            byte[] cache = new byte[1024];
            int nRead = 0;
            while ((nRead = cipherInputStream.read(cache)) != -1) {
                outputStream.write(cache, 0, nRead);
                outputStream.flush();
            }
            return true;
        } catch (Exception ex){
            logger.error("AES 加密",ex);
        }
        return false;
    }

    /**
     * AES 加密
     * 
     * @param sKey
     * @param inputStream
     * @param outputStream
     * @return
     */
    public static boolean encryptFile(String sKey, InputStream inputStream,
            OutputStream outputStream){
        Cipher cipher = null;
        try {
            Key k = toKey(Base64Utils.decode(sKey));
            byte[] raw = k.getEncoded();
            SecretKeySpec secretKeySpec = new SecretKeySpec(raw, ALGORITHM);
            cipher = Cipher.getInstance(ALGORITHM_FOR_CIPHER);
            cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
        } catch (NoSuchAlgorithmException e) {
            logger.error("AES 加密",e);
        } catch (NoSuchPaddingException e) {
            logger.error("AES 加密",e);
        } catch (InvalidKeyException e) {
            logger.error("AES 加密",e);
        } catch (IOException e) {
            logger.error("AES 加密",e);
        }
        // 以加密流写入文件
        try(CipherInputStream cipherInputStream = new CipherInputStream(inputStream, cipher);) {
            byte[] cache = new byte[1024];
            int nRead = 0;
            while ((nRead = cipherInputStream.read(cache)) != -1) {
                outputStream.write(cache, 0, nRead);
                outputStream.flush();
            }
            return true;
        }catch (Exception ex){
            logger.error("AES 加密",ex);
        }
        return false;
    }

    /**
     * AES 解密
     * 
     * @param sKey
     * @param sourceFilePath
     * @param destFilePath
     * @return
     */
    public static boolean decryptFile(String sKey, String sourceFilePath,
            String destFilePath) {
        File decryptFile = null;
        Cipher cipher = null;
        try {
            decryptFile = new File(destFilePath);
            decryptFile.createNewFile();
            Key k = toKey(Base64Utils.decode(sKey));
            byte[] raw = k.getEncoded();
            SecretKeySpec secretKeySpec = new SecretKeySpec(raw, ALGORITHM);
            cipher = Cipher.getInstance(ALGORITHM_FOR_CIPHER);
            cipher.init(Cipher.DECRYPT_MODE, secretKeySpec);
        }catch (InvalidKeyException e) {
            logger.error("AES 加密",e);
            return false;
        } catch (NoSuchAlgorithmException e) {
            logger.error("AES 加密",e);
            return false;
        } catch (NoSuchPaddingException e) {
            logger.error("AES 加密",e);
            return false;
        } catch (IOException e) {
            logger.error("AES 加密",e);
            return false;
        }

        try(InputStream inputStream = new FileInputStream(sourceFilePath);
            OutputStream outputStream = new FileOutputStream(decryptFile);
            CipherOutputStream cipherOutputStream = new CipherOutputStream(outputStream, cipher);) {
            byte[] buffer = new byte[1024];
            int r;
            while ((r = inputStream.read(buffer)) >= 0) {
                cipherOutputStream.write(buffer, 0, r);
            }
        } catch (IOException e) {
            logger.error("AES 加密",e);
            return false;
        }
        return true;
    }

    /**
     * <p>
     * 文件加密
     * </p>
     * 
     * @param key
     * @param sourceFilePath
     * @param destFilePath
     * @throws IOException
     */
    public static void encryptFileOld(String key, String sourceFilePath,
            String destFilePath) throws IOException {
        File sourceFile = new File(sourceFilePath);
        File destFile = new File(destFilePath);
        if (!sourceFile.exists() || !sourceFile.isFile()) {
            return;
        }
        if (!destFile.getParentFile().exists()) {
            destFile.getParentFile().mkdirs();
        }
        destFile.createNewFile();

        Cipher cipher = null;
        try {
            Key k = toKey(Base64Utils.decode(key));
            byte[] raw = k.getEncoded();
            SecretKeySpec secretKeySpec = new SecretKeySpec(raw, ALGORITHM);
            cipher = Cipher.getInstance(ALGORITHM_FOR_CIPHER);
            cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (NoSuchPaddingException e) {
            e.printStackTrace();
        } catch (InvalidKeyException e) {
            e.printStackTrace();
        }

        try(InputStream in = new FileInputStream(sourceFile);
            OutputStream out = new FileOutputStream(destFile);
            CipherInputStream cin = new CipherInputStream(in, cipher);) {

            byte[] cache = new byte[CACHE_SIZE];
            int nRead = 0;
            while ((nRead = cin.read(cache)) != -1) {
                out.write(cache, 0, nRead);
                out.flush();
            }
        } catch (IOException e) {
            // io异常仍需调用方处理
            throw e;
        } catch (Exception e) {
            // 其它异常包装为RuntimeException
//                throw new PafaRuntimeException("文件加密过程出现异常", e);
        }
    }

    /**
     * <p>
     * 文件解密
     * </p>
     * 
     * @param key
     * @param sourceFilePath
     * @param destFilePath
     * @throws Exception
     */
    public static void decryptFileOld(String key, String sourceFilePath,
            String destFilePath) throws IOException {
        File sourceFile = new File(sourceFilePath);
        File destFile = new File(destFilePath);
        if (!sourceFile.exists() || !sourceFile.isFile()) {
            return;
        }
        if (!destFile.getParentFile().exists()) {
            destFile.getParentFile().mkdirs();
        }
        destFile.createNewFile();
        Cipher cipher = null;
        try {
            Key k = toKey(Base64Utils.decode(key));
            byte[] raw = k.getEncoded();
            SecretKeySpec secretKeySpec = new SecretKeySpec(raw, ALGORITHM);
            cipher = Cipher.getInstance(ALGORITHM_FOR_CIPHER);
            cipher.init(Cipher.DECRYPT_MODE, secretKeySpec);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (NoSuchPaddingException e) {
            e.printStackTrace();
        } catch (InvalidKeyException e) {
            e.printStackTrace();
        }

        try(InputStream in = new FileInputStream(sourceFile);
            OutputStream out = new FileOutputStream(destFile);
            CipherOutputStream cout = new CipherOutputStream(out, cipher);) {

            byte[] cache = new byte[CACHE_SIZE];
            int nRead = 0;
            while ((nRead = in.read(cache)) != -1) {
                cout.write(cache, 0, nRead);
                cout.flush();
            }
        } catch (IOException e) {
            // io异常仍需调用方处理
            throw e;
        } catch (Exception e) {
            // 其它异常包装为RuntimeException
//                throw new PafaRuntimeException("文件解密过程出现异常", e);
        }
    }

    /**
     * 解密
     * 
     * @param content
     *            待解密内容
     * @param key
     *            解密密钥
     * @return
     */
    public static byte[] decrypt(byte[] content, String key) {
        try {
            Key k = toKey(Base64Utils.decode(key));
            byte[] raw = k.getEncoded();
            SecretKeySpec secretKeySpec = new SecretKeySpec(raw, ALGORITHM);
            Cipher cipher = Cipher.getInstance(ALGORITHM_FOR_CIPHER);
            cipher.init(Cipher.DECRYPT_MODE, secretKeySpec);
            return cipher.doFinal(content);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (NoSuchPaddingException e) {
            e.printStackTrace();
        } catch (InvalidKeyException e) {
            e.printStackTrace();
        } catch (IllegalBlockSizeException e) {
            e.printStackTrace();
        } catch (BadPaddingException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * <p>
     * 转换密钥
     * </p>
     * 
     * @param key
     * @return
     * @throws Exception
     */
    private static Key toKey(byte[] key) {
        SecretKey secretKey = new SecretKeySpec(key, ALGORITHM);
        return secretKey;
    }

}
