腾讯Kona Crypto是一个Java安全Provider实现,其Provider名称为KonaCrypto。它遵循相关的国家标准实现了如下的国密基础算法:
SM2,它是一个基于椭圆曲线(ECC)的公钥加密算法,在实现该算法时遵循了如下的国家标准:
SM3,它是一个密码学安全的哈希算法,在实现该算法时遵循了如下的国家标准:
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密钥对与生成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);