国密SM2的公私钥与BC库的格式转换

一、前言

        国密SM2公私钥一般为C语言的结构体转为java对象得到的。该场景一般是通过密码机生成的外部非对称密钥对。运算也是有密码机进行运算,但如果想脱离密码机,使用java语言进行签名验签、加解密等活动,该如何实现?

        本文以java的BC库为例,完成国密公私钥格式转换为BC库格式,然后通过Java的JCE框架完成签名、验签、加/解密等功能。

二、公私钥格式转换实现

        首先我们先了解下SM2公私钥的结构,SM2私钥主要是一个32位的D值,公钥的结构为32位的X变量和Y变量。

        我们先看下SM2的国密私钥如果转为BC库的格式:首先在使用BC库的类,加入以下代码,用于密码提供商的注册。

        

static{
    if(Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
       Security.addProvider(new BouncyCastleProvider());
    }
}

私钥转换:BC库格式的私钥是需要包含公钥的,否则在签名的时候,使用SM3算法无法做数据预处理。

/**
     * GM格式ECC私钥转换BC格式私钥
     * @param privateKey ECC外部私钥
     * @return BouncyCastle格式私钥
     */
    public static BCECPrivateKey eccPrivateKeyConverToBC(ECCrefPrivateKey.ByReference privateKey, HsmDef.ECCrefPublicKey.ByReference publicKey){

        X9ECParameters x9ECParameters = GMNamedCurves.getByName("sm2p256v1");

        // 椭圆曲线公钥或私钥的基本域参数。
        ECParameterSpec ecDomainParameters = new ECParameterSpec(x9ECParameters.getCurve(), x9ECParameters.getG(),
                x9ECParameters.getN());

        BigInteger d = new BigInteger(privateKey.getK());

        //获得BC库格式的ECC公钥
        BCECPublicKey bcecPublicKey =  eccPublicKeyConverToBC(publicKey);

        //组装域参数
        ECDomainParameters domainParameters = new ECDomainParameters(x9ECParameters.getCurve(), x9ECParameters.getG(),
                x9ECParameters.getN(), x9ECParameters.getH());
        //组装BC库格式私钥对象
        BCECPrivateKey bcPrivateKey = new BCECPrivateKey("EC",new ECPrivateKeyParameters(d, domainParameters),bcecPublicKey,ecDomainParameters,BouncyCastleProvider.CONFIGURATION);
        return bcPrivateKey;

    }

公钥转换:

/**
     * ECC公钥转换BC格式公钥
     * @param publicKey ECC外部私钥
     * @return BouncyCastle格式私钥
     */
    public static BCECPublicKey eccPublicKeyConverToBC(ECCrefPublicKey.ByReference publicKey){

        BigInteger x = null;
        BigInteger y = null;
        
        if (ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN) {
            x = new BigInteger(1,publicKey.getX());
            y = new BigInteger(1,publicKey.getY());
        } else {
            x = new BigInteger(publicKey.getX());
            y = new BigInteger(publicKey.getY());
        }

        X9ECParameters x9ECParameters = GMNamedCurves.getByName("sm2p256v1");
        // 椭圆曲线公钥或私钥的基本域参数。
        ECParameterSpec ecDomainParameters = new ECParameterSpec(x9ECParameters.getCurve(), x9ECParameters.getG(),
                x9ECParameters.getN());
        // 通过公钥x、y分量创建椭圆曲线公钥规范
        ECPublicKeySpec ecPublicKeySpec = new ECPublicKeySpec(x9ECParameters.getCurve().createPoint(x, y),
                ecDomainParameters);
        KeyFactory keyFactory = null;
        try {
            keyFactory = KeyFactory.getInstance("EC", BouncyCastleProvider.PROVIDER_NAME);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch(NoSuchProviderException e){
            e.printStackTrace();
        }
        try {
            return (BCECPublicKey)keyFactory.generatePublic(ecPublicKeySpec);
        } catch (InvalidKeySpecException e) {
            e.printStackTrace();
        }

        return null;
    }

SM2签名:签名值为byte数组

/**
     * 签名运算
     * @param data 待签名数据
     * @param signAlg 签名算法(如:SM3WithSM2、SHA256WithSM2)
     * @param softPrivateKey 私钥
     * @return 签名值
     */
    public byte[] sign(byte[] data, String signAlg, PrivateKey softPrivateKey) {
        Signature inSignatue = null;
        try {
            inSignatue = Signature.getInstance(signAlg, BouncyCastleProvider.PROVIDER_NAME);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
            throw new CryptoException("未找到算法",e);
        }catch (NoSuchProviderException e){
            throw new CryptoException("未找到安全程序提供商,原因:"+e.getMessage(),e);
        }


        try {
            inSignatue.initSign(softPrivateKey);
        } catch (InvalidKeyException e) {
            e.printStackTrace();
            throw new CryptoException("无效的密钥",e);
        }
        try {
            inSignatue.update(data);
        } catch (SignatureException e) {
            e.printStackTrace();
            throw new CryptoException("签名更新数据异常",e);
        }
        byte[] signOut = null;
        try {
            signOut = inSignatue.sign();
        } catch (SignatureException e) {
            e.printStackTrace();
            throw new CryptoException("签名异常",e);
        }
        return signOut;
    }

SM2验签:

/**
     * 验签运算
     * @param sign 签名值(签名的结果)
     * @param data 数据原文
     * @param signAlg 签名算法(与签名时的算法一致)
     * @param softPublicKey 公钥
     * @return true为验签通过,false为未通过
     */
    public boolean verifySign(byte[] sign, byte[] data, String signAlg, PublicKey softPublicKey) {
        Signature inSignatue = null;
        try {
            inSignatue = Signature.getInstance(signAlg, BouncyCastleProvider.PROVIDER_NAME);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
            throw new CryptoException("未找到算法",e);
        }catch (NoSuchProviderException e){
            e.printStackTrace();
            throw new CryptoException("当需要特定的安全提供程序但在环境中不可用时,会引发此异常:"+e.getMessage(),e);
        }

        try {
            inSignatue.initVerify(softPublicKey);
        } catch (InvalidKeyException e) {
            e.printStackTrace();
            throw new CryptoException("无效的密钥",e);
        }
        try {
            inSignatue.update(data);
        } catch (SignatureException e) {
            e.printStackTrace();
            throw new CryptoException("验签更新数据异常",e);
        }
        boolean flag = false;
        try {
            flag = inSignatue.verify(sign);
        } catch (SignatureException e) {
            e.printStackTrace();
            throw new CryptoException("验签异常",e);
        }
        return flag;
    }

SM2公钥加密:

/**
 *@param publicKey SM2公钥
 *@param algorithm 加密算法,固定传“SM2”即可
 *@param inData 待加密的数据
 *@return 密文数据
 **/
public byte[] publicKeyEnc(PublicKey publicKey,String algorithm,byte[] inData){
        Cipher cipher = null;
        try {
            cipher = Cipher.getInstance(algorithm, BouncyCastleProvider.PROVIDER_NAME);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
            throw new CryptoException("未找到算法,原因:"+e.getMessage(),e);
        } catch (NoSuchPaddingException e) {
            e.printStackTrace();
            throw new CryptoException("未找到填充模式,原因:"+e.getMessage(),e);
        }catch (NoSuchProviderException e){
            e.printStackTrace();
            throw new CryptoException("当需要特定的安全提供程序但在环境中不可用时,会引发此异常:"+e.getMessage(),e);
        }

        try {
            cipher.init(Cipher.ENCRYPT_MODE, publicKey);
        } catch (InvalidKeyException e) {
            e.printStackTrace();
            throw new CryptoException("无效的密钥,原因:"+e.getMessage(),e);
        }

        byte[] tTemp = null;
        try {
            tTemp = cipher.doFinal(inData);
        } catch (IllegalBlockSizeException e) {
            e.printStackTrace();
            throw new CryptoException("非法块大小异常",e);

        } catch (BadPaddingException e) {
            e.printStackTrace();
            throw new CryptoException("错误的填充",e);
        }
        return tTemp;
    }

SM2私钥解密:

/**
     * 外部密钥解密
     * @param privateKey 私钥
     * @param algorithm 加密算法(与加密时使用的算法一致)
     * @param inData 待解密数据
     * @return 明文数据
     */
    private byte[] externalDecrypt(PrivateKey privateKey,String algorithm,byte[] inData){
        Cipher cipher = null;
        try {
            cipher = Cipher.getInstance(algorithm, BouncyCastleProvider.PROVIDER_NAME);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
            throw new CryptoException("未找到算法",e);
        }catch (NoSuchPaddingException e) {
            e.printStackTrace();
            throw new CryptoException("未找到填充模式",e);
        }catch (NoSuchProviderException e){
            throw new CryptoException("当需要特定的安全提供程序但在环境中不可用时,会引发此异常:"+e.getMessage(),e);
        }
        try {
            cipher.init(Cipher.DECRYPT_MODE, privateKey);
        } catch (InvalidKeyException e) {
            e.printStackTrace();
            throw new CryptoException("无效的密钥",e);
        }
        byte[] tResult = null;
        try {
            tResult = cipher.doFinal(inData);
        } catch (IllegalBlockSizeException e) {
            e.printStackTrace();
            throw new CryptoException("非法块大小异常",e);
        } catch (BadPaddingException e) {
            e.printStackTrace();
            throw new CryptoException("错误的填充",e);
        }
        return tResult;
    }

以上则为SM2算法的相关知识,如有不合理的地方,后期发现后再改。

你可能感兴趣的:(密码学,密码学,java,算法)