前言
市面上关于国密的文章其实已经很多了,我在好多年前也写过使用 bouncycastle 库的博客,但我用的那个bc库对国密的支持不能说没有吧,只能说坑很多。具体版本是1.57,现在你去百度搜索“bc库国密踩坑”这几个关键字,不出意外的话出来的第一个结果就是我曾经写的踩坑指南。阅读量还不少,可见踩坑的人也不少。后来我想升级,但发现新版的写法变化很大,都变化这么大了,我干嘛不找找别的国密库呢。
正文开始
在一次看国内开源项目的时候看到阿里的铜锁、腾讯国密Kona套件(腾讯有个jdk也加kona),不过铜锁好像没有java版,然后我就选择了kona作为新的国密组件。
那kona和BC库的使用又有什么不一样呢,它遵循标准的JCA框架实现了国密密码学算法,这个还是比较吸引人的,如果从RSA改为SM2可以说只需要很小的改动就可以了,当然你的RSA也需要时标准的写法。官方文档有些细节的地方没写出来,可以参考我的笔记。
首先是依赖
需要最新版去看github仓库的最新release。
<dependency>
<groupId>com.tencent.kona</groupId>
<artifactId>kona-crypto</artifactId>
<version>1.0.12</version>
</dependency>
kona套件提供了国密证书的解析与验证和国密传输层的支持,这里我们只需要SM2 SM3 SM4,所以只需要引入 kona-crypto 就可以了。
然后可能还需要一个16进制转换相关的包,我用的是 org.apache.commons 包提供的 HEX 工具类
使用
辅助类
这里我创建了一个辅助类来封装公钥私钥对。
public class KeyPairOfString {
/**公钥*/
private String publicKey;
/**私钥*/
private String privateKey;
/**
* 无参构造方法
*/
public KeyPairOfString(){
super();
}
/**
* 构造方法
* @param publicKey
* @param privateKey
*/
public KeyPairOfString(String publicKey, String privateKey){
this.publicKey = publicKey;
this.privateKey = privateKey;
}
}
加载
在使用KonaCrypto中的任何特性之前,必须要加载KonaCryptoProvider,所以我是所有调用国密方法中都加上了下面的代码
// 内部已经做了判断,重复调用不影响
Security.addProvider(new KonaCryptoProvider());
创建密钥对
/**
* 生成SM2密钥对并放入自定义封装中
*/
public static KeyPairOfString generateSm2KeyPair() {
// 在使用KonaCrypto中的任何特性之前,必须要加载KonaCryptoProvider
Security.addProvider(new KonaCryptoProvider());
KeyPairGenerator keyPairGenerator = null;
try {
// 标准JCA写法
keyPairGenerator = KeyPairGenerator.getInstance("SM2");
} catch (NoSuchAlgorithmException e) {
// 转换为运行时异常
throw new RuntimeException(e);
}
// 拿到密钥对象
KeyPair keyPair = keyPairGenerator.generateKeyPair();
ECPublicKey publicKey = (ECPublicKey) keyPair.getPublic();
ECPrivateKey privateKey = (ECPrivateKey) keyPair.getPrivate();
// 将公钥和私钥转换成16进制字符串,方便传输存储
// 公钥为长度为65字节,格式为04||x||y,其中04表示非压缩格式,x和y分别为该公钥点在椭圆曲线上的仿射横坐标和纵坐标的值
String publickeyHex = Hex.encodeHexString(publicKey.getEncoded());
// 私钥长度为32字节
String privateHex = Hex.encodeHexString(privateKey.getEncoded());
return new KeyPairOfString(publickeyHex, privateHex);
}
SM2公钥加密
国密官方文档中密文排序格式为C1||C3||C2,最早的版本有过C1||C2||C3的标准,所以导致某些库的老版本支持C1||C2||C3的模式。
注意:一般js库或者BC库,都没有按照ASN.1 DER 格式来处理。但是国密标准中密文传输推荐采用ASN.1 DER 格式,所以Kona默认生成和接受的都是该格式的数据,和这些工具对接时要注意转换,Kona提供了一个转换工具 SM2Ciphertext
- C1||C3||C2 with ASN.1 DER 格式转成原始的C1C3C2格式
// C1||C3||C2 with ASN.1 DER 格式转成原始的C1C3C2格式
byte[] rawC1C3C2 = SM2Ciphertext.builder()
.format(SM2Ciphertext.Format.DER_C1C3C2) // 指定输入数据的格式
.encodedCiphertext(temp) // 按输入格式进行编码
.build()
.rawC1C3C2(); // 转换格式
- 将原始的C1C3C2格式转换成C1||C3||C2 with ASN.1 DER 格式
// 将原始的C1C3C2格式转换成C1||C3||C2 with ASN.1 DER 格式
// js加密的数据一般都不带04前缀,没有的情况补上
if (!ciphertext.startsWith("04")){
ciphertext = "04" + ciphertext;
}
byte[] derC1C3C2 = SM2Ciphertext.builder()
.format(SM2Ciphertext.Format.RAW_C1C3C2)
.encodedCiphertext(ciphertext)
.build()
.derC1C3C2();
- 使用公钥加密
/**
* sm2公钥加密
* <p>出于性能考虑,与其它的非对称加密算法(如RSA和EC)相同,SM2加密算法一般只用于加密少量的关键性数据.</p>
* <p>生成的格式为标准的C1||C3||C2 with ASN.1 DER 格式,如需其他格式请自行转换</p>
* @param publicKeyStr sm2公钥(16进制字符串)
* @param data 待加密数据
* @return 加密后的数据(16进制字符串,C1||C3||C2 with ASN.1 DER 格式)
*/
public static String encrypt(String publicKeyStr, String data){
// 在使用KonaCrypto中的任何特性之前,必须要加载KonaCryptoProvider
Security.addProvider(new KonaCryptoProvider());
try {
KeyFactory keyFactory = KeyFactory.getInstance("SM2");
SM2PublicKeySpec publicKeySpec = new SM2PublicKeySpec(Hex.decodeHex(publicKeyStr));
ECPublicKey publicKey = (ECPublicKey)keyFactory.generatePublic(publicKeySpec);
Cipher cipher = Cipher.getInstance("SM2");
// 使用公钥对Cipher进行初始化,指定其使用加密模式。
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
// 待加密数据转成byte并加密
byte[] message = data.getBytes(CHARSETS_DEFULT);
byte[] ciphertext = cipher.doFinal(message);
return Hex.encodeHexString(ciphertext);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
SM2私钥解密
/**
* sm2私钥解密,密文格式为C1||C3||C2 with ASN.1 DER 格式,如果是其他格式请先使用SM2Ciphertext工具类进行转换
* @param privateKeyStr sm2私钥(16进制字符串)
* @param data 待解密数据(16进制字符串,C1||C3||C2 with ASN.1 DER 格式)
*/
public static String decrypt(String privateKeyStr, String data) {
try {
// 在使用KonaCrypto中的任何特性之前,必须要加载KonaCryptoProvider
Security.addProvider(new KonaCryptoProvider());
// 获取密钥工厂,获取私钥对象
KeyFactory keyFactory = KeyFactory.getInstance("SM2");
SM2PrivateKeySpec privateKeySpec = new SM2PrivateKeySpec(Hex.decodeHex(privateKeyStr));
PrivateKey privateKey = keyFactory.generatePrivate(privateKeySpec);
// 初始化cipher
Cipher cipher = Cipher.getInstance("SM2");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
// 已加密数据解成byte并解密
byte[] dataByte = Hex.decodeHex(data);
byte[] cleartext = cipher.doFinal(dataByte);
return new String(cleartext, CHARSETS_DEFULT);
}catch (Exception e){
throw new RuntimeException(e);
}
}
SM3
使用SM3算法与使用JDK自带的其它哈希算法(如SHA-256)的方式是完全相同的,仅需要调用JDK API就可以生成消息摘要(哈希值)。
可以一次性输入全部消息数据,然后生成消息摘要。也可以分多次传递消息数据的片断,最后再生成消息摘要。
/**
* 使用sm3生成信息摘要
* @param content 需要生成摘要的消息内容
*/
public static String digest(String... content) {
try {
// 在使用KonaCrypto中的任何特性之前,必须要加载KonaCryptoProvider
Security.addProvider(new KonaCryptoProvider());
MessageDigest md = MessageDigest.getInstance("SM3");
// 循环调用输入消息内容
for (String s : content){
md.update(s.getBytes(CHARSETS_DEFULT));
}
// 最后再生成消息摘要
byte[] digest = md.digest();
return Hex.encodeHexString(digest);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
SM4
使用SM4算法与使用JDK自带的其它分组加密算法(如AES)的方式是完全相同的,仅需要调用JDK API就可以进行SM4加密和解密操作。KonaCrypto支持了SM4的四种分组操作模式,包括CBC,CTR,ECB和GCM,同时还支持了PKCS#7填充规范。
准备密钥,其长度为16字节。这里我只写一下 ECB 模式的写法
/**
* SM4/ECB/PKCS7Padding对称加密算法
* @param key 密钥,每两位组长一个16进制数,需要16组
*/
public static String encryptByEcb(String content, String key){
try {
// 在使用KonaCrypto中的任何特性之前,必须要加载KonaCryptoProvider
Security.addProvider(new KonaCryptoProvider());
// 这里密钥的解码方式有两种按理应该是Hex.decodeHex(key),但是这种模key.length()要等于32才行也就是两个字符标识一个16进制数, 想用16个字符就用 key.getbytes()。这里可能我也没有理解清楚,欢迎有懂得兄弟指教。
byte[] keys;
if (key.length() == 16){
keys = key.getBytes(StandardCharsets.UTF_8);
}else{
keys = Hex.decodeHex(key);
}
SecretKey secretKey = new SecretKeySpec(keys, "SM4");
// 创建Cipher实例
Cipher cipher = Cipher.getInstance(SM4_ECB_PKCS7_TRANSFORMATION);
// 初始化cipher,指定使用加密模式
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
// 传入消息内容并加密
byte[] contentByte = content.getBytes(CHARSETS_DEFULT);
byte[] ciphertext = cipher.doFinal(contentByte);
return Hex.encodeHexString(ciphertext);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* SM4/ECB/PKCS7Padding对称解密
* @param key 密钥,每两位组长一个16进制数,需要16组
*/
public static String decryptByEcb(String cipherContent, String key){
try {
// 在使用KonaCrypto中的任何特性之前,必须要加载KonaCryptoProvider
Security.addProvider(new KonaCryptoProvider());
// 兼容密钥的length为16和32的
byte[] keys;
if (key.length() == 16){
keys = key.getBytes(StandardCharsets.UTF_8);
}else{
keys = Hex.decodeHex(key);
}
SecretKey secretKey = new SecretKeySpec(keys, "SM4");
// 创建Cipher实例
Cipher cipher = Cipher.getInstance(SM4_ECB_PKCS7_TRANSFORMATION);
// 初始化cipher,指定使用解密模式
cipher.init(Cipher.DECRYPT_MODE, secretKey);
// 解密
byte[] cipherByte = Hex.decodeHex(cipherContent);
byte[] cleartext = cipher.doFinal(cipherByte);
return new String(cleartext, CHARSETS_DEFULT);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
结束语
以上只是简单的写了国密SM2、SM3、SM4的最常见的用法,如果你只是找一个能用的国密工具类,复制粘贴就可以了,但你想要多了解一点,那么还是建议你看看其他资料。
参考资料
Kona国密套件直接搜腾讯kona的话出来的可能kona jdk