Skip to content

SM2密码算法 JAVA 调用演示程序。

Notifications You must be signed in to change notification settings

sxyseo/alg-sm2-demo

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

24 Commits
 
 
 
 
 
 
 
 

Repository files navigation

SM2密码算法 JAVA 调用Demo

[TOC]

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

密钥对生成

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();

关于椭圆曲线的推荐参数请参考 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


JAVA SM2 数字证书生成Demo

Before Start

X.509数字证书请参考:

RFC5280 Internet X.509 Public Key Infrastructure Certificate and Certificate Revocation List (CRL) Profile

中文版的简要介绍可以参考这篇文章 Agzs . X509证书--ANS1结构

如果还未能生成SM2密钥对请先阅读

Build with Maven

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

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

QuickStart

Github

/**
 * BouncyCastle算法提供者
 */
private static final Provider BC = new BouncyCastleProvider();

生成自签名公私钥对

keyPairGenerator的构造请参考 JAVA SM2 密钥生成 签名验签

// 产生密钥对
KeyPair keyPair = keyPairGenerator.generateKeyPair();

证书签名算法算法提供者

在制作证书时需要使用到签名算法签名证书中部分数据区域,国密类型的数字证书使用的签名算法是SM3withSM2,这里使用私钥创建算法提供容器。

ContentSigner sigGen = new JcaContentSignerBuilder("SM3withSM2")
        .setProvider(BC)
        .build(keyPair.getPrivate());

设置证书信息

设置证书的基本数据:使用者信息、颁发者信息、证书序号、证书生效日期、证书失效日期,以及证书扩展属性。

标识信息构造(DN)

上面提到的使用者信息、颁发者信息,使用Distinct Name的方式来描述。

关于DN中的各个字段的含义请参考 IBM Previous Next Distinguished Names

实际上简单来讲就是,用来确定“实体” 身份/信息 的一系列列键值对组成的字符串。

这里的键是一个ASN1ObjectIdentifier,实际上Bouncycastle已经为我们把需要的大多键都已经列好了,我们只要使用这个类org.bouncycastle.asn1.x500.style.BCStyle的静态变量就可以。

private static X500NameBuilder createStdBuilder() {
    X500NameBuilder builder = new X500NameBuilder(BCStyle.INSTANCE);
    // 国家代码
    builder.addRDN(BCStyle.C, "CN");
    // 组织
    builder.addRDN(BCStyle.O, "HZNU");
    // 省份
    builder.addRDN(BCStyle.ST, "Zhejiang");
    // 地区
    builder.addRDN(BCStyle.L, "Hangzhou");
    return builder;
}

然后我们就可以使用X500NameBuilderbuild()方法构造对应的DN了。

BCStyle 源码请参考,也可以使用与该文件中处于同级目录的RFC4519Style

获取扩展密钥用途构造(可选)

如果需要设置证书的扩展密钥用途,可以使用DERSequence来构造一个拓展密钥用途的序列。

拓展密钥用途

public static DERSequence extendedKeyUsage() {
        // 构造容器对象
        ASN1EncodableVector vector = new ASN1EncodableVector();
        // 客户端身份认证
        vector.add(KeyPurposeId.id_kp_clientAuth);
        // 安全电子邮件
        vector.add(KeyPurposeId.id_kp_emailProtection);
        return new DERSequence(vector);
}

扩展密钥用途的各个OID Bouncycastle 已经为我们提供,请参考KeyPurposeId

证书信息构造

接下来可以使用X509v3CertificateBuilder 来设置证书的基本参数,下面列举基本一些证书参数和扩展参数的设置方式。

// 构造X.509 第3版的证书构建者
X509v3CertificateBuilder certGen = new JcaX509v3CertificateBuilder(
        // 颁发者信息
        createStdBuilder().build()
        // 证书序列号
        , BigInteger.valueOf(1)
        // 证书生效日期
        , new Date(System.currentTimeMillis() - 50 * 1000)
        // 证书失效日期
        , new Date(System.currentTimeMillis() + 50 * 1000)
        // 使用者信息(PS:由于是自签证书,所以颁发者和使用者DN都相同)
        , createStdBuilder().build()
        // 证书公钥
        , keyPair.getPublic())
        /*
        设置证书扩展
        证书扩展属性,请根据需求设定,参数请参考 《RFC 5280》
         */
        // 设置密钥用法
        .addExtension(Extension.keyUsage, false
                , new X509KeyUsage(X509KeyUsage.digitalSignature | X509KeyUsage.nonRepudiation))
        // 设置扩展密钥用法:客户端身份认证、安全电子邮件
        .addExtension(Extension.extendedKeyUsage, false, extendedKeyUsage())
        // 基础约束,标识是否是CA证书,这里false标识为实体证书
        .addExtension(Extension.basicConstraints, false, new BasicConstraints(false))
        // Netscape Cert Type SSL客户端身份认证
        .addExtension(MiscObjectIdentifiers.netscapeCertType, false, new NetscapeCertType(NetscapeCertType.sslClient));

更多 Netscape Cert Type 类型请参考:NetscapeCertType

X.509格式证书对象生成

通过使用证书构造好的信息,接下就能够生成x509格式的证书对象

// 将证书构造参数装换为X.509证书对象
X509Certificate certificate = new JcaX509CertificateConverter()
        .setProvider(BC)
        .getCertificate(certGen.build(sigGen));

保存证书

如果需要永久的保存刚才生成的这份证书,那么我们需要对这个证书进行序列化,常见的证书序列化是将ASN.1编码的证书对象,进行BASE64编码,这样证书更加便于网络传输。

public static void makeCertFile(X509Certificate x509Certificate, Path savePath) throws CertificateEncodingException, IOException {
    if (Files.exists(savePath)) {
        // 删除已存在文件
        Files.deleteIfExists(savePath);
    }
    // 创建文件
    Files.createFile(savePath);
    // 获取ASN.1编码的证书字节码
    byte[] asn1BinCert = x509Certificate.getEncoded();
    // 编码为BASE64 便于传输
    byte[] base64EncodedCert = Base64.encode(asn1BinCert);
    // 写入文件
    Files.write(savePath, base64EncodedCert);
}

给与文件名称,调用保存(常见的DER编码的证书后缀名为.cer)。

// 保存为证书文件
makeCertFile(certificate, Paths.get("test-cert.cer"));

生成的证书截图

ASN.1 结构解析工具

asn1js

通过该工具,输入证书的BASE64编码的字符串,我们能够快速分析证书的结构,以及各种字段。 截图

致谢

在编写代码时参考了下面的例子:

参考列表:

About

SM2密码算法 JAVA 调用演示程序。

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • Java 100.0%