国密算法是指 SM2 SM3 SM4 这3套含数据对称加解密,数据签名,数据非对称加解密功能的数据加密算法。其中 SM4 算法用于数据对称加密和解密;SM3算法用于计算数据的摘要签名;SM2算法用于数据 非对称加密和解密。
在政务行业的一些政务项目或产品中,会要求使用国密算法来替代 RSA,MD5,DES等算法。
这里我使用的是jdk1.8 的maven项目,需要在pom.xml里引入以下依赖
<dependency>
<groupId>cn.hutoolgroupId>
<artifactId>hutool-allartifactId>
<version>5.7.8version>
dependency>
<dependency>
<groupId>org.bouncycastlegroupId>
<artifactId>bcprov-jdk15onartifactId>
<version>1.69version>
dependency>
package cn.demo.sm;
import cn.hutool.core.date.StopWatch;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.crypto.SmUtil;
import cn.hutool.crypto.symmetric.SM4;
public class SM4Test {
/**
* sm4 对称加密 ,需要1个加密key
* @param args
*/
public static void main(String[] args) {
//SM4 requires a 128 bit key
//需要一个长度为16的字符串 16*8=128 bit
String key = RandomUtil.randomString(16);
System.err.println("生成1个128bit的加密key:"+key);
//原文
String str = "hello";
System.err.println("原文:"+str);
StopWatch sw = StopWatch.create("q11");
sw.start();
SM4 sm41 = SmUtil.sm4(key.getBytes());
//加密为Hex
String hexPass = sm41.encryptHex(str);
System.err.println("Hex形式的密文:"+hexPass);
sw.stop();
System.err.println(sw.getLastTaskInfo().getTimeSeconds());
sw.start();
//加密为base64
String base64Pass = sm41.encryptBase64(str);
System.err.println("base64形式的密文:"+base64Pass);
sw.stop();
System.err.println(sw.getLastTaskInfo().getTimeSeconds());
System.err.println("--------------");
//hex解密
String s = sm41.decryptStr(hexPass);
System.out.println(s);
System.out.println("--------------");
//base64解密
String s2 = sm41.decryptStr(base64Pass);
System.out.println(s2);
}
}
测试结果
生成1个128bit的加密key:jsimjrby3wqb7dbq
原文:hello
Hex形式的密文:a18a4bbea96ca3103192d20650f8f190
1.19635956
base64形式的密文:oYpLvqlsoxAxktIGUPjxkA==
0.001947113
--------------
hello
--------------
hello
可以看出SM4加密需要1个128bit的加密key ,后面进行加密和解密都需要这个key。
SM4加密时有2种密文形式,一种是hex,一种是base64。
经测试发现SM4返回Hex形式的密文更耗时,所以建议开发中使用SM4返回base64形式的密文。
package cn.demo.sm;
import cn.hutool.crypto.SmUtil;
public class SM3Test {
/**
* sm3 摘要签名算法
* @param args
*/
public static void main(String[] args) {
String str = "hello2023";
/**
* str重复执行sm3签名后的值 是相同的
*/
String s = SmUtil.sm3(str);
System.out.println(s);
}
}
测试结果
69f5c5c5413eaf9543b1e35ce6aa60d0eab217764e3f9d621e30785c8471e08f
可以看出SM3摘要签名算法 是可以重复执行,且多次执行结果一致的。
需要注意的是SM3 算法是不可逆的,只能从原文得到摘要签名,不能从摘要签名 反向得到原文,
这一点和MD5算法是相似的。
package cn.demo.sm;
import cn.hutool.core.codec.Base64;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.SmUtil;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.SM2;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import java.io.*;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;
public class SM2Utils {
static final BouncyCastleProvider bc = new BouncyCastleProvider();
public static Map<String,Object> generateKey(){
KeyPair pair = SecureUtil.generateKeyPair("SM2");
Map<String,Object> map = new HashMap<>();
map.put("publicKey",pair.getPublic());
map.put("privateKey",pair.getPrivate());
return map;
}
public static String encrypt(String body, PublicKey aPublic){
SM2 sm2 = SmUtil.sm2();
sm2.setPublicKey(aPublic);
String s = sm2.encryptBcd(body, KeyType.PublicKey);
return s;
}
public static String decrypt(String data, PrivateKey privateKey){
SM2 sm2obj = SmUtil.sm2();
sm2obj.setPrivateKey(privateKey);
String decStr = StrUtil.utf8Str(sm2obj.decryptFromBcd(data,KeyType.PrivateKey));
return decStr;
}
/**
* 从字符串中读取 私钥 key
* @param privateKeyStr String
* @return PrivateKey
*/
public static PrivateKey strToPrivateKey(String privateKeyStr){
PrivateKey privateKey = null;
try {
byte[] encPriv = Base64.decode(privateKeyStr);
KeyFactory keyFact = KeyFactory.getInstance("EC", bc);
privateKey = keyFact.generatePrivate(new PKCS8EncodedKeySpec(encPriv));
}catch (Exception e){
e.printStackTrace();
}
return privateKey;
}
/**
* 从字符串中读取 公钥 key
* @param publicKeyStr String
* @return PublicKey
*/
public static PublicKey strToPublicKey(String publicKeyStr){
PublicKey publicKey = null;
try {
byte[] encPub = Base64.decode(publicKeyStr);
KeyFactory keyFact = KeyFactory.getInstance("EC", bc);
publicKey = keyFact.generatePublic(new X509EncodedKeySpec(encPub));
}catch (Exception e){
e.printStackTrace();
}
return publicKey;
}
/**
* 公钥 key 转文件
* @param publicKey PublicKey
* @param path String
*/
public static void exportPublicKey(PublicKey publicKey,String path){
File file = new File(path);
try {
if (!file.exists()){
file.createNewFile();
}
byte[] encPub = publicKey.getEncoded();
FileOutputStream fos = new FileOutputStream(file);
fos.write(encPub);
fos.close();
}catch (IOException e){
e.printStackTrace();
}
}
/**
* 私钥 key 转文件
* @param privateKey PrivateKey
* @param keyPath String
*/
public static void exportPrivateKey(PrivateKey privateKey, String keyPath){
File file = new File(keyPath);
try {
if (!file.exists()){
file.createNewFile();
}
byte[] encPriv = privateKey.getEncoded();
FileOutputStream fos = new FileOutputStream(file);
fos.write(encPriv);
fos.close();
}catch (IOException e){
e.printStackTrace();
}
}
public static PublicKey importPublicKey(String path){
File file = new File(path);
try {
if (!file.exists()){
return null;
}
FileInputStream fis = new FileInputStream(file);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[16];
int size;
while ((size = fis.read(buffer)) != -1){
baos.write(buffer,0,size);
}
fis.close();
byte[] bytes = baos.toByteArray();
String publicKeyStr = Base64.encode(bytes);
return strToPublicKey(publicKeyStr);
}catch (IOException e){
e.printStackTrace();
}
return null;
}
public static PrivateKey importPrivateKey(String keyPath){
File file = new File(keyPath);
try {
if (!file.exists()){
return null;
}
FileInputStream fis = new FileInputStream(file);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte buffer[] = new byte[16];
int size;
while ((size = fis.read(buffer)) != -1){
baos.write(buffer,0,size);
}
fis.close();
byte[] bytes = baos.toByteArray();
String privateKeyStr = Base64.encode(bytes);
return strToPrivateKey(privateKeyStr);
}catch (Exception e){
e.printStackTrace();
}
return null;
}
}
package cn.demo.sm;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.SmUtil;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.SM2;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
public class SM2Test {
public static void test1(){
String text = "测试aaaaaaaaa";
SM2 sm2 = SmUtil.sm2();
//第一种 使用随机密钥对
String s = sm2.encryptBcd(text, KeyType.PublicKey);
System.out.println(s);
String s1 = StrUtil.utf8Str(sm2.decryptFromBcd(s, KeyType.PrivateKey));
System.out.println(s1);
}
public static void test2(){
String text = "测试aaaaaaaaa";
System.err.println(text);
//第二种 使用自定义密钥对
KeyPair keyPair = SecureUtil.generateKeyPair("SM2");
byte[] priKey = keyPair.getPrivate().getEncoded();
byte[] pubKey = keyPair.getPublic().getEncoded();
System.err.println("=====================================");
SM2 sm2obj = SmUtil.sm2(priKey,pubKey);
//公钥加密,私钥解密
String encStr = sm2obj.encryptBcd(text,KeyType.PublicKey);
System.err.println(encStr);
String decStr = StrUtil.utf8Str(sm2obj.decryptFromBcd(encStr,KeyType.PrivateKey));
System.err.println(decStr);
}
public static void main(String[] args) {
//第一种 使用随机密钥对
// test1();
//第二种 使用自定义密钥对
// test2();
//第三种 生成pem文件,从pem文件里 读取 公钥 和 私钥,再进行 公钥加密,私钥解密
test3();
}
/**
* SM2非对称加密
*/
public static void test3() {
String text = "测试aaaaaaaaa";
System.err.println(text);
KeyPair pair = SecureUtil.generateKeyPair("SM2");
PublicKey aPublic = pair.getPublic();
PrivateKey aPrivate = pair.getPrivate();
//公钥 key 和私钥 key 转文件
SM2Utils.exportPublicKey(aPublic,"F:/sm2/public_key.pem");
SM2Utils.exportPrivateKey(aPrivate,"F:/sm2/private_key.pem");
//从pem文件里 读取 公钥 和 私钥
PublicKey pubk2 = SM2Utils.importPublicKey("F:/sm2/public_key.pem");
PrivateKey priK2 = SM2Utils.importPrivateKey("F:/sm2/private_key.pem");
//公钥加密
SM2 sm2 = SmUtil.sm2();
sm2.setPublicKey(pubk2);
String encStr = sm2.encryptBcd(text, KeyType.PublicKey);
System.err.println(encStr);
//私钥解密
SM2 sm2obj = SmUtil.sm2();
sm2obj.setPrivateKey(priK2);
String decStr = StrUtil.utf8Str(sm2obj.decryptFromBcd(encStr,KeyType.PrivateKey));
System.err.println(decStr);
}
}
测试结果
测试aaaaaaaaa
04A278AAF9E455BAA2B09EA635DA71342ED507CC72F4D8E616D156BF378206CF1BC319146B863DA0C76784B1DC637E72BFA21D98A82169816AEF98B83F0F29AC97AC951FFF72300B64A9D438749C7F6D7AEFC7581D314CB01C9ABA96DD7591E0DF3990069E1BB8E7923B29B0F5FF12AD
测试aaaaaaaaa
SM2算法的加密和解密过程类似于RSA加解密。
可以看出SM2加密需要1个密钥对 ,后面进行加密和解密都需要这个密钥对,
这个密钥对可以分开保存成2个pem文件,
程序员可以从pem文件里 读取 公钥 和 私钥,再进行 公钥加密,私钥解密。