SM2国密生成公私钥加密解密

import org.bouncycastle.asn1.gm.GMNamedCurves;

import org.bouncycastle.asn1.x9.X9ECParameters;

import org.bouncycastle.crypto.AsymmetricCipherKeyPair;

import org.bouncycastle.crypto.CryptoException;

import org.bouncycastle.crypto.engines.SM2Engine;

import org.bouncycastle.crypto.generators.ECKeyPairGenerator;

import org.bouncycastle.crypto.params.*;

import org.bouncycastle.crypto.signers.SM2Signer;

import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;

import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;

import org.bouncycastle.jce.provider.BouncyCastleProvider;

import org.bouncycastle.jce.spec.ECParameterSpec;

import org.bouncycastle.jce.spec.ECPrivateKeySpec;

import org.bouncycastle.jce.spec.ECPublicKeySpec;

import org.bouncycastle.math.ec.ECPoint;

import org.bouncycastle.util.Strings;

import org.bouncycastle.util.encoders.Hex;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import java.io.UnsupportedEncodingException;

import java.math.BigInteger;

import java.security.*;

import java.security.spec.ECGenParameterSpec;

public class SM2Utils {

private static final LoggerLOGGER = LoggerFactory.getLogger(SM2Utils.class);

    /**

    * @Description 生成秘钥对

    * @Author zd

    * @return KeyPair

*/

    public static KeyPaircreateECKeyPair() {

//使用标准名称创建EC参数生成的参数规范

        final ECGenParameterSpec sm2Spec =new ECGenParameterSpec("sm2p256v1");

        // 获取一个椭圆曲线类型的密钥对生成器

        final KeyPairGenerator kpg;

        try {

kpg = KeyPairGenerator.getInstance("EC", new BouncyCastleProvider());

            // 使用SM2算法域参数集初始化密钥生成器(默认使用以最高优先级安装的提供者的 SecureRandom 的实现作为随机源)

            // kpg.initialize(sm2Spec);

            // 使用SM2的算法域参数集和指定的随机源初始化密钥生成器

            kpg.initialize(sm2Spec, new SecureRandom());

            // 通过密钥生成器生成密钥对

            return kpg.generateKeyPair();

        }catch (Exception e) {

e.printStackTrace();

return null;

        }

}

/**

    * 生成SM2公私钥对

    * @return

    */

    private static AsymmetricCipherKeyPairgenKeyPair0() {

//获取一条SM2曲线参数

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

        //构造domain参数

        ECDomainParameters domainParameters =new ECDomainParameters(sm2ECParameters.getCurve(),

                sm2ECParameters.getG(), sm2ECParameters.getN());

        //1.创建密钥生成器

        ECKeyPairGenerator keyPairGenerator =new ECKeyPairGenerator();

        //2.初始化生成器,带上随机数

        try {

keyPairGenerator.init(new ECKeyGenerationParameters(domainParameters, SecureRandom.getInstance("SHA1PRNG")));

        }catch (NoSuchAlgorithmException e) {

LOGGER.error("生成公私钥对时出现异常:", e);

//            e.printStackTrace();

        }

//3.生成密钥对

        AsymmetricCipherKeyPair asymmetricCipherKeyPair = keyPairGenerator.generateKeyPair();

        return asymmetricCipherKeyPair;

    }

/**

    * @Description 公钥加密

    * @Author zd

    * @param publicKeyHex SM2十六进制公钥

    * @param data        明文数据

    * @return String

*/

    public static Stringencrypt(String publicKeyHex, String data) {

return encrypt(getECPublicKeyByPublicKeyHex(publicKeyHex), data, 1);

    }

/**

    * @Description 公钥加密

    * @Author zd

    * @param publicKey SM2公钥

    * @param data      明文数据

    * @param modeType  加密模式

    * @return String

*/

    public static Stringencrypt(BCECPublicKey publicKey, String data, int modeType) {

//加密模式

        SM2Engine.Mode mode = SM2Engine.Mode.C1C3C2;

        if (modeType !=1) {

mode = SM2Engine.Mode.C1C2C3;

        }

//通过公钥对象获取公钥的基本域参数。

        ECParameterSpec ecParameterSpec = publicKey.getParameters();

        ECDomainParameters ecDomainParameters =new ECDomainParameters(ecParameterSpec.getCurve(),

                ecParameterSpec.getG(), ecParameterSpec.getN());

        //通过公钥值和公钥基本参数创建公钥参数对象

        ECPublicKeyParameters ecPublicKeyParameters =new ECPublicKeyParameters(publicKey.getQ(), ecDomainParameters);

        //根据加密模式实例化SM2公钥加密引擎

        SM2Engine sm2Engine =new SM2Engine(mode);

        //初始化加密引擎

        sm2Engine.init(true, new ParametersWithRandom(ecPublicKeyParameters, new SecureRandom()));

        byte[] arrayOfBytes =null;

        try {

//将明文字符串转换为指定编码的字节串

            byte[] in = data.getBytes("utf-8");

            //通过加密引擎对字节数串行加密

            arrayOfBytes = sm2Engine.processBlock(in, 0, in.length);

        }catch (Exception e) {

System.out.println("SM2加密时出现异常:" + e.getMessage());

            e.printStackTrace();

        }

//将加密后的字节串转换为十六进制字符串

        return Hex.toHexString(arrayOfBytes);

    }

/**

    * @Description 私钥解密

    * @Author zd

    * @param privateKeyHex SM2十六进制私钥

    * @param cipherData    密文数据

    * @return String

*/

    public static Stringdecrypt(String privateKeyHex, String cipherData) {

return decrypt(getBCECPrivateKeyByPrivateKeyHex(privateKeyHex), cipherData, 1);

    }

/**

    * @Description 私钥解密

    * @Author zd

    * @param privateKey SM私钥

    * @param cipherData 密文数据

    * @param modeType  解密模式

    * @return

    */

    public static Stringdecrypt(BCECPrivateKey privateKey, String cipherData, int modeType) {

//解密模式

        SM2Engine.Mode mode = SM2Engine.Mode.C1C3C2;

        if (modeType !=1) {

mode = SM2Engine.Mode.C1C2C3;

        }

//将十六进制字符串密文转换为字节数组(需要与加密一致,加密是:加密后的字节数组转换为了十六进制字符串)

        byte[] cipherDataByte = Hex.decode(cipherData);

        //通过私钥对象获取私钥的基本域参数。

        ECParameterSpec ecParameterSpec = privateKey.getParameters();

        ECDomainParameters ecDomainParameters =new ECDomainParameters(ecParameterSpec.getCurve(),

                ecParameterSpec.getG(), ecParameterSpec.getN());

        //通过私钥值和私钥钥基本参数创建私钥参数对象

        ECPrivateKeyParameters ecPrivateKeyParameters =new ECPrivateKeyParameters(privateKey.getD(),

                ecDomainParameters);

        //通过解密模式创建解密引擎并初始化

        SM2Engine sm2Engine =new SM2Engine(mode);

        sm2Engine.init(false, ecPrivateKeyParameters);

        String result =null;

        try {

//通过解密引擎对密文字节串进行解密

            byte[] arrayOfBytes = sm2Engine.processBlock(cipherDataByte, 0, cipherDataByte.length);

            //将解密后的字节串转换为utf8字符编码的字符串(需要与明文加密时字符串转换成字节串所指定的字符编码保持一致)

            result =new String(arrayOfBytes, "utf-8");

        }catch (Exception e) {

System.out.println("SM2解密时出现异常" + e.getMessage());

        }

return result;

    }

//椭圆曲线ECParameters ASN.1 结构

    private static X9ECParametersx9ECParameters = GMNamedCurves.getByName("sm2p256v1");

    //椭圆曲线公钥或私钥的基本域参数。

    private static ECParameterSpececDomainParameters =new ECParameterSpec(x9ECParameters.getCurve(), x9ECParameters.getG(), x9ECParameters.getN());

    /**

    * @Description 公钥字符串转换为 BCECPublicKey 公钥对象

    * @Author zd

    * @param pubKeyHex 64字节十六进制公钥字符串(如果公钥字符串为65字节首个字节为0x04:表示该公钥为非压缩格式,操作时需要删除)

    * @return BCECPublicKey SM2公钥对象

    */

    public static BCECPublicKeygetECPublicKeyByPublicKeyHex(String pubKeyHex) {

//截取64字节有效的SM2公钥(如果公钥首个字节为0x04)

        if (pubKeyHex.length() >128) {

pubKeyHex = pubKeyHex.substring(pubKeyHex.length() -128);

        }

//将公钥拆分为x,y分量(各32字节)

        String stringX = pubKeyHex.substring(0, 64);

        String stringY = pubKeyHex.substring(stringX.length());

        //将公钥x、y分量转换为BigInteger类型

        BigInteger x =new BigInteger(stringX, 16);

        BigInteger y =new BigInteger(stringY, 16);

        //通过公钥x、y分量创建椭圆曲线公钥规范

        ECPublicKeySpec ecPublicKeySpec =new ECPublicKeySpec(x9ECParameters.getCurve().createPoint(x, y), ecDomainParameters);

        //通过椭圆曲线公钥规范,创建出椭圆曲线公钥对象(可用于SM2加密及验签)

        return new BCECPublicKey("EC", ecPublicKeySpec, BouncyCastleProvider.CONFIGURATION);

    }

/**

    * @Description 私钥字符串转换为 BCECPrivateKey 私钥对象

    * @Author zd

    * @param privateKeyHex 32字节十六进制私钥字符串

    * @return BCECPrivateKey SM2私钥对象

    */

    public static BCECPrivateKeygetBCECPrivateKeyByPrivateKeyHex(String privateKeyHex) {

//将十六进制私钥字符串转换为BigInteger对象

        BigInteger d =new BigInteger(privateKeyHex, 16);

        //通过私钥和私钥域参数集创建椭圆曲线私钥规范

        ECPrivateKeySpec ecPrivateKeySpec =new ECPrivateKeySpec(d, ecDomainParameters);

        //通过椭圆曲线私钥规范,创建出椭圆曲线私钥对象(可用于SM2解密和签名)

        return new BCECPrivateKey("EC", ecPrivateKeySpec, BouncyCastleProvider.CONFIGURATION);

    }

/**

    * 私钥签名

    * @param privateKey    私钥

    * @param content      待签名内容

    * @return

    */

    public static Stringsign(String privateKey, String content)throws CryptoException {

//待签名内容转为字节数组

        byte[] message = Hex.decode(content);

        //获取一条SM2曲线参数

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

        //构造domain参数

        ECDomainParameters domainParameters =new ECDomainParameters(sm2ECParameters.getCurve(),

                sm2ECParameters.getG(), sm2ECParameters.getN());

        BigInteger privateKeyD =new BigInteger(privateKey, 16);

        ECPrivateKeyParameters privateKeyParameters =new ECPrivateKeyParameters(privateKeyD, domainParameters);

        //创建签名实例

        SM2Signer sm2Signer =new SM2Signer();

        //初始化签名实例,带上ID,国密的要求,ID默认值:1234567812345678

        try {

sm2Signer.init(true, new ParametersWithID(new ParametersWithRandom(privateKeyParameters, SecureRandom.getInstance("SHA1PRNG")), Strings.toByteArray("1234567812345678")));

        }catch (NoSuchAlgorithmException e) {

log.error("签名时出现异常:", e);

        }

sm2Signer.update(message, 0, message.length);

        //生成签名,签名分为两部分r和s,分别对应索引0和1的数组

        byte[] signBytes = sm2Signer.generateSignature();

        String sign = Hex.toHexString(signBytes);

        return sign;

    }

/**

    * 验证签名

    * @param publicKey    公钥

    * @param content      待签名内容

    * @param sign          签名值

    * @return

    */

    public static boolean verify(String publicKey, String content, String sign) {

//待签名内容

        byte[] message = Hex.decode(content);

        byte[] signData = Hex.decode(sign);

        // 获取一条SM2曲线参数

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

        // 构造domain参数

        ECDomainParameters domainParameters =new ECDomainParameters(sm2ECParameters.getCurve(),

                sm2ECParameters.getG(),

                sm2ECParameters.getN());

        //提取公钥点

        ECPoint pukPoint = sm2ECParameters.getCurve().decodePoint(Hex.decode(publicKey));

        // 公钥前面的02或者03表示是压缩公钥,04表示未压缩公钥, 04的时候,可以去掉前面的04

        ECPublicKeyParameters publicKeyParameters =new ECPublicKeyParameters(pukPoint, domainParameters);

        //创建签名实例

        SM2Signer sm2Signer =new SM2Signer();

        ParametersWithID parametersWithID =new ParametersWithID(publicKeyParameters, Strings.toByteArray("1234567812345678"));

        sm2Signer.init(false, parametersWithID);

        sm2Signer.update(message, 0, message.length);

        //验证签名结果

        boolean verify = sm2Signer.verifySignature(signData);

        return verify;

    }

public static void main(String[] args)throws UnsupportedEncodingException, CryptoException, NoSuchAlgorithmException, InvalidAlgorithmParameterException {

String data ="{\"head\":{\"reqCode\":\"1001\",\"reqDate\":\"20220321\",\"reqTime\":\"231815\",\"reqId\":\"2022\",\"bbkNbr\":\"379\"},\"body\":{\"regCode\":\"8238003300209049600\"}}";

        String timestamp = String.valueOf(System.currentTimeMillis());

        AsymmetricCipherKeyPair asymmetricCipherKeyPair =genKeyPair0();

        //提取公钥点

        ECPoint ecPoint = ((ECPublicKeyParameters) asymmetricCipherKeyPair.getPublic()).getQ();

        //公钥前面的02或者03表示是压缩公钥,04表示未压缩公钥,04的时候,可以去掉前面的04

        String publicStr = Hex.toHexString(ecPoint.getEncoded(false));

        BigInteger privatekey = ((ECPrivateKeyParameters) asymmetricCipherKeyPair.getPrivate()).getD();

        String privateStr = privatekey.toString(16);

        System.out.println("---->生成公钥:" + publicStr);

        System.out.println("---->生成私钥:" + privateStr);

        String encryptData =encrypt(publicStr, data);

        System.out.println("---->加密结果:" + encryptData);

        String decryptData =decrypt(privateStr,encryptData);

        System.out.println("---->解密结果:" + decryptData);

        String sign = SM2Utils.sign(privateStr,Hex.toHexString((data + timestamp).getBytes()));

        System.out.println("---->签名结果:" + sign);

        boolean verify = SM2Utils.verify(publicStr, Hex.toHexString((data + timestamp).getBytes()), sign);

        System.out.println("---->验签结果:" + verify);

    }

}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,794评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,050评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,587评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,861评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,901评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,898评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,832评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,617评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,077评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,349评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,483评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,199评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,824评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,442评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,632评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,474评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,393评论 2 352

推荐阅读更多精彩内容