package cn.anicert.secure.access.crypto.util;

import java.math.BigInteger;
import java.security.*;
import java.security.spec.ECPoint;
import java.security.spec.ECPrivateKeySpec;
import java.security.spec.ECPublicKeySpec;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import org.bouncycastle.asn1.gm.GMNamedCurves;
import org.bouncycastle.asn1.gm.GMObjectIdentifiers;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.jcajce.provider.asymmetric.util.EC5Util;
import org.bouncycastle.jcajce.provider.asymmetric.util.ECUtil;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.spec.ECNamedCurveSpec;
import org.bouncycastle.math.ec.ECCurve;

public class SM2KeyUtil {
    private static final X9ECParameters ecParameters;
    private static final ECNamedCurveSpec sm2Spec;
    private static final BouncyCastleProvider bcProvider;
    private static final KeyPairGenerator keyGenerator;
    private static final KeyFactory keyFactory;
    private static final ECCurve curve;

    public SM2KeyUtil() {
    }

    public static KeyPair genKeyPair() {
        return keyGenerator.generateKeyPair();
    }

    public static byte[] encodeWithPoint(BCECPublicKey pub) {
        return pub.getQ().getEncoded(false);
    }

    public static BCECPublicKey decodeWithPoint(byte[] data) throws InvalidKeySpecException {
        ECPoint point = EC5Util.convertPoint(curve.decodePoint(data));
        return (BCECPublicKey)keyFactory.generatePublic(new ECPublicKeySpec(point, sm2Spec));
    }

    public static String encodeStrWithPoint(BCECPublicKey pub) {
        return Base64.encode(encodeWithPoint(pub));
    }

    public static BCECPublicKey decodeWithPoint(String base64) throws InvalidKeySpecException {
        return decodeWithPoint(Base64.decode(base64));
    }

    public static String getX64(BCECPublicKey pub) {
        return Base64.encode(pub.getQ().getXCoord().getEncoded());
    }

    public static String getY64(BCECPublicKey pub) {
        return Base64.encode(pub.getQ().getYCoord().getEncoded());
    }

    public static BCECPublicKey fromXY(String x64, String y64) throws InvalidKeySpecException {
        return fromXY(Base64.decode(x64), Base64.decode(y64));
    }

    public static byte[] getX(BCECPublicKey pub) {
        return pub.getQ().getXCoord().getEncoded();
    }

    public static byte[] getY(BCECPublicKey pub) {
        return pub.getQ().getYCoord().getEncoded();
    }

    public static BCECPublicKey fromXY(byte[] x, byte[] y) throws InvalidKeySpecException {
        ECPoint point = EC5Util.convertPoint(curve.createPoint(new BigInteger(1, x), new BigInteger(1, y)));
        return (BCECPublicKey)keyFactory.generatePublic(new ECPublicKeySpec(point, sm2Spec));
    }

    public static byte[] encodeWithBigInteger(BCECPrivateKey pri) {
        return pri.getD().toByteArray();
    }

    public static BCECPrivateKey decodeWithBigInteger(byte[] data) throws InvalidKeySpecException {
        return (BCECPrivateKey)keyFactory.generatePrivate(new ECPrivateKeySpec(new BigInteger(1, data), sm2Spec));
    }

    public static String encodeStrWithBigInteger(BCECPrivateKey pri) {
        return Base64.encode(encodeWithBigInteger(pri));
    }

    public static BCECPrivateKey decodeWithBigInteger(String base64) throws InvalidKeySpecException {
        return decodeWithBigInteger(Base64.decode(base64));
    }

    public static byte[] encodeWithKey(Key key) {
        return key.getEncoded();
    }

    public static BCECPublicKey decodeWithX509(byte[] data) throws InvalidKeySpecException {
        return (BCECPublicKey)keyFactory.generatePublic(new X509EncodedKeySpec(data));
    }

    public static BCECPrivateKey decodeWithPKCS8(byte[] data) throws InvalidKeySpecException {
        return (BCECPrivateKey)keyFactory.generatePrivate(new PKCS8EncodedKeySpec(data));
    }

    public static String encodeStrWithKey(Key key) {
        return Base64.encode(encodeWithKey(key));
    }

    public static BCECPublicKey decodeWithX509(String base64) throws InvalidKeySpecException {
        return decodeWithX509(Base64.decode(base64.replaceAll("-----.*-----", "")));
    }

    public static BCECPrivateKey decodeWithPKCS8(String base64) throws InvalidKeySpecException {
        return decodeWithPKCS8(Base64.decode(base64.replaceAll("-----.*-----", "")));
    }

    public static CipherParameters forParameters(Key key) throws InvalidKeyException {
        if (key instanceof PublicKey) {
            return ECUtil.generatePublicKeyParameter((PublicKey)key);
        } else if (key instanceof PrivateKey) {
            return ECUtil.generatePrivateKeyParameter((PrivateKey)key);
        } else {
            throw new InvalidKeyException("not expect key type");
        }
    }

    static {
        try {
            ecParameters = GMNamedCurves.getByName("sm2p256v1");
            sm2Spec = new ECNamedCurveSpec(GMObjectIdentifiers.sm2p256v1.toString(), ecParameters.getCurve(), ecParameters.getG(), ecParameters.getN());
            bcProvider = new BouncyCastleProvider();
            keyGenerator = KeyPairGenerator.getInstance("EC", bcProvider);
            keyGenerator.initialize(sm2Spec, new SecureRandom());
            keyFactory = KeyFactory.getInstance("EC", bcProvider);
            curve = ecParameters.getCurve();
        } catch (InvalidAlgorithmParameterException | NoSuchAlgorithmException var1) {
            throw new RuntimeException("无法提供SM2算法", var1);
        }
    }

    public static void main(String[] args) {
        KeyPair smKeyPair = SM2KeyUtil.genKeyPair();
        String publicKey = Base64.encode(smKeyPair.getPublic().getEncoded());
        String privateKey =  Base64.encode(smKeyPair.getPrivate().getEncoded());

       System.out.println("public key:"+ publicKey);
       System.out.println("private key:"+ privateKey);
    }
}
