目录
介绍
针对 Bouncy Castle
做了封装工具类,用于实现国密算法中的 SM2、SM3、SM4。
国密算法工具封装包括:
- 非对称加密和签名:SM2
- 摘要签名算法:SM3
- 对称加密:SM4
国密算法需要引入 Bouncy Castle
库的依赖。
开始
引入 Bouncy Castle
依赖
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.70</version>
</dependency>
SM2 算法
非对称加密 SM2
包含:生成公钥私钥、加密、解密、加签、验签
完整代码 (SM2Util.java)
import org.bouncycastle.asn1.gm.GMNamedCurves;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.crypto.CryptoException;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.crypto.digests.SM3Digest;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.generators.ECKeyPairGenerator;
import org.bouncycastle.crypto.params.*;
import org.bouncycastle.crypto.signers.SM2Signer;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.math.ec.ECCurve;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.util.encoders.Hex;
import java.math.BigInteger;
import java.security.*;
/**
* SM2Util 类用于生成、加密、解密、签名和验签 SM2 密钥对及数据。
* 该类使用 BouncyCastle 提供的安全库来实现各种 SM2 操作。
*/
public class SM2Util {
// 静态代码块,在类加载时自动添加 BouncyCastle 提供者
static {
Security.addProvider(new BouncyCastleProvider());
}
// 定义 SM2 曲线名称常量
private static final String CURVE_NAME = "sm2p256v1";
private static final SM2Engine.Mode mode = SM2Engine.Mode.C1C3C2;
/**
* 打印生成的 SM2 公钥和私钥,以十六进制格式显示
*/
public static void printGenSM2Key() {
// 获取 SM2 曲线参数
X9ECParameters ecParameters = GMNamedCurves.getByName(CURVE_NAME);
ECDomainParameters domainParameters = new ECDomainParameters(
ecParameters.getCurve(),
ecParameters.getG(),
ecParameters.getN(),
ecParameters.getH());
// 使用 SM2 算法生成密钥对
ECKeyPairGenerator keyPairGenerator = new ECKeyPairGenerator();
ECKeyGenerationParameters keyGenerationParameters = new ECKeyGenerationParameters(domainParameters, new SecureRandom());
keyPairGenerator.init(keyGenerationParameters);
// 生成密钥对
AsymmetricCipherKeyPair keyPair = keyPairGenerator.generateKeyPair();
ECPoint publicKeyPoint = ((org.bouncycastle.crypto.params.ECPublicKeyParameters) keyPair.getPublic()).getQ();
ECPrivateKeyParameters privateKey = (ECPrivateKeyParameters) keyPair.getPrivate();
// 获取公钥和私钥的字节数组
byte[] publicKeyBytes = publicKeyPoint.getEncoded(false); // false表示未压缩格式
byte[] privateKeyBytes = privateKey.getD().toByteArray();
// 将字节数组转换为 Hex String
String publicKeyHex = Hex.toHexString(publicKeyBytes);
String privateKeyHex = Hex.toHexString(privateKeyBytes);
// 输出公钥和私钥的 Hex String
System.out.println("Public Key (Hex): " + publicKeyHex);
System.out.println("Private Key (Hex): " + privateKeyHex);
}
/**
* SM2 加密
*
* @param data 要加密的数据
* @param publicKeyStr 公钥的字符串表示形式
* @return 加密后的数据,以十六进制表示
* @throws InvalidCipherTextException 加密失败
*/
public static String encrypt(String data, String publicKeyStr) throws InvalidCipherTextException {
byte[] dataBytes = data.getBytes();
ECPublicKeyParameters publicKey = convertStringToPublicKey(publicKeyStr);
// 使用 SM3 和 C1C3C2 模式
SM2Engine engine = new SM2Engine(new SM3Digest(), mode);
engine.init(true, new ParametersWithRandom(publicKey));
byte[] bytes = engine.processBlock(dataBytes, 0, dataBytes.length);
return Hex.toHexString(bytes);
}
/**
* SM2 解密
*
* @param encryptedData 加密后的数据,以十六进制表示
* @param privateKeyStr 私钥的字符串表示形式
* @return 解密后的原始数据
* @throws InvalidCipherTextException 解密失败
*/
public static String decrypt(String encryptedData, String privateKeyStr) throws InvalidCipherTextException {
byte[] dataBytes = Hex.decodeStrict(encryptedData);
ECPrivateKeyParameters privateKey = convertStringToPrivateKey(privateKeyStr);
// 使用 SM3 和 C1C3C2 模式
SM2Engine engine = new SM2Engine(new SM3Digest(), mode);
engine.init(false, privateKey);
byte[] bytes = engine.processBlock(dataBytes, 0, dataBytes.length);
return new String(bytes);
}
/**
* 将字符串形式的公钥转换为 PublicKey 对象
*
* @param publicKeyStr 公钥的字符串表示形式
* @return 转换后的 PublicKey 对象
*/
public static ECPublicKeyParameters convertStringToPublicKey(String publicKeyStr) {
// 获取 SM2 曲线参数
X9ECParameters x9ECParameters = GMNamedCurves.getByName(CURVE_NAME);
ECDomainParameters ecDomainParameters = new ECDomainParameters(x9ECParameters.getCurve(), x9ECParameters.getG(), x9ECParameters.getN(), x9ECParameters.getH());
ECCurve curve = ecDomainParameters.getCurve();
ECPoint ecPoint = curve.decodePoint(Hex.decode(publicKeyStr));
return new ECPublicKeyParameters(ecPoint, ecDomainParameters);
}
/**
* 将字符串形式的私钥转换为 PrivateKey 对象
*
* @param privateKeyStr 私钥的字符串表示形式
* @return 转换后的 PrivateKey 对象
*/
public static ECPrivateKeyParameters convertStringToPrivateKey(String privateKeyStr) {
// 添加 BouncyCastle 作为安全提供者
Security.addProvider(new BouncyCastleProvider());
// 将 Hex 私钥转换为 BigInteger
BigInteger privateKeyD = new BigInteger(1, Hex.decode(privateKeyStr));
// 获取 SM2 曲线参数
X9ECParameters x9ECParameters = GMNamedCurves.getByName(CURVE_NAME);
ECDomainParameters ecDomainParameters = new ECDomainParameters(x9ECParameters.getCurve(), x9ECParameters.getG(), x9ECParameters.getN(), x9ECParameters.getH());
return new ECPrivateKeyParameters(privateKeyD, ecDomainParameters);
}
/**
* SM2 签名
*
* @param data 要签名的数据
* @param privateKeyStr 私钥的字符串表示形式
* @return 签名后的数据,以十六进制表示
* @throws CryptoException 签名失败
*/
public static String sign(String data, String privateKeyStr) throws CryptoException {
byte[] dataBytes = data.getBytes();
ECPrivateKeyParameters privateKey = convertStringToPrivateKey(privateKeyStr);
// 创建 SM2Signer 对象
SM2Signer signer = new SM2Signer();
signer.init(true, new ParametersWithRandom(privateKey));
// 更新签名器的数据
signer.update(dataBytes, 0, dataBytes.length);
// 生成签名
byte[] bytes = signer.generateSignature();
return Hex.toHexString(bytes);
}
/**
* SM2 验签
*
* @param data 原始数据
* @param signature 签名数据,以十六进制表示
* @param publicKeyStr 公钥的字符串表示形式
* @return 验签结果,true 表示签名有效,false 表示无效
*/
public static boolean verify(String data, String signature, String publicKeyStr) {
byte[] dataBytes = data.getBytes();
byte[] signatureBytes = Hex.decodeStrict(signature);
ECPublicKeyParameters publicKey = convertStringToPublicKey(publicKeyStr);
// 创建 SM2Signer 对象
SM2Signer signer = new SM2Signer();
signer.init(false, publicKey);
// 更新签名器的数据
signer.update(dataBytes, 0, dataBytes.length);
// 验证签名
return signer.verifySignature(signatureBytes);
}
public static void main(String[] args) throws Exception {
// 打印生成的 SM2 密钥对
printGenSM2Key();
String data = "Hello SM";
String privateKey = "009b2efe43ebc2e9dc9c3c3ef69d382aac9dcbddebf5bcc9a96a9a3fc3f9a09cf3";
String publicKey = "04173d57a1df1ea8c0d22c8fbb0ea8abdc88b3c994851cbad1c28cfe06ebdb54ecbfbb38a984d41f5d2218b2381a2e53cc7d13dd0d10a315c76f331aa57d5cda01";
String encryptData = encrypt(data, publicKey);
System.out.println("SM2 加密" + encryptData);
String decryptData = decrypt(encryptData, privateKey);
System.out.println("SM2 解密" + decryptData);
String sign = sign(data, privateKey);
System.out.println("SM2 加签" + sign);
System.out.println("SM2 验签" + verify(data, sign, publicKey));
}
}
测试调用
public static void main(String[] args) throws Exception {
// 打印生成的 SM2 密钥对
printGenSM2Key();
String data = "Hello SM";
String privateKey = "009b2efe43ebc2e9dc9c3c3ef69d382aac9dcbddebf5bcc9a96a9a3fc3f9a09cf3";
String publicKey = "04173d57a1df1ea8c0d22c8fbb0ea8abdc88b3c994851cbad1c28cfe06ebdb54ecbfbb38a984d41f5d2218b2381a2e53cc7d13dd0d10a315c76f331aa57d5cda01";
String encryptData = encrypt(data, publicKey);
System.out.println("SM2 加密" + encryptData);
String decryptData = decrypt(encryptData, privateKey);
System.out.println("SM2 解密" + decryptData);
String sign = sign(data, privateKey);
System.out.println("SM2 加签" + sign);
System.out.println("SM2 验签" + verify(data, sign, publicKey));
}
1. 生成公钥私钥
// 打印生成的 SM2 密钥对
printGenSM2Key();
2. 加密解密
String data = "Hello SM";
String privateKey = "009b2efe43ebc2e9dc9c3c3ef69d382aac9dcbddebf5bcc9a96a9a3fc3f9a09cf3";
String publicKey = "04173d57a1df1ea8c0d22c8fbb0ea8abdc88b3c994851cbad1c28cfe06ebdb54ecbfbb38a984d41f5d2218b2381a2e53cc7d13dd0d10a315c76f331aa57d5cda01";
String encryptData = encrypt(data, publicKey);
System.out.println("SM2 加密" + encryptData);
String decryptData = decrypt(encryptData, privateKey);
System.out.println("SM2 解密" + decryptData);
3. 加签验签
String sign = sign(data, privateKey);
System.out.println("SM2 加签" + sign);
System.out.println("SM2 验签" + verify(data, sign, publicKey));
SM3 算法
摘要加密 SM3
1. 摘要加密
String digest = digest("Hello SM");
System.out.println("SM3 摘要加密" + digest);
完整代码(SM3Util.java)
import org.bouncycastle.crypto.digests.SM3Digest;
import org.bouncycastle.util.encoders.Hex;
/**
* SM3Util 类用于执行 SM3 摘要加密操作。
* SM3 是中国国家密码管理局定义的一种哈希算法,类似于 SHA-256。
*/
public class SM3Util {
/**
* 生成数据的 SM3 摘要
*
* @param data 数据
* @return 生成的摘要,以十六进制表示
*/
public static String digest(String data) {
byte[] bytes = data.getBytes();
SM3Digest digest = new SM3Digest();
digest.update(bytes, 0, bytes.length);
byte[] hash = new byte[digest.getDigestSize()];
digest.doFinal(hash, 0);
return Hex.toHexString(hash);
}
public static void main(String[] args) {
String digest = digest("Hello SM");
System.out.println("SM3 摘要加密" + digest);
}
}
SM4 算法
对称加密 SM4
包含 生成随机密钥、加密、解密
1. 生成随机密钥
String key = generateKey();
System.out.println("SM4 随机密钥" + key);
控制台输出:
SM4 随机密钥86ac19e32caff89b4c0555751347c308
2. 加密解密
String key = generateKey();
System.out.println("SM4 随机密钥" + key);
String data = "Hello SM";
String encrypt = encrypt(data, key);
System.out.println("SM4 加密" + encrypt);
String decryptData = decrypt(encrypt, key);
System.out.println("SM4 解密" + decryptData);
控制台输出:
SM4 随机密钥27976d9e26e6c84fbdf04ac54b312287
SM4 加密cb822754fb3cbaa4a7a856caa77add09
SM4 解密Hello SM
完整代码(SM4Util.java)
import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.crypto.engines.SM4Engine;
import org.bouncycastle.crypto.modes.CBCBlockCipher;
import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.crypto.params.ParametersWithIV;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.util.encoders.Hex;
import java.security.SecureRandom;
import java.security.Security;
import java.util.Arrays;
public class SM4Util {
static {
Security.addProvider(new BouncyCastleProvider());
}
private static final int BLOCK_SIZE = 16;
/**
* 生成一个随机的 SM4 密钥
*
* @return 生成长度 32 的密钥,返回的密钥以十六进制字符串形式表示
*/
public static String generateKey() {
byte[] key = new byte[BLOCK_SIZE];
SecureRandom random = new SecureRandom();
random.nextBytes(key);
return Hex.toHexString(key);
}
/**
* SM4加密
*
* @param data 需要加密的数据
* @param key 密钥 32 位十六进制字符串表示的(16 字节)
*
* @return 加密后的数据
* @throws InvalidCipherTextException 加密失败时抛出异常
*/
public static String encrypt(String data, String key) throws InvalidCipherTextException {
return encrypt(data, key, null);
}
/**
* SM4解密
*
* @param encryptedData 加密的数据
* @param key 密钥 32 位十六进制字符串表示的(16 字节)
* @return 解密后的数据
* @throws InvalidCipherTextException 解密失败时抛出异常
*/
public static String decrypt(String encryptedData, String key) throws InvalidCipherTextException {
return decrypt(encryptedData, key, null);
}
/**
* SM4加密
*
* @param data 需要加密的数据
* @param key 密钥 32 位十六进制字符串表示的(16 字节)
* @param iv 初始化向量 32 位十六进制字符串表示的(16 字节)或为空,若为空则使用全零 IV
*
* @return 加密后的数据
* @throws InvalidCipherTextException 加密失败时抛出异常
*/
public static String encrypt(String data, String key, String iv) throws InvalidCipherTextException {
byte[] keyBytes = Hex.decodeStrict(key);
if (keyBytes.length != BLOCK_SIZE) {
throw new IllegalArgumentException("Key 长度必须为 32");
}
byte[] ivBytes;
if (iv == null || iv.isEmpty()) {
ivBytes = new byte[BLOCK_SIZE]; // 默认使用全零 IV
} else {
ivBytes = Hex.decodeStrict(iv);
if (ivBytes.length != BLOCK_SIZE) {
throw new IllegalArgumentException("IV 如果不为空,长度必须为 32");
}
}
SM4Engine sm4Engine = new SM4Engine();
CBCBlockCipher cbcBlockCipher = new CBCBlockCipher(sm4Engine);
PaddedBufferedBlockCipher cipher = new PaddedBufferedBlockCipher(cbcBlockCipher);
CipherParameters params = new ParametersWithIV(new KeyParameter(keyBytes), ivBytes);
cipher.init(true, params);
byte[] dataBytes = data.getBytes();
byte[] process = process(cipher, dataBytes);
return Hex.toHexString(process);
}
/**
* SM4解密
*
* @param encryptedData 加密的数据
* @param key 密钥 32 位十六进制字符串表示的(16 字节)
* @param iv 初始化向量 32 位十六进制字符串表示的(16 字节)或为空,若为空则使用全零 IV
* @return 解密后的数据
* @throws InvalidCipherTextException 加密失败时抛出异常
*/
public static String decrypt(String encryptedData, String key, String iv) throws InvalidCipherTextException {
byte[] keyBytes = Hex.decodeStrict(key);
if (keyBytes.length != BLOCK_SIZE) {
throw new IllegalArgumentException("Key 长度必须为 32");
}
byte[] ivBytes;
if (iv == null || iv.isEmpty()) {
ivBytes = new byte[BLOCK_SIZE]; // 默认使用全零 IV
} else {
ivBytes = Hex.decodeStrict(iv);
if (ivBytes.length != BLOCK_SIZE) {
throw new IllegalArgumentException("IV 如果不为空,长度必须为 32");
}
}
SM4Engine sm4Engine = new SM4Engine();
CBCBlockCipher cbcBlockCipher = new CBCBlockCipher(sm4Engine);
PaddedBufferedBlockCipher cipher = new PaddedBufferedBlockCipher(cbcBlockCipher);
CipherParameters params = new ParametersWithIV(new KeyParameter(keyBytes), ivBytes);
cipher.init(false, params);
byte[] encryptedDataBytes = Hex.decodeStrict(encryptedData);
byte[] process = process(cipher, encryptedDataBytes);
return new String(process);
}
/**
* 处理加密或解密的数据
*
* @param cipher 加密器或解密器
* @param data 数据
*
* @return 处理后的数据
* @throws InvalidCipherTextException 处理失败时抛出异常
*/
private static byte[] process(PaddedBufferedBlockCipher cipher, byte[] data) throws InvalidCipherTextException {
byte[] outputBuffer = new byte[cipher.getOutputSize(data.length)];
int length = cipher.processBytes(data, 0, data.length, outputBuffer, 0);
length += cipher.doFinal(outputBuffer, length);
return Arrays.copyOf(outputBuffer, length);
}
public static void main(String[] args) throws Exception {
String key = generateKey();
System.out.println("SM4 随机密钥" + key);
String data = "Hello SM";
String encrypt = encrypt(data, key);
System.out.println("SM4 加密" + encrypt);
String decryptData = decrypt(encrypt, key);
System.out.println("SM4 解密" + decryptData);
}
}