JAVA SM2 密钥生成 签名验签 【快速入门】

Before Start

SM2算法使用请参考:《GMT 0009-2012 SM2密码算法使用规范 》


bouncycastle - 1.57版本之后,加入了对 我国的SM2、SM3、SM4算法的支持

Bouncycastle releasenotes

BC版本日志

Build with Maven

适配JDK 1.5 版本

<dependency>
    <groupId>org.bouncycastle</groupId>
    <artifactId>bcprov-jdk15on</artifactId>
    <version>1.60</version>
</dependency>

QuickStart

Github

密钥对生成

SM2 非对称算法密钥对生成。

// 获取SM2椭圆曲线的参数
final ECGenParameterSpec sm2Spec = new ECGenParameterSpec("sm2p256v1");
// 获取一个椭圆曲线类型的密钥对生成器
final KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC", new BouncyCastleProvider());
// 使用SM2参数初始化生成器
kpg.initialize(sm2Spec);

// 使用SM2的算法区域初始化密钥生成器
kpg.initialize(sm2Spec, new SecureRandom());
// 获取密钥对
KeyPair keyPair = kpg.generateKeyPair();

感谢 githuber Gsealy的指点,密钥生成器构造更加简单。

关于椭圆曲线的推荐参数请参考 IETF draft-shen-sm2-ecdsa-02 #appendix-D

在BC中已经为构造了SM2算法参数,并提供算法OID,请参考:

国密算法OID及意义

国密算法OID 源码

SM2算法推荐参数 源码

公钥验签

产生了密钥对之后,就可以使用JAVA security 提供的一些标准化的接口来完成签名验签操作。

/*
获取公私钥
 */
PublicKey publicKey = keyPair.getPublic();
PrivateKey privateKey = keyPair.getPrivate();

// 生成SM2sign with sm3 签名验签算法实例
Signature signature = Signature.getInstance(
				 GMObjectIdentifiers.sm2sign_with_sm3.toString()
				, new BouncyCastleProvider());

/*
 * 签名
 */
// 签名需要使用私钥,使用私钥 初始化签名实例
signature.initSign(privateKey);
// 签名原文
byte[] plainText = "Hello world".getBytes(StandardCharsets.UTF_8);
// 写入签名原文到算法中
signature.update(plainText);
// 计算签名值
byte[] signatureValue = signature.sign();
System.out.println("signature: \n" + Hex.toHexString(signatureValue));

/*
 * 验签
 */
// 签名需要使用公钥,使用公钥 初始化签名实例
signature.initVerify(publicKey);
// 写入待验签的签名原文到算法中
signature.update(plainText);
// 验签
System.out.println("Signature verify result: " + signature.verify(signatureValue));

你可以在 国密算法OID 中找到需要算法的OID字符串。
如: SM2Sign-with-SM3 1.2.156.10197.1.501

证书验签

证书格式多为一个DER编码,也就是一串BASE64串,我们可以通过BC包提供的解析功能来解析SM2证书并并验签。

// 证书串,通常通过读取证书文件获取到,这里是一张SM2证书。
String certStr = "MIICCTCCAay....t5H";

// 签名原文
String plaintext = "signdata";

// 签名产生签名值,此处的签名值实际上就是 R和S的sequence
String signValueStr = "MEUCI...DOeFE=";
byte[] signValue = Base64.decode(signValueStr);
/*
 * 解析证书
 */
CertificateFactory factory = new CertificateFactory();
X509Certificate certificate = (X509Certificate) factory.engineGenerateCertificate(new ByteArrayInputStream(Base64.decode(certStr)));

System.out.println(certificate.getSigAlgName());

// 验证签名
Signature signature = Signature.getInstance(certificate.getSigAlgName(), new BouncyCastleProvider());
signature.initVerify(certificate);
signature.update(plaintext.getBytes(StandardCharsets.UTF_8));

System.out.println(signature.verify(signValue));

密钥对的反序列化

  1. 获取椭圆曲线类型的密钥工厂。
  2. 根据公私钥对的格式进行反序列化。
    public static void main(String[] args) throws NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException, SignatureException {
        final BouncyCastleProvider bc = new BouncyCastleProvider();

        /*
        >> 公钥BASE64: MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAESic24soUECzuSh2aYH0e+hQYh+/I01NmfjOnm5mwyUEYQvNCPTzn3BlNyufgMV+DWLUKV+2h0+PVel9jYTfG8Q==
        >> 私钥BASE64: MIGTAgEAMBMGByqGSM49AgEGCCqBHM9VAYItBHkwdwIBAQQg0dYU+I6IdiSe8bvWlsHuWfsjSn3XFZqOGWO3K1814O6gCgYIKoEcz1UBgi2hRANCAARKJzbiyhQQLO5KHZpgfR76FBiH78jTU2Z+M6ebmbDJQRhC80I9POfcGU3K5+AxX4NYtQpX7aHT49V6X2NhN8bx
        signature:
        3045022100ff9a872f21e47d4fba8f37b48a62cc2e6fdde843a40cbc96242536afc10a395e02203bbab982d1bb6a7ee5f5f6b34cd887c255ae4dcc14dd87ecae2e0392611b7a8c
         */
//        // 公私钥是16进制情况下解码
//        byte[] encPub = Hex.decode("...");
//        byte[] encPriv =  Hex.decode("...");
        byte[] encPub = Base64.decode("MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAESic24soUECzuSh2aYH0e+hQYh+/I01NmfjOnm5mwyUEYQvNCPTzn3BlNyufgMV+DWLUKV+2h0+PVel9jYTfG8Q==");
        byte[] encPriv = Base64.decode("MIGTAgEAMBMGByqGSM49AgEGCCqBHM9VAYItBHkwdwIBAQQg0dYU+I6IdiSe8bvWlsHuWfsjSn3XFZqOGWO3K1814O6gCgYIKoEcz1UBgi2hRANCAARKJzbiyhQQLO5KHZpgfR76FBiH78jTU2Z+M6ebmbDJQRhC80I9POfcGU3K5+AxX4NYtQpX7aHT49V6X2NhN8bx");
        byte[] plainText = "你好".getBytes(StandardCharsets.UTF_8);

        KeyFactory keyFact = KeyFactory.getInstance("EC", bc);
        // 根据采用的编码结构反序列化公私钥
        PublicKey pub = keyFact.generatePublic(new X509EncodedKeySpec(encPub));
        PrivateKey priv = keyFact.generatePrivate(new PKCS8EncodedKeySpec(encPriv));


        Signature signature = Signature.getInstance("SM3withSm2", bc);
        signature.initVerify(pub);
        signature.update(plainText);
        // 验证签名值
        boolean res = signature.verify(Hex.decode("3045022100ff9a872f21e47d4fba8f37b48a62cc2e6fdde843a40cbc96242536afc10a395e02203bbab982d1bb6a7ee5f5f6b34cd887c255ae4dcc14dd87ecae2e0392611b7a8c"));
        System.out.println(">> 验证结果:" + res);
    }

参考自 org.bouncycastle.jcajce.provider.test.GeneralKeyTest#testSM2

  • 15
    点赞
  • 118
    收藏
    觉得还不错? 一键收藏
  • 43
    评论
您好,您的问题似乎有些不明确。您是想了解如何在 Java 或 Go 中使用国密 SM2 算法进行签名验签,或是想了解如何进行 P7 格式的签名验签吗?以下是两个问题的回答: 1. 如何在 Java 或 Go 中使用国密 SM2 算法进行签名验签? 在 Java 中使用国密 SM2 算法进行签名验签,您可以使用 BouncyCastle 库。具体实现可以参考下面的代码: Java: ```java // 加载 BouncyCastle Provider Security.addProvider(new BouncyCastleProvider()); // 生成密钥对 KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("EC", "BC"); ECGenParameterSpec ecGenParameterSpec = new ECGenParameterSpec("sm2p256v1"); keyPairGenerator.initialize(ecGenParameterSpec, new SecureRandom()); KeyPair keyPair = keyPairGenerator.generateKeyPair(); // 获取私钥和公钥 PrivateKey privateKey = keyPair.getPrivate(); PublicKey publicKey = keyPair.getPublic(); // 签名 Signature signature = Signature.getInstance("SM3withSM2", "BC"); signature.initSign(privateKey); signature.update(data); byte[] signatureBytes = signature.sign(); // 验签 signature.initVerify(publicKey); signature.update(data); boolean verifyResult = signature.verify(signatureBytes); ``` Go: ```go // 生成密钥对 privateKey, err := sm2.GenerateKey() if err != nil { log.Fatal(err) } publicKey := &privateKey.PublicKey // 签名 hash := sm3.SumSM3(data) signature, err := privateKey.Sign(rand.Reader, hash[:], nil) if err != nil { log.Fatal(err) } // 验签 verifyResult := publicKey.Verify(hash[:], signature) ``` 2. 如何进行 P7 格式的签名验签? 在 Java 中进行 P7 格式的签名验签,您可以使用 BouncyCastle 库。具体实现可以参考下面的代码: Java: ```java // 加载 BouncyCastle Provider Security.addProvider(new BouncyCastleProvider()); // 读取证书和私钥 KeyStore keyStore = KeyStore.getInstance("PKCS12"); keyStore.load(new FileInputStream("keystore.p12"), "password".toCharArray()); PrivateKey privateKey = (PrivateKey) keyStore.getKey("alias", "password".toCharArray()); Certificate[] certChain = keyStore.getCertificateChain("alias"); X509Certificate cert = (X509Certificate) certChain[0]; // 加载证书链 JcaCertStore jcaCertStore = new JcaCertStore(Arrays.asList(certChain)); // 签名 ContentSigner signer = new JcaContentSignerBuilder("SM3withSM2").setProvider("BC").build(privateKey); CMSTypedData cmsData = new CMSProcessableByteArray(data); CMSSignedDataGenerator generator = new CMSSignedDataGenerator(); generator.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().build()).build(signer, cert)); generator.addCertificates(jcaCertStore); CMSSignedData signedData = generator.generate(cmsData, true); byte[] signedBytes = signedData.getEncoded(); // 验签 CMSSignedData cms = new CMSSignedData(signedBytes); SignerInformationStore signers = cms.getSignerInfos(); Collection<SignerInformation> c = signers.getSigners(); Iterator<SignerInformation> it = c.iterator(); if (it.hasNext()) { SignerInformation signer = it.next(); X509CertificateHolder certHolder = (X509CertificateHolder) cms.getCertificates().getMatches(signer.getSID()).iterator().next(); if (signer.verify(new JcaSimpleSignerInfoVerifierBuilder().build(certHolder))) { return true; } } return false; ``` Go: ```go // 读取证书和私钥 pemData, err := ioutil.ReadFile("keystore.pem") if err != nil { log.Fatal(err) } blocks := pem.Decode(pemData) key, err := x509.ParsePKCS8PrivateKey(blocks.Bytes) if err != nil { log.Fatal(err) } certData, err := ioutil.ReadFile("cert.pem") if err != nil { log.Fatal(err) } cert, err := x509.ParseCertificate(certData) if err != nil { log.Fatal(err) } // 加载证书链 certs := []*x509.Certificate{cert} intermediateData, err := ioutil.ReadFile("intermediate.pem") if err == nil { for { blocks := pem.Decode(intermediateData) if blocks == nil { break } intermediateCert, err := x509.ParseCertificate(blocks.Bytes) if err != nil { log.Fatal(err) } certs = append(certs, intermediateCert) } } // 签名 hash := sm3.SumSM3(data) signedData, err := cms.NewSignedData(hash[:], key, cert, certs...) if err != nil { log.Fatal(err) } signedBytes, err := signedData.ToDER() if err != nil { log.Fatal(err) } // 验签 cms, err := cms.ParseCMS(signedBytes) if err != nil { log.Fatal(err) } signerInfos := cms.GetSignerInfos() for _, signerInfo := range signerInfos { err = signerInfo.Verify(cert) if err == nil { return true } } return false ```
评论 43
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值