package org.jisonami.crypto;

import org.apache.commons.codec.binary.Base64;

import javax.crypto.KeyAgreement;
import javax.crypto.SecretKey;
import javax.crypto.interfaces.DHKey;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.interfaces.ECKey;
import java.security.spec.AlgorithmParameterSpec;
import java.util.HashMap;
import java.util.Map;

/**
 * <p>Created by jisonami on 16-10-15.</p>
 * <p>处理密钥协商算法,如DH</p>
 * <p>使用Bouncy Castle组件包可支持ECDH</p>
 * <p>默认使用DH算法和UTF-8编码</p>
 *
 * @author jisonami
 * @see AbstractCryptography
 * @since 0.0.1
 */
public class KeyAgreementCryptography extends AbstractNonSymmetricCryptography {

    public KeyAgreementCryptography() {
        getConfiguration().setKeyAlgorithm(Algorithms.DH).setCipherAlgorithm(Algorithms.DES).setKeySize(Algorithms.KEYSIZE_1024);
    }

    public KeyAgreementCryptography(Configuration configuration) {
        super(configuration);
    }

    /**
     * 加密操作
     *
     * @param data 需要加密的数据
     * @param key  密钥的二进制形式
     * @return 加密后的数据
     */
    public String encrypt(String data, byte[] key) {
        try {
            byte[] result = this.encrypt(data.getBytes(getConfiguration().getCharset()), key);
            return new String(Base64.encodeBase64(result), getConfiguration().getCharset());
        } catch (UnsupportedEncodingException e) {
            throw new CryptographyException(ExceptionInfo.UNSUPPORTED_ENCODING_EXCEPTION_INFO + getConfiguration().getCharset(), e);
        }
    }

    /**
     * 加密操作
     *
     * @param data 需要加密的数据
     * @param key  密钥的二进制形式
     * @return 加密后的数据 Base64URL形式
     */
    public String encryptURL(String data, byte[] key) {
        try {
            byte[] result = this.encrypt(data.getBytes(getConfiguration().getCharset()), key);
            return new String(Base64.encodeBase64URLSafe(result), getConfiguration().getCharset());
        } catch (UnsupportedEncodingException e) {
            throw new CryptographyException(ExceptionInfo.UNSUPPORTED_ENCODING_EXCEPTION_INFO + getConfiguration().getCharset(), e);
        }
    }

    /**
     * 加密操作
     *
     * @param data 需要加密的数据
     * @param key  密钥的二进制形式
     * @return 加密后的数据
     */
    public byte[] encrypt(byte[] data, byte[] key) {
        Key k = toSecretKey(key);
        return this.encrypt(data, k);
    }


    /**
     * 解密操作
     *
     * @param data 需要解密的数据
     * @param key  密钥的二进制形式
     * @return 解密后的数据
     */
    public String decrypt(String data, byte[] key) {
        byte[] result = Base64.decodeBase64(data);
        try {
            return new String(this.decrypt(result, key), getConfiguration().getCharset());
        } catch (UnsupportedEncodingException e) {
            throw new CryptographyException(ExceptionInfo.UNSUPPORTED_ENCODING_EXCEPTION_INFO + getConfiguration().getCharset(), e);
        }
    }

    /**
     * 解密操作
     *
     * @param data 需要解密的数据 Base64URL形式
     * @param key  密钥的二进制形式
     * @return 解密后的数据
     */
    public String decryptURL(String data, byte[] key) {
        try {
            byte[] result = Base64.decodeBase64(data.getBytes(getConfiguration().getCharset()));
            return new String(this.decrypt(result, key), getConfiguration().getCharset());
        } catch (UnsupportedEncodingException e) {
            throw new CryptographyException(ExceptionInfo.UNSUPPORTED_ENCODING_EXCEPTION_INFO + getConfiguration().getCharset(), e);
        }
    }

    /**
     * 解密操作
     *
     * @param data 需要解密的数据
     * @param key  密钥的二进制形式
     * @return 解密后的数据
     */
    public byte[] decrypt(byte[] data, byte[] key) {
        Key k = toSecretKey(key);
        return this.decrypt(data, k);
    }

    /**
     * 将对称密钥二进制形式转换成对称密钥
     *
     * @param key 对称密钥的二进制形式
     * @return 对称密钥对象
     */
    public SecretKey toSecretKey(byte[] key) {
        return new SecretKeySpec(key, getConfiguration().getCipherAlgorithm());
    }

    /**
     * 根据甲方私钥和乙方公钥生成甲方本地对称密钥,或者根据乙方私钥和甲方公钥生成乙方本地对称密钥
     *
     * @param publicKey  公钥二进制形式
     * @param privateKey 私钥二进制形式
     * @return 对称密钥对象
     */
    public byte[] initSecretKey(byte[] publicKey, byte[] privateKey) {
        PublicKey pubKey = this.toPublicKey(publicKey);
        PrivateKey priKey = this.toPrivateKey(privateKey);
        KeyAgreement keyAgreement = null;
        try {
            keyAgreement = KeyAgreement.getInstance(getConfiguration().getKeyAlgorithm());
        } catch (NoSuchAlgorithmException e) {
            throw new CryptographyException(ExceptionInfo.NO_SUCH_ALGORITHM_EXCEPTION_INFO + getConfiguration().getKeyAlgorithm(), e);
        }
        try {
            keyAgreement.init(priKey);
        } catch (InvalidKeyException e) {
            throw new CryptographyException(ExceptionInfo.INVALID_KEY_EXCEPTION_INFO + priKey.toString(), e);
        }
        try {
            keyAgreement.doPhase(pubKey, true);
        } catch (InvalidKeyException e) {
            throw new CryptographyException(ExceptionInfo.INVALID_KEY_EXCEPTION_INFO + pubKey.toString(), e);
        }
        try {
            SecretKey secretKey = keyAgreement.generateSecret(getConfiguration().getCipherAlgorithm());
            return secretKey.getEncoded();
        } catch (NoSuchAlgorithmException e) {
            throw new CryptographyException(ExceptionInfo.NO_SUCH_ALGORITHM_EXCEPTION_INFO + getConfiguration().getCipherAlgorithm(), e);
        } catch (InvalidKeyException e) {
            throw new CryptographyException(ExceptionInfo.INVALID_KEY_EXCEPTION_INFO, e);
        }
    }

    /**
     * 初始化密钥协商算法的甲方密钥对
     *
     * @return 甲方密钥对
     */
    @Override
    public Map<String, Key> initKey() {
        return super.initKey();
    }

    /**
     * 初始化密钥协商算法的乙方密钥对
     *
     * @param publicKey 甲方公钥的二进制形式
     * @return 乙方密钥对
     */
    public Map<String, Key> initKey(byte[] publicKey) {
        PublicKey pubKey = this.toPublicKey(publicKey);
        KeyPairGenerator keyPairGenerator = getKeyPairGenerator();
        AlgorithmParameterSpec algorithmParameterSpec = null;
        if (pubKey instanceof DHKey) {
            algorithmParameterSpec = ((DHKey) pubKey).getParams();
        } else if (pubKey instanceof ECKey) {
            algorithmParameterSpec = ((ECKey) pubKey).getParams();
        } else {
            throw new CryptographyException(ExceptionInfo.NO_SUCH_ALGORITHM_EXCEPTION_INFO + getConfiguration().getKeyAlgorithm());
        }
        try {
            keyPairGenerator.initialize(algorithmParameterSpec);
        } catch (InvalidAlgorithmParameterException e) {
            throw new CryptographyException(ExceptionInfo.NO_SUCH_ALGORITHM_EXCEPTION_INFO + getConfiguration().getKeyAlgorithm(), e);
        }
        KeyPair keyPair = keyPairGenerator.generateKeyPair();
        Map<String, Key> keyMap = new HashMap<String, Key>();
        keyMap.put(PRIVATE_KEY, keyPair.getPrivate());
        keyMap.put(PUBLIC_KEY, keyPair.getPublic());
        return keyMap;
    }


}