JAVA集成国密SM4加解密
- 一、pom配置
- 二、代码集成
- 2.1、目录结构
- 2.2、源码
- 2.3、测试
- 三、遇到的坑
- 3.1、秘钥长度
- 3.2、转码问题
- 四、相关链接
国密算法概述:https://blog.csdn.net/qq_38254635/article/details/131801527
SM4对称算法
SM4 无线局域网标准的分组数据算法。对称加密,密钥长度和分组长度均为128位
一、pom配置
<!-- 国密 -->
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15to18</artifactId>
<version>1.66</version>
</dependency>
二、代码集成
2.1、目录结构
2.2、源码
ConfigBean.java
package com.secret.sm4;
public class ConfigBean {
/**
* 秘钥空间大小
*/
public static final int SM4_KEY_SIZE = 128;
/**
* 算法编号
*/
public static final String ALGORITHM_NAME = "SM4";
/**
* 首次加密初始向量 01030507090B0D0F11131517191B1D1F
*/
public static final byte[] SM4_KEY_IV = { 1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31 };
/**
* ECB模式串
*/
public static final String ALGORITHM_ECB_PADDING = "SM4/ECB/PKCS5Padding";
/**
* CBC模式串
*/
public static final String ALGORITHM_CBC_PADDING = "SM4/CBC/PKCS5Padding";
}
ProviderSingleton.java
package com.secret.sm4;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
public class ProviderSingleton {
private static BouncyCastleProvider instance = null;
private ProviderSingleton() {
}
private static synchronized void syncInit() {
if (instance == null) {
instance = new BouncyCastleProvider();
}
}
public static BouncyCastleProvider getInstance() {
if (instance == null) {
syncInit();
}
return instance;
}
}
SecretCommon.java
package com.secret.sm4;
import org.apache.tomcat.util.codec.binary.Base64;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.pqc.math.linearalgebra.ByteUtils;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import java.security.Security;
/**
* SM4分组对称密码算法(对称算法)
* SM4分组密码算法是我国自主设计的分组对称密码算法,用于实现数据的加密/解密运算,以保证数据和信息的机密性。
* 要保证一个对称密码算法的安全性的基本条件是其具备足够的密钥长度,SM4算法与AES算法具有相同的密钥长度分组长度128比特,因此在安全性上高于3DES算法。
*/
public class SecretCommon {
static {
Security.addProvider(new BouncyCastleProvider());
}
/**
* 生成秘钥
*/
public static String generateKey() throws NoSuchAlgorithmException {
return ByteUtils.toHexString(generateKeyByte(ConfigBean.SM4_KEY_SIZE, ProviderSingleton.getInstance()));
}
/**
* 生成秘钥
*/
public static String generateKeyBC() throws NoSuchProviderException, NoSuchAlgorithmException {
return ByteUtils.toHexString(generateKeyByte(ConfigBean.SM4_KEY_SIZE, BouncyCastleProvider.PROVIDER_NAME));
}
public static byte[] generateKeyByte(int keySize, String var) throws NoSuchAlgorithmException, NoSuchProviderException {
KeyGenerator keyGenerator = KeyGenerator.getInstance(ConfigBean.ALGORITHM_NAME, var);
keyGenerator.init(keySize, new SecureRandom());
return keyGenerator.generateKey().getEncoded();
}
public static byte[] generateKeyByte(int keySize, BouncyCastleProvider var) throws NoSuchAlgorithmException {
KeyGenerator keyGenerator = KeyGenerator.getInstance(ConfigBean.ALGORITHM_NAME, var);
keyGenerator.init(keySize, new SecureRandom());
return keyGenerator.generateKey().getEncoded();
}
/**
* ECB模式,加密
* @param plainText 明文字符串
* @param keyText 秘钥内容
*/
public static String encryptECB(String plainText, String keyText) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
return Base64.encodeBase64String(cipherECB(Cipher.ENCRYPT_MODE, plainText.getBytes(StandardCharsets.UTF_8), keyText.getBytes()));
}
/**
* ECB模式,解密
* @param cipherText 密文字符串
* @param keyText 秘钥内容
*/
public static String decryptECB(String cipherText, String keyText) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
return new String(cipherECB(Cipher.DECRYPT_MODE, Base64.decodeBase64(cipherText), keyText.getBytes()));
}
/**
* ECB模式,加解密算法基础方法
* @param mode 加解密模式 Cipher.ENCRYPT_MODE 1:加密/ Cipher.DECRYPT_MODE 2:解密
* @param plainByte 需加密明文内容/待解密密文内容
* @param keyByte 秘钥内容
*/
public static byte[] cipherECB(int mode, byte[] plainByte, byte[] keyByte) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
Cipher cipher = Cipher.getInstance(ConfigBean.ALGORITHM_ECB_PADDING, ProviderSingleton.getInstance());
cipher.init(mode, new SecretKeySpec(keyByte, ConfigBean.ALGORITHM_NAME));
return cipher.doFinal(plainByte);
}
/**
* CBC模式,加密
* @param plainText 明文字符串
* @param keyText 秘钥内容
*/
public static String encryptCBC(String plainText, String keyText) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
return Base64.encodeBase64String(cipherCBC(Cipher.ENCRYPT_MODE, plainText.getBytes(StandardCharsets.UTF_8), keyText.getBytes()));
}
/**
* CBC模式,解密
* @param cipherText 密文字符串
* @param keyText 秘钥内容
*/
public static String decryptCBC(String cipherText, String keyText) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
return new String(cipherCBC(Cipher.DECRYPT_MODE, Base64.decodeBase64(cipherText), keyText.getBytes()));
}
/**
* CBC模式,加解密算法基础方法
* @param mode 加解密模式 Cipher.ENCRYPT_MODE 1:加密/ Cipher.DECRYPT_MODE 2:解密
* @param plainByte 需加密明文内容/待解密密文内容
* @param keyByte 秘钥内容
*/
public static byte[] cipherCBC(int mode, byte[] plainByte, byte[] keyByte) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
Cipher cipher = Cipher.getInstance(ConfigBean.ALGORITHM_CBC_PADDING, ProviderSingleton.getInstance());
cipher.init(mode, new SecretKeySpec(keyByte, ConfigBean.ALGORITHM_NAME), new IvParameterSpec(ConfigBean.SM4_KEY_IV));
return cipher.doFinal(plainByte);
}
}
Utils.java
package com.secret.sm4;
import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
public class Utils {
/**
* 生成秘钥
*/
public static String generateKey() throws NoSuchAlgorithmException {
return SecretCommon.generateKey().substring(0, 16);
}
/**
* 默认加密方法(ECB)
*/
public static String encrypt(String plainText, String keyText) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
return encryptECB(plainText, keyText);
}
/**
* 默认解密方法(ECB)
*/
public static String decrypt(String cipherText, String keyText) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
return decryptECB(cipherText, keyText);
}
/**
* ECB模式,加密
* @param plainText 明文字符串
* @param keyText 秘钥内容
*/
public static String encryptECB(String plainText, String keyText) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
return SecretCommon.encryptECB(plainText, keyText);
}
/**
* ECB模式,解密
* @param cipherText 密文字符串
* @param keyText 秘钥内容
*/
public static String decryptECB(String cipherText, String keyText) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
return SecretCommon.decryptECB(cipherText, keyText);
}
/**
* CBC模式,加密
* @param plainText 明文字符串
* @param keyText 秘钥内容
*/
public static String encryptCBC(String plainText, String keyText) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
return SecretCommon.encryptCBC(plainText, keyText);
}
/**
* CBC模式,解密
* @param cipherText 密文字符串
* @param keyText 秘钥内容
*/
public static String decryptCBC(String cipherText, String keyText) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
return SecretCommon.decryptCBC(cipherText, keyText);
}
}
测试类:Test.java
package com.secret.sm4;
public class Test {
public static void main(String[] args) throws Exception{
String key = Utils.generateKey();
System.out.println("生成SM4秘钥:" + key);
String plainText = "Believe in yourself, you are the best";
String ECBText = Utils.encrypt(plainText, key);
System.out.println("ECB默认加密后密文:" + ECBText);
System.out.println("ECB默认解密后明文:" + Utils.decrypt(ECBText, key));
String CBCText = Utils.encryptCBC(plainText, key);
System.out.println("CBC加密后密文:" + CBCText);
System.out.println("CBC解密后明文:" + Utils.decryptCBC(CBCText, key));
}
}
2.3、测试
使用方法参考测试类即可。
三、遇到的坑
3.1、秘钥长度
使用KeyGenerator构建的秘钥长度太长无法使用,会提示:
秘钥取16位即可使用。
3.2、转码问题
在使用 ECB模式 时,加解密都会报错,头大。
加密报错如下:
Exception in thread "main" java.lang.IllegalArgumentException: Last encoded character (before the paddings if any) is a valid base 64 alphabet but not a possible value. Expected the discarded bits to be zero.
at org.apache.tomcat.util.codec.binary.Base64.validateCharacter(Base64.java:429)
at org.apache.tomcat.util.codec.binary.Base64.decode(Base64.java:671)
at org.apache.tomcat.util.codec.binary.BaseNCodec.decode(BaseNCodec.java:362)
at org.apache.tomcat.util.codec.binary.BaseNCodec.decode(BaseNCodec.java:353)
at org.apache.tomcat.util.codec.binary.BaseNCodec.decode(BaseNCodec.java:379)
at org.apache.tomcat.util.codec.binary.Base64.decodeBase64(Base64.java:172)
at com.secret.sm4.SecretCommon.encryptECB(SecretCommon.java:64)
at com.secret.sm4.Utils.encryptECB(Utils.java:39)
at com.secret.sm4.Utils.encrypt(Utils.java:23)
at com.secret.sm4.Test.main(Test.java:10)
解密报错如下:
Exception in thread "main" javax.crypto.BadPaddingException: pad block corrupted
at org.bouncycastle.jcajce.provider.symmetric.util.BaseBlockCipher$BufferedGenericBlockCipher.doFinal(Unknown Source)
at org.bouncycastle.jcajce.provider.symmetric.util.BaseBlockCipher.engineDoFinal(Unknown Source)
at javax.crypto.Cipher.doFinal(Cipher.java:2165)
at com.secret.sm4.SecretCommon.cipherECB(SecretCommon.java:85)
at com.secret.sm4.SecretCommon.decryptECB(SecretCommon.java:73)
at com.secret.sm4.Utils.decryptECB(Utils.java:48)
at com.secret.sm4.Utils.decrypt(Utils.java:30)
at com.secret.sm4.Test.main(Test.java:12)
查阅了资料才发现,是转byte[]数组的问题。
String plainText = "Believe in yourself, you are the best";
byte[] byte1 = plainText.getBytes();
byte[] byte2 = plainText.getBytes(StandardCharsets.UTF_8);
byte[] byte3 = Base64.decodeBase64(plainText);
byte[] byte4 = Hex.decode(plainText);
Base64.encodeBase64String()
new String()
测试的时候发现,同样是转byte[],不同的方法,可能结果会不一样,可以自行尝试验证。
加密的时候:
入参直接使用 getBytes()获取byte数组,返回参数使用 Base64.encodeBase64String()即可。
解密的时候:
入参使用 Base64.decodeBase64()获取byte数组,返回参数直接new String() 即可。
四、相关链接
国密算法概述:https://blog.csdn.net/qq_38254635/article/details/131801527
JAVA集成国密SM2:https://blog.csdn.net/qq_38254635/article/details/131810661
JAVA集成国密SM3:https://blog.csdn.net/qq_38254635/article/details/131810696
单例模式及多线程并发在单例模式中的影响:https://blog.csdn.net/qq_38254635/article/details/119888843