Java使用国密算法
什么是国密算法
国密算法是指 SM2 SM3 SM4 这3套含数据对称加解密,数据签名,数据非对称加解密功能的数据加密算法。其中 SM4 算法用于数据对称加密和解密;SM3算法用于计算数据的摘要签名;SM2算法用于数据 非对称加密和解密。在政务行业的一些政务项目或产品中,会要求使用国密算法来替代 RSA,MD5,DES等算法。
在Java项目中使用国密算法
引入相关依赖
这里我使用的是jdk1.8 的maven项目,需要在pom.xml里引入以下依赖
<dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.7.8</version></dependency><dependency><groupId>org.bouncycastle</groupId><artifactId>bcprov-jdk15on</artifactId><version>1.69</version></dependency>
Java使用SM4算法对称加密解密
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 bitString 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());//加密为HexString hexPass = sm41.encryptHex(str);System.err.println("Hex形式的密文:"+hexPass);sw.stop();System.err.println(sw.getLastTaskInfo().getTimeSeconds());sw.start();//加密为base64String 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原文:helloHex形式的密文:a18a4bbea96ca3103192d20650f8f1901.19635956base64形式的密文:oYpLvqlsoxAxktIGUPjxkA==0.001947113--------------hello--------------hello
可以看出SM4加密需要1个128bit的加密key ,后面进行加密和解密都需要这个key。
SM4加密时有2种密文形式,一种是hex,一种是base64。
经测试发现SM4返回Hex形式的密文更耗时,所以建议开发中使用SM4返回base64形式的密文。
Java使用SM3算法计算数据的摘要签名
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算法是相似的。
Java使用SM2算法进行非对称加密解密
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 */publicstatic 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);}}
测试结果
测试aaaaaaaaa04A278AAF9E455BAA2B09EA635DA71342ED507CC72F4D8E616D156BF378206CF1BC319146B863DA0C76784B1DC637E72BFA21D98A82169816AEF98B83F0F29AC97AC951FFF72300B64A9D438749C7F6D7AEFC7581D314CB01C9ABA96DD7591E0DF3990069E1BB8E7923B29B0F5FF12AD测试aaaaaaaaa
SM2算法的加密和解密过程类似于RSA加解密。
可以看出SM2加密需要1个密钥对 ,后面进行加密和解密都需要这个密钥对,
这个密钥对可以分开保存成2个pem文件,
程序员可以从pem文件里 读取 公钥 和 私钥,再进行 公钥加密,私钥解密。