前言
国家密码管理局于2010年12月17日发布了SM2算法,并要求现有的基于RSA算法的电子认证系统、密钥管理系统、应用系统进升级改造,使用支持国密SM2算法的证书。
基于ECC椭圆曲线算法的SM2算法,则普遍采用256位密钥长度,它的单位安全强度相对较高,在工程应用中比较难以实现,破译或求解难度基本上是指数级的。因此,SM2算法可以用较少的计算能力提供比RSA算法更高的安全强度,而所需的密钥长度却远比RSA算法低。
同时,现在好多软件公司对于软件的安全性要求越来越高,大部分软件出于对用户隐私的保护都会使用加密算法对用户信息相关接口进行后端加密,前端解密处理。这里就不多说废话了 直接上干活
引入jar包
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.70</version>
</dependency>
生成公钥和私钥
/**
* 生成SM2密钥对
* @return org.bouncycastle.crypto.AsymmetricCipherKeyPair
* @author fyh
**/
public static AsymmetricCipherKeyPair generateKeyPair() {
ECKeyPairGenerator generator = new ECKeyPairGenerator();
generator.init(new ECKeyGenerationParameters(DOMAIN_PARAMS, new SecureRandom()));
return generator.generateKeyPair();
}
/**
* 获取公钥字符串
*/
/**
* 获取公钥字符串。
*
* @param keyPair 密钥对
* @return 公钥字符串
*/
public static String getPublicKeyString(AsymmetricCipherKeyPair keyPair) {
ECPublicKeyParameters ecPublicKeyParameters = (ECPublicKeyParameters) keyPair.getPublic();
return Hex.toHexString(ecPublicKeyParameters.getQ().getEncoded(false));
}
/**
* 获取私钥字符串。
*
* @param keyPair 密钥对
* @return 私钥字符串
*/
public static String getPrivateKeyString(AsymmetricCipherKeyPair keyPair) {
ECPrivateKeyParameters ecPrivateKeyParameters = (ECPrivateKeyParameters) keyPair.getPrivate();
return Hex.toHexString(ecPrivateKeyParameters.getD().toByteArray());
}
//直接使用main方法生成公钥和私钥
public static void main(String[] args) {
AsymmetricCipherKeyPair keyPair = Sm2Util.generateKeyPair();
String publicKey = Sm2Util.getPublicKeyString(keyPair);
String privateKey = Sm2Util.getPrivateKeyString(keyPair);
System.out.println("#blade配置 \n" +
"blade:\n" +
" auth:\n" +
" public-key: " + publicKey + "\n" +
" private-key: " + privateKey);
}
加密、解密以及加签和验签工具类实现
private static final ECDomainParameters DOMAIN_PARAMS;
static {
Security.addProvider(new BouncyCastleProvider());
X9ECParameters spec = GMNamedCurves.getByName("sm2p256v1");
DOMAIN_PARAMS = new ECDomainParameters(spec.getCurve(), spec.getG(), spec.getN(), spec.getH());
}
/**
* 获取私钥字符串
* @param data
* @return
*/
public static ECPrivateKeyParameters stringToPrivateKey(String data) {
return new ECPrivateKeyParameters(new BigInteger(Hex.decode(data)), DOMAIN_PARAMS);
}
/**
* 从字符串恢复公钥
* @param data
* @return
*/
public static ECPublicKeyParameters stringToPublicKey(String data) {
return new ECPublicKeyParameters(DOMAIN_PARAMS.getCurve().decodePoint(Hex.decode(data)), DOMAIN_PARAMS);
}
public static byte[] encrypt3(String input, String publicKey) {
return encrypt3(input, stringToPublicKey(publicKey));
}
public static byte[] encrypt3(String input, ECPublicKeyParameters publicKey) {
try {
SM2Engine engine = new SM2Engine(new SM3Digest(), SM2Engine.Mode.C1C3C2);
engine.init(true, new ParametersWithRandom(publicKey, new SecureRandom()));
byte[] inputBytes = input.getBytes();
return engine.processBlock(inputBytes, 0, inputBytes.length);
} catch (Exception e) {
return null;
}
}
public static String decrypt3(byte[] encrypted, String privateKey) {
return decrypt3(encrypted, stringToPrivateKey(privateKey));
}
public static String decrypt3(byte[] encrypted, ECPrivateKeyParameters privateKey) {
try {
SM2Engine engine = new SM2Engine(new SM3Digest(), SM2Engine.Mode.C1C3C2);
engine.init(false, privateKey);
byte[] decryptedBytes = engine.processBlock(encrypted, 0, encrypted.length);
return new String(decryptedBytes);
} catch (Exception e) {
return null;
}
}
/**
* 生成签名
* @param input 数据
@param privateKey 私钥
* @return byte[]
* @date 2025/7/31 15:35
* @author fyh
**/
public static byte[] sign2(String input, String privateKey) {
return sign2(input, stringToPrivateKey(privateKey));
}
/**
* 使用SM2算法进行数据签名。
*
* @param input 需要签名的数据
* @param privateKey 私钥
* @return 签名
*/
public static byte[] sign2(String input, ECPrivateKeyParameters privateKey) {
try {
SM2Signer signer = new SM2Signer();
signer.init(true, new ParametersWithRandom(privateKey, new SecureRandom()));
byte[] inputBytes = input.getBytes();
signer.update(inputBytes, 0, inputBytes.length);
return signer.generateSignature();
} catch (Exception e) {
return null;
}
}
/**
* 验证签名
* @param input 数据
@param signature 签名
@param publicKey 公钥
* @return boolean
* @date 2025/7/31 15:36
* @author fyh
**/
public static boolean verify2(String input, byte[] signature, String publicKey) {
return verify2(input, signature, stringToPublicKey(publicKey));
}
/**
* 使用SM2算法验证签名。
*
* @param input 数据
* @param signature 签名
* @param publicKey 公钥
* @return 是否验证成功
*/
public static boolean verify2(String input, byte[] signature, ECPublicKeyParameters publicKey) {
try {
SM2Signer signer = new SM2Signer();
signer.init(false, publicKey);
byte[] inputBytes = input.getBytes();
signer.update(inputBytes, 0, inputBytes.length);
return signer.verifySignature(signature);
} catch (Exception e) {
return false;
}
}
//使用main测试加解密是否正常
public static void main(String[] args) {
String jobNumber ="90006790";
byte[] encryptedData = Sm2Util.encrypt3(jobNumber, publicKey);
//将数组转换为字符串
String hexString = Hex.toHexString(encryptedData);
byte[] bytes = Sm2Util.sign2(jobNumber, privateKey);
System.out.println("前段加密数据:"+hexString);
System.out.println("前段加签:"+bytes);
String s1 = Sm2Util.decrypt3(encryptedData, privateKey);
boolean b = Sm2Util.verify2(s1, bytes, publicKey);
System.out.println("解密数据:"+s1);
System.out.println("验签:"+b);
}
具体使用方式
我们可以在需要加密或者解密的地方调用公共方法然后实现对字符串的加密或者解密操作 就比如一个前后端分离的项目,在登录的时候 需要前端实现加密,加签操作后端实现解密,验签操作的过程
protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {
Map<String, String> parameters = tokenRequest.getRequestParameters();
String username = MapUtils.getString(parameters, "username");
String password = MapUtils.getString(parameters, "password");
//SM2Key.PRIVATE_KEY_ANDROID 是解密的私钥
username = SM2Util.decrypt3(HexUtil.decodeHex(username), SM2Key.PRIVATE_KEY_ANDROID);
password = SM2Util.decrypt3(HexUtil.decodeHex(password), SM2Key.PRIVATE_KEY_ANDROID);
String jsonStr = "{\"username\":\"{}\",\"password\":\"{}\"}";
jsonStr = StrUtil.format(jsonStr,
username,
password
);
//HexUtil.decodeHex 方法是将字符串转换为二进制,SM2Key.PUBLIC_KEY_SIGN 是验签的公钥
boolean b = SM2Util.verify2(jsonStr, HexUtil.decodeHex(parameters.get("dataS")),SM2Key.PUBLIC_KEY_SIGN);
if (!b) {
//验签失败
throw new DataFailException(ResultEnum.USER_WECHAT_LOGIN_FAIL.getCode() + "", "请检查数据完整性");
}
//此处省去逻辑代码实现部分
}
备注:上述代码中的SM2Key类是专门用户存放公钥和私钥的公共类,可以将生成的公钥和私钥,验签的数据放在SM2Key中方便后期使用时直接调用即可