腾讯国密套件KonaCrypto使用(SM2加密)

介绍

腾讯Kona Crypto是一个Java安全Provider实现,其Provider名称为KonaCrypto。它遵循相关的国家标准实现了如下的国密基础算法:

SM2,它是一个基于椭圆曲线(ECC)的公钥加密算法,在实现该算法时遵循了如下的国家标准:

  • GB/T 32918.1-2016 第1部分:总则
  • GB/T 32918.2-2016 第2部分:数字签名
  • GB/T32918.3-2016 第3部分:密钥交换协议
  • GB/T 32918.4-2016 第4部分:公钥加密算法
  • GB/T 32918.5-2017 第5部分:参数定义

SM3,它是一个密码学安全的哈希算法,在实现该算法时遵循了如下的国家标准:

  • GB/T 32905-2016 SM3密码杂凑算法

SM4,它是一个分组加密算法,在实现该算法时遵循了如下的国家标准:

  • GB/T 32907-2016 SM4分组密码算法

为了提供上述特性,KonaCrypto基于JDK标准的Java Cryptography Architecture (JCA)框架,实现了JDK定义的KeyPairGeneratorSpi,SignatureSpi,CipherSpi,MessageDigestSpi,MacSpi和KeyAgreementSpi等Service Provider Interface (SPI)。

使用

由于KonaCrypto是基于JCA框架的,所以在使用风格上,与其它的JCA实现(如JDK自带的SunJCE和SunEC)是一样的。正常地,应用程序并不需要直接访问KonaCrypto中的算法实现类,而是通过相关的JDK API去调用指定算法的实现。了解JCA的设计原理与代码风格,对于应用KonaCrypto是非常有帮助的,请阅读官方的[参考指南]。

引入依赖

        <!-- https://mvnrepository.com/artifact/com.tencent.kona/kona-crypto -->
        <dependency>
            <groupId>com.tencent.kona</groupId>
            <artifactId>kona-crypto</artifactId>
            <version>1.0.8</version>
        </dependency>

加载

在使用KonaCrypto中的任何特性之前,必须要加载KonaCryptoProvider,

Security.addProvider(new KonaCryptoProvider());

上面的方法会将这个Provider加到整个Provider列表的最后一位,其优先级则为最低。如有必要,可以使用下面的方法将它们插入到Provider列表的指定位置,

Security.insertProviderAt(new KonaCryptoProvider(), position);

position的值越小,代表的优先级越高,最小可为1。

SM2

密钥对

生成SM2密钥对与生成JDK自带的其它算法(如EC)密钥对的方式是完全相同的,仅需要调用标准的JDK API就可以生成密钥对。

创建KeyPairGenerator实例。

KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("SM2");

生成密钥对。

KeyPair keyPair = keyPairGenerator.generateKeyPair();
ECPublicKey publicKey = (ECPublicKey) keyPair.getPublic();
ECPrivateKey privateKey = (ECPrivateKey) keyPair.getPrivate();

SM2的密钥对本质还是EC密钥对,所以其中的公钥与私钥也分别符合ECPublicKey与ECPrivateKey的属性。

SM2公钥的编码长度固定为65字节,其格式为04|x|y,其中04表示非压缩格式,x和y分别为该公钥点在椭圆曲线上的仿射横坐标和纵坐标的值。

byte[] encodedPublicKey = publicKey.getEncoded();

SM2私钥的编码长度固定为32字节,无编码格式。

byte[] encodedPrivateKey = privateKey.getEncoded();

关于密钥对生成器API的更详细用法,请参考KeyPairGenerator的官方文档。

准备公钥与私钥

一般情况下,在签名和加密操作中,都是使用已有的密钥对,并不需要临时生成。所以需要像下面那样,读取公钥与私钥数据,分别生成PublicKey和PrivateKey对象。

byte[] encodedPublicKey = <编码形式的公钥>;
byte[] encodedPrivateKey = <编码形式的私钥>;

KeyFactory keyFactory = KeyFactory.getInstance("SM2");

SM2PublicKeySpec publicKeySpec = new SM2PublicKeySpec(encodedPublicKey);
PublicKey publicKey = keyFactory.generatePublic(publicKeySpec);

SM2PrivateKeySpec privateKeySpec = new SM2PrivateKeySpec(encodedPrivateKey);
PrivateKey privateKey = keyFactory.generatePrivate(privateKeySpec);

签名

使用SM2签名算法与使用JDK自带的其它签名算法(如ECDSA)的方式是非常相似的,但在参数设置上需要使用自定义API。

创建Signature实例。

Signature signature = Signature.getInstance("SM2);

使用私钥进行初始化,以准备进行签名操作。

signature.initSign(privateKey);

上面使用的是一种简约的初始化方式,它会使用默认的SM2 ID,即1234567812345678。它还会使用私钥去计算出公钥,因为根据规范,公钥也要参与签名值的计算过程,这是与国际签名算法(如ECDSA)的一个重大不同点。但计算公钥会有一定的开销,对性能会有负面影响。

如果要使用非默认ID,或者不希望额外地计算公钥,则在初始化时之前需要额外设置一个定制的AlgorithmParameterSpec实例,即SM2SignatureParameterSpec。

byte[] altID = <定制化的ID>;
ECPublicKey publicKey = <公钥>;
SM2SignatureParameterSpec paramSpec = new SM2SignatureParameterSpec(altID, publicKey);
signature.setParameter(paramSpec);
signature.initSign(privateKey);

参数设置与初始化完成之后,就可以传入被签名的消息数据了。

byte[] message = <被签名的消息数据>;
signature.update(message);

生成签名值。

byte[] sign = signature.sign();

SM2签名值使用ASN.1格式进行编码,其长度在71到73字节之间。

使用公钥进行初始化,以准备进行验签操作。

signature.initVerify(publicKey);

传入被签名的消息数据。

byte[] message = <被签名的消息数据>;
signature.update(message);

再传入已生成的签名值进行验证,

boolean verified = signature.verify(sign);

如果验证成功,返回true,否则返回false。

必须要注意的是,私钥用于签名,公钥用于验签。关于签名算法API的更详细用法,请参考Signature的官方文档。

加密

出于性能考虑,与其它的非对称加密算法(如RSA和EC)相同,SM2加密算法一般只用于加密少量的关键性数据。

创建Cipher实例,

Cipher cipher = Cipher.getInstance("SM2");

使用公钥对Cipher进行初始化,指定其使用加密模式。

cipher.init(Cipher.ENCRYPT_MODE, publicKey);

传入消息数据生成密文。

byte[] message = <被加密的消息数据>;
byte[] ciphertext = cipher.doFinal(message);

使用私钥对Cipher进行初始化,指定其使用解密模式。

cipher.init(Cipher.DECRYPT_MODE, privateKey);

传入密文生成明文。

byte[] cleartext = cipher.doFinal(ciphertext);

必须要注意的是,公钥用于加密,私钥用于解密。关于加密算法API的更详细用法,请参考Cipher的官方文档。

下面是一些简单的应用代码,具体如何使用要根据自己的项目情况来决定。

     // 在使用KonaCrypto中的任何特性之前,必须要加载KonaCryptoProvider,
            Security.addProvider(new KonaCryptoProvider());
            KeyFactory keyFactory = KeyFactory.getInstance("SM2");
            // 创建KeyPairGenerator实例,
            SM2KeyPairGenerator sm2KeyPairGenerator = new SM2KeyPairGenerator();
            // 生成随机密钥对。
            KeyPair keyPair = sm2KeyPairGenerator.generateKeyPair();
            // SM2的密钥对本质还是EC密钥对,所以其中的公钥与私钥也分别符合ECPublicKey与ECPrivateKey的属性。
            //SM2公钥的编码长度固定为65字节,其格式为04|x|y,其中04表示非压缩格式,x和y分别为该公钥点在椭圆曲线上的仿射横坐标和纵坐标的值。
            ECPublicKey publicKey1 = (ECPublicKey) keyPair.getPublic();
//            // SM2私钥的编码长度固定为32字节,无编码格式。
            ECPrivateKey privateKey1 = (ECPrivateKey) keyPair.getPrivate();

            byte[] encodedPublicKey = publicKey1.getEncoded();
            byte[] encodedPrivateKey = privateKey1.getEncoded();
            // 将公钥私钥转换输出
            String PrivateKey = Base64.getEncoder().encodeToString(encodedPrivateKey);
            String PublicKey = Base64.getEncoder().encodeToString(encodedPublicKey);
            System.out.println("Public:"+ PublicKey);
            System.out.println("Private:"+ PrivateKey);
            // 公钥
            SM2PublicKeySpec publicKeyKeySpec = new SM2PublicKeySpec(encodedPublicKey);
            PublicKey publicKey = keyFactory.generatePublic(publicKeyKeySpec);
            // 私钥
            SM2PrivateKeySpec privateKeySpec = new SM2PrivateKeySpec(encodedPrivateKey);
            PrivateKey privateKey = keyFactory.generatePrivate(privateKeySpec);

            // 要加密的数据2
            byte[] data = ("这里是要加密的内容").getBytes("UTF-8");
            // 创建Cipher实例,
            Cipher cipher = Cipher.getInstance("SM2");
            // 使用公钥对Cipher进行初始化,指定其使用加密模式。
            cipher.init(Cipher.ENCRYPT_MODE, publicKey);
            // 传入密文生成明文。
            byte[] ciphertext = cipher.doFinal(data);


            // 使用私钥对Cipher进行初始化,指定其使用解密模式。
            cipher.init(Cipher.DECRYPT_MODE, privateKey);
            String jiamijieguo = Base64.getEncoder().encodeToString(ciphertext);
            System.out.println("加密结果:"+Base64.getEncoder().encodeToString(ciphertext));
            // 传入密文生成明文。
            byte[] cleartext = cipher.doFinal(Base64.getDecoder().decode(jiamijieguo));
            System.out.println("解密结果:"+ new String(cleartext, StandardCharsets.UTF_8));


            // 签名
            Signature signature = Signature.getInstance("SM2");
            signature.initSign(privateKey);
            signature.update(cleartext);
            byte[] sign = signature.sign();
            System.out.println("sign:"+Base64.getEncoder().encodeToString(sign));

            // 验签
            signature.initVerify(publicKey);
            signature.update(cleartext);
            boolean verified = signature.verify(sign);
            System.out.println("验签结果:"+verified);

猜你喜欢

转载自blog.csdn.net/Soncat2000/article/details/130807625