package com.bootdo.datas.tools.gm;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.security.*;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;

import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;

import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.util.encoders.Hex;

/**
 * HASH||ENVELOPE||CIPHER
 */
public class FileCryptTool {
    public static final boolean DEBUG_CRYPT = false;

    /**
     * @param certFilePath 证书路径
     * @param srcFilePath  原文件路径
     * @param encFilePath  加密文件路径
     */
    public static void encryptFile(String certFilePath, String srcFilePath, String encFilePath) {
        RandomAccessFile srcRAF = null;
        RandomAccessFile dstRAF = null;
        try {
            srcRAF = new RandomAccessFile(srcFilePath, "r");
            dstRAF = new RandomAccessFile(encFilePath, "rw");
            long srcLength = srcRAF.length();
            if (srcLength > Integer.MAX_VALUE) {
                return;
            }
            byte[] srcData = new byte[(int) srcLength];
            srcRAF.readFully(srcData);

            //1.保存证书
            int certLen = 0;
            dstRAF.write(toBytesLittleEndian(certLen));//写入证书长度

            //2.保存密钥信封
            //2.a生成16字节随机数
            SecureRandom srand = new SecureRandom();
            byte[] symmKey = new byte[16];
            srand.nextBytes(symmKey);
            if (DEBUG_CRYPT) {
                System.out.println("文件密钥(加密)：" + Hex.toHexString(symmKey));
            }

            //2.b使用服务器证书加密16字节随机数
            X509Certificate certificate = SM2CertUtil.getX509Certificate(certFilePath);
            BCECPublicKey bcecPublicKey = SM2CertUtil.getBCECPublicKey(certificate);
            byte[] symmKeyCipher = SM2Util.encrypt(bcecPublicKey, symmKey);
            if (DEBUG_CRYPT) {
                System.out.println("文件密钥密文(加密)：" + Hex.toHexString(symmKeyCipher));
            }
            dstRAF.write(toBytesLittleEndian(symmKeyCipher.length - 1));//写入密钥信封长度
            dstRAF.write(symmKeyCipher, 1, symmKeyCipher.length - 1);//写入密钥信封

            //3.加密文件
            byte[] encData = SM4Util.encrypt_Cbc_Padding(symmKey, symmKey, srcData);
            dstRAF.write(toBytesLittleEndian(encData.length));
            dstRAF.write(encData);

            //4.保存签名值
            int signatureLen = 0;
            dstRAF.write(toBytesLittleEndian(signatureLen));
        } catch (IOException e) {
            e.printStackTrace();
        } catch (CertificateException e) {
            e.printStackTrace();
        } catch (NoSuchProviderException e) {
            e.printStackTrace();
        } catch (InvalidCipherTextException e) {
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (InvalidKeyException e) {
            e.printStackTrace();
        } catch (InvalidAlgorithmParameterException e) {
            e.printStackTrace();
        } catch (NoSuchPaddingException e) {
            e.printStackTrace();
        } catch (BadPaddingException e) {
            e.printStackTrace();
        } catch (IllegalBlockSizeException e) {
            e.printStackTrace();
        } finally {
            try {
                if (srcRAF != null)
                    srcRAF.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if (dstRAF != null)
                    dstRAF.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * @param priKeyPath  私钥文件路径
     * @param encFilePath 加密文件路径
     * @param decFilePath 解密文件路径
     */
    public static void decryptFile(String priKeyPath, String encFilePath, String decFilePath) {
        RandomAccessFile srcRAF = null;
        RandomAccessFile dstRAF = null;
        try {
            srcRAF = new RandomAccessFile(encFilePath, "r");
            dstRAF = new RandomAccessFile(decFilePath, "rw");
            //0.验证签名

            //1.读取证书
            //Read 4B Length Buffer
            byte[] rlBuffer = new byte[4];
            srcRAF.read(rlBuffer);
            int certLen = toIntLittleEndian(rlBuffer);
            if (certLen > 0) {

            }

            //2.读取密钥信封
            byte[] symmKey = null;
            srcRAF.read(rlBuffer);
            int kcLen = toIntLittleEndian(rlBuffer);//读取密钥信封长度
            if (kcLen > 0) {
                //2.a读取密钥信封数据
                byte[] kcBuff = new byte[kcLen + 1];
                kcBuff[0] = 0x04;
                int kcrLen = srcRAF.read(kcBuff, 1, kcLen);
                if (kcrLen != kcLen) {
                    return;
                }

                //2.b解密密钥信封数据
                KeyPair keyPair = PemUtil.getKeyPair(priKeyPath);
                BCECPrivateKey bcecPrivateKey = (BCECPrivateKey) keyPair.getPrivate();
                symmKey = SM2Util.decrypt(bcecPrivateKey, kcBuff);
                if (DEBUG_CRYPT) {
                    System.out.println("文件密钥(解密)：" + Hex.toHexString(symmKey));
                }
            }
            if (symmKey == null) {
                return;
            }

            //3.文件解密
            byte[] decData = null;
            srcRAF.read(rlBuffer);
            int encDataLen = toIntLittleEndian(rlBuffer);
            if (encDataLen > 0) {
                byte[] encData = new byte[encDataLen];
                srcRAF.read(encData);
                decData = SM4Util.decrypt_Cbc_Padding(symmKey, symmKey, encData);
                if (decData != null) {
                    dstRAF.write(decData);
                }
            }
        } catch (InvalidCipherTextException e) {
            e.printStackTrace();
        } catch (NoSuchPaddingException e) {
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (NoSuchProviderException e) {
            e.printStackTrace();
        } catch (InvalidKeyException e) {
            e.printStackTrace();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (InvalidAlgorithmParameterException e) {
            e.printStackTrace();
        } catch (BadPaddingException e) {
            e.printStackTrace();
        } catch (IllegalBlockSizeException e) {
            e.printStackTrace();
        } finally {
            try {
                if (srcRAF != null)
                    srcRAF.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if (dstRAF != null)
                    dstRAF.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * @param certFilePath 证书路径
     */
    public static byte[] encryptData(String certFilePath, byte[] srcData) {
        try {
            //3.加密对称密钥
            X509Certificate certificate = SM2CertUtil.getX509Certificate(certFilePath);
            BCECPublicKey bcecPublicKey = SM2CertUtil.getBCECPublicKey(certificate);
            return SM2Util.encrypt(bcecPublicKey, srcData);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (CertificateException e) {
            e.printStackTrace();
        } catch (NoSuchProviderException e) {
            e.printStackTrace();
        } catch (InvalidCipherTextException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * @param priKeyPath 私钥文件路径
     * @return
     */
    public static byte[] decryptData(String priKeyPath, byte[] encData) {
        try {
            //1.私钥解密数据
            KeyPair keyPair = PemUtil.getKeyPair(priKeyPath);
            BCECPrivateKey bcecPrivateKey = (BCECPrivateKey) keyPair.getPrivate();
            return SM2Util.decrypt(bcecPrivateKey, encData);
        } catch (InvalidCipherTextException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 低字节在前（低字节序）256=0x00010000
     */
    public static byte[] toBytesLittleEndian(int n) {
        byte[] b = new byte[4];
        b[0] = (byte) (n & 0xff);
        b[1] = (byte) (n >> 8 & 0xff);
        b[2] = (byte) (n >> 16 & 0xff);
        b[3] = (byte) (n >> 24 & 0xff);
        return b;
    }

    /**
     * 低字节在前（低字节序）0x00010000=256
     */
    public static int toIntLittleEndian(byte[] b) {
        int sum = 0;
        for (int i = 0; i < b.length; i++) {
            sum += (b[i] & 0xff) << (i * 8);
        }
        return sum;
    }

    /**
     * 高字节在前（高字节序）256=0x00000100
     */
    public static byte[] toBytesBigEndian(int n) {
        byte[] b = new byte[4];
        b[3] = (byte) (n & 0xff);
        b[2] = (byte) (n >> 8 & 0xff);
        b[1] = (byte) (n >> 16 & 0xff);
        b[0] = (byte) (n >> 24 & 0xff);
        return b;
    }

    /**
     * 高字节在前（高字节序）0x00000100=256
     */
    public static int toIntBigEndian(byte[] b) {
        int sum = 0;
        for (int i = 0; i < b.length; i++) {
            sum += (b[i] & 0xff) << ((b.length - 1 - i) * 8);
        }
        return sum;
    }
}
