终于把前后端sm加解密以及加签验证调通了!
领导要求我对项目的数据传输安全考虑下,因此就想到了对敏感字段做加密和对请求、响应做数字签名验证。网上看了很多文章,可能是因为我对加密这块不了解,感觉都比较乱。所以前前后后花了4天才把前后端调通。特地写一篇文章记录下流程。这里使用的是sm国密算法。不对的地方请读者评论指出。
1.简单说明:
-
前端使用sm-crypto库
-
后端加密库使用bc库,架构上使用aop,注解等实现文章来源:https://www.toymoban.com/news/detail-620015.html
2.具体实现-前端
- 加密流程:生成一个对称密钥,对每个字段使用sm4对称加密,然后进行base64编码。对称密钥使用sm2非对称加密
// 生成对称密钥
export function genSM4SymmetricKey(){
let sl = "abcdef0123456789";
let bl=""
for (let i = 0; i < 16; i++) {
bl = bl.concat(sl.charAt(Math.floor(Math.random() * 16)));
}
return bl.toString();
}
// 字段sm4加密再转base64
export function encryptovalue(value,symmetricKey){
let key=StringToHex(symmetricKey)
return Base64.btoa( HexToString(sm4.encrypt( value,key)) );
}
// 对象添加加密后的对称密钥属性
export function addSm4SymmetricKey(symmetricKey,obj){
// 加密对称密钥,前面加04
sm2.doEncrypt(symmetricKey,publicKey,cipherMode);
obj["sm4SymmetricKey"]="04"+symmetricKey
return obj
}
- 解密流程:对后端响应的数据对象中的加密字段进行base64解码,sm2解密解出对称密钥,再使用对称密钥进行sm4解密
// 解密对称密钥
export function decryptSm4SymmetricKey(sm4SymmetricKey){
// 去除开头的04
sm4SymmetricKey = sm4SymmetricKey.substr(2)
return sm2.doDecrypt(sm4SymmetricKey,privateKey);
}
// 字段sm4解密
export function decryptovalue(value,symmetricKey){
value=base64ToHex(value)
console.log("value=",value)
let des = sm4.decrypt(value,symmetricKey)
console.log("base64解码后解密:",des)
return des
}
- 加签流程:加密前进行加签,对象key排序后,拼接成key=value&key=value&key=value…的形式,再进行sm2数字签名。
export function sm2Signature(obj){
let str = signatureStr(obj)
// 签名
let sigValueHex4 = sm2.doSignature(str, privateKey, {
hash:true,
der:true,
})
console.log("签名串:",sigValueHex4)
return sigValueHex4;
}
- 验签流程:解密之后要进行验签,将解密后的对象key排序后,拼接成key=value&key=value&key=value…的形式,进行sm2签名验证
export function sm2VerifySignature(obj){
console.log(obj)
let signature = obj.signature
delete obj["sm4SymmetricKey"]
delete obj["signature"]
let str = signatureStr(obj)
let verifyResult = sm2.doVerifySignature(str, signature, publicKey, {
der:true,
hash: true,
})
console.log("验签结果:",verifyResult)
return verifyResult
}
- 测试页面代码
<template>
<input type="text" v-model="objfront.address" />
<input type="text" v-model="objfront.password" />
<button @click="sm">加密</button>
<button @click="send">发送</button>
<div>后端返回</div>
<input type="text" v-model="decrespData.address" />
<input type="text" v-model="decrespData.password" />
</template>
<script>
export default {
name: 'HelloWorld',
props: {
msg: String
},
}
</script>
<script setup>
import {onBeforeMount, onMounted,ref,reactive} from "vue";
import {sm2,sm4} from "sm-crypto";
import {Base64} from "js-base64";
import {test} from "@/api/test";
import {
decryptovalue,
decryptSm4SymmetricKey,
encryptovalue,
randomString,
sm2Signature, sm2VerifySignature
} from "../utils/cryptTools";
let obj=reactive({
})
let objfront=reactive({
name:'1',
age:'12',
address:"1",
password:"2",
timeStamp:"",
nonceStr:""
})
let decrespData=ref({
})
//sm2的加解密时有两种方式即0——C1C2C3、1——C1C3C2,前后端需要统一
const cipherMode = 1
const publicKey = '04bdf29de4e09f8ddd484035879705530e7af03b431370fa7a3734dbf3b9ad937a640b90d0824e4ffad681d77363f4ddb15a574afc86a0a289902dafeca73712bc'
function StringToHex(str) {
if (str == '')
return '';
let hex = [];
for (var i = 0; i < str.length; i++) {
hex.push((str.charCodeAt(i)).toString(16));
}
return hex.join('');
}
function HexToString(str1) {
let hex = str1.toString();
let str = "";
for (let n = 0; n < hex.length; n += 2) {
str += String.fromCharCode(parseInt(hex.substr(n, 2), 16));
}
return str;
}
function genSM4SymmetricKey(){
let sl = "abcdef0123456789";
let bl=""
for (let i = 0; i < 16; i++) {
bl = bl.concat(sl.charAt(Math.floor(Math.random() * 16)));
}
return bl.toString();
}
function sm(){
// 生成时间戳
objfront.timeStamp = new Date().getTime();
// 生成随机字符串
objfront.nonceStr = randomString(16)
// 前端业务对象放入请求对象
obj = JSON.parse(JSON.stringify(objfront));
// 签名放入对象
obj["signature"] = sm2Signature(objfront)
let symmetricKey = genSM4SymmetricKey();
// 对称加密
obj.password = encryptovalue( objfront.password,symmetricKey );
obj.address = encryptovalue( objfront.address,symmetricKey );
// 加密对称密钥,前面加04
symmetricKey = sm2.doEncrypt(symmetricKey,publicKey,cipherMode);
obj["sm4SymmetricKey"]="04"+symmetricKey
}
function send(){
test(obj).then((res)=>{
let data = res.data;
decrespData.value = res.data
// 解出对称密钥
let symmetricKey = decryptSm4SymmetricKey(data.sm4SymmetricKey);
console.log("对称密钥:",symmetricKey)
// 解密字段
decrespData.value.address = decryptovalue(data.address,StringToHex(symmetricKey))
decrespData.value.password = decryptovalue(data.password,StringToHex(symmetricKey))
// 验签
if(!sm2VerifySignature(decrespData.value)){
return
}
console.log("成功!")
})
}
</script>
3.具体实现-后端
- 创建注解@Crypt
@Target({ElementType.TYPE,ElementType.METHOD,ElementType.FIELD}) // 可以作用在类上和方法和字段上。
@Retention(RetentionPolicy.RUNTIME)
public @interface Crypt {
}
@Data
public class User extends BaseObject {
private String name;
@Crypt
private String password;
private String age;
@Crypt
private String address;
}
@Api(tags = "用户控制器")
@ApiModel
@RestController
@ResponseBody
@RequestMapping("/pa")
public class UserController {
@Crypt
@PostMapping("test")
public Result<User> test(@RequestBody User user){
User newUser = new User();
newUser.setName("后端返回");
newUser.setPassword(user.getPassword());
newUser.setAddress(user.getAddress());
newUser.setAge(user.getAge());
return Result.success(newUser);
}
@Crypt
@GetMapping("testget")
public Result<String> testget(){
return Result.success("hello");
}
}
创建切面类文章来源地址https://www.toymoban.com/news/detail-620015.html
package com.lxls.personalachievement.common.safe.aop;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson2.JSONObject;
import com.google.common.collect.Maps;
import com.google.common.io.BaseEncoding;
import com.lxls.personalachievement.common.result.Result;
import com.lxls.personalachievement.common.result.ResultCode;
import com.lxls.personalachievement.common.safe.SMConfig;
import com.lxls.personalachievement.common.safe.annotation.Crypt;
import com.lxls.personalachievement.tools.sm.BytesUtil;
import com.lxls.personalachievement.tools.sm.KeyUtil;
import com.lxls.personalachievement.tools.sm.SMUtil;
import com.lxls.personalachievement.tools.smnew.SM2Util;
import org.apache.commons.codec.binary.Base64;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.bouncycastle.crypto.CryptoException;
import org.bouncycastle.util.encoders.Hex;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cglib.beans.BeanMap;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
/**
* @author: WangQC
* @date 2023/2/10 13:34
*/
@Order(Ordered.HIGHEST_PRECEDENCE)
@Aspect
@Component
public class AspectSafe {
Logger log = LoggerFactory.getLogger(this.getClass());
@Autowired
private SMConfig smConfig;
@Autowired
private SMUtil smUtil;
// 定义切点(在Crypt切点或者controller方法上)
@Pointcut("@annotation(com.lxls.personalachievement.common.safe.annotation.Crypt)")
public void pt() {
}
//环绕通知,方法前后都可以进行增强
@Around("pt()")
public Object crypt(ProceedingJoinPoint joinPoint) throws Throwable {
RequestAttributes ra = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes sra = (ServletRequestAttributes) ra;
HttpServletRequest request = sra.getRequest();
if("GET".equals(request.getMethod())){
return joinPoint.proceed();
}
Result responseObj = null;
try {
Object arg = joinPoint.getArgs()[0];
// 获取参数
Object requestObj = joinPoint.getArgs()[0];
// 解密
Object[] decryptObj = handleDecrypt(requestObj);
if( decryptObj[0] instanceof Result ){
// 返回对象为Result,则表示出错应当返回前端
return decryptObj[0];
}
// 验签
if(!handleSignatureVerificate(requestObj)){
// 验签失败返回
return Result.failed(ResultCode.SIGNATUR_ERROR,ResultCode.SIGNATUR_ERROR.getMsg());
}
// 调用执行方法
responseObj = (Result) joinPoint.proceed(decryptObj);
// 加签
responseObj = handleSign(responseObj.getData());
// 返回加密
handleEncrypt(responseObj.getData());
} catch (NoSuchMethodException e) {
e.printStackTrace();
log.error("SecureFieldAop处理出现异常{}", e);
return Result.failed(ResultCode.CRYPTOERROR,ResultCode.CRYPTOERROR.getMsg());
} catch (Throwable throwable) {
throwable.printStackTrace();
log.error("SecureFieldAop处理出现异常{}", throwable);
return Result.failed(ResultCode.CRYPTOERROR,ResultCode.CRYPTOERROR.getMsg());
}
return responseObj;
}
// 解密处理
private Object[] handleDecrypt(Object requsetObj) throws IllegalAccessException {
// 如果是post或put才执行
log.debug("待解密参数:{}",requsetObj);
if (Objects.isNull(requsetObj)) {
return null;
}
String symmetricKey=null;
Field[] superfields = requsetObj.getClass().getSuperclass().getDeclaredFields();
Field[] fields = requsetObj.getClass().getDeclaredFields();
for (Field field : superfields) {
if("sm4SymmetricKey".equals(field.getName())){
// 解密sm4密钥
field.setAccessible(true);
String encString=(String)field.get(requsetObj);
if( !StringUtils.hasText(encString)){
Object [] objects=new Object[]{Result.failed(ResultCode.NOSYMMETRICKEY,ResultCode.NOSYMMETRICKEY.getMsg())};
return objects;
}
byte[] bytes = BytesUtil.hexToBytes(encString);
symmetricKey = smUtil.sm2Decrypt(smConfig.getPrivateKey(),bytes);
log.debug("解密出的对称密钥:{}",symmetricKey);
break;
}
}
for (Field field : fields) {
boolean hasSecureField = field.isAnnotationPresent(Crypt.class);
if (hasSecureField) {
field.setAccessible(true);
Object o = field.get(requsetObj);
String s = smUtil.sm4Decrypt(symmetricKey, Base64.decodeBase64((String) field.get(requsetObj)));
field.set(requsetObj,s);
}
}
log.debug("解密后请求对象:{}",requsetObj);
Object [] objects=new Object[]{requsetObj};
return objects;
}
// 加密处理
private Object handleEncrypt(Object responseObj) throws IllegalAccessException, UnsupportedEncodingException {
log.debug("待加密参数,{}",responseObj);
if (Objects.isNull(responseObj)) {
return null;
}
// 获取请求参数对象
Class<?> requestObjClass = responseObj.getClass();
// 获取类上的注解,如果存在,则整个对象属性都进行加密
Crypt annotation = requestObjClass.getAnnotation(Crypt.class);
// 获取字段
Field[] fields = requestObjClass.getDeclaredFields();
Field[] superFields = requestObjClass.getSuperclass().getDeclaredFields();
// 生成sm4 key
String sm4SymmetricKey = KeyUtil.genSM4SymmetricKey();
log.debug("待加密的对称密钥:{}",sm4SymmetricKey);
// 加密对称密钥
String sm4SymmetricKeyEncrypt = smUtil.sm2Encrypt(smConfig.getPublicKey(), sm4SymmetricKey);
for(Field field : superFields){
if("sm4SymmetricKey".equals(field.getName())){
field.setAccessible(true);
field.set(responseObj,sm4SymmetricKeyEncrypt);
break;
}
}
if (annotation == null) {
// 对每个字段进行遍历,有注解则进行加密
for (Field field : fields) {
if (field.getAnnotation(Crypt.class) == null) {
continue;
} else {
// 加密字段
field.setAccessible(true);
String plaintextValue = (String) field.get(responseObj);
byte[] bytes = smUtil.sm4Encrypt(sm4SymmetricKey, plaintextValue);
field.set(responseObj, Base64.encodeBase64String(bytes));
}
}
} else {
// 类上注解,对类中的所有字段进行加密
for (Field field : fields) {
// 加密字段
field.setAccessible(true);
String plaintextValue = (String) field.get(responseObj);
byte[] bytes = smUtil.sm4Encrypt(sm4SymmetricKey, plaintextValue); // 用sm4加密字段
field.set(responseObj, Base64.encodeBase64String(bytes));
}
}
log.debug("加密后参数,{}",responseObj);
return responseObj;
}
// 验签处理
private Boolean handleSignatureVerificate(Object requsetObj) throws IllegalAccessException {
/**
* 1.将对象转为map,
* 2.删除签名和对称密钥的key
* 3.根据key进行排序
* 4.验签
*/
String signature=null;
Field[] superfields = requsetObj.getClass().getSuperclass().getDeclaredFields();
Field[] fields = requsetObj.getClass().getDeclaredFields();
for (Field field : superfields) {
if("signature".equals(field.getName())){
// 获取签名串
field.setAccessible(true);
signature=(String)field.get(requsetObj);
break;
}
}
String str = generateSignatureStr(requsetObj);
log.debug("验签待签名串,{}",str);
boolean b = SM2Util.verify(smConfig.getPublicKey(),Hex.toHexString(str.getBytes()), signature);
return b;
}
// 加签处理
private Result handleSign(Object responseObj) throws IllegalAccessException, CryptoException {
/**
* 1.明文参与加签
* 2.添加随机字符串和时间戳
* 3.签名放入返回对象
*/
Long timeStamp=null;
String nonceStr=null;
Field[] superfields = responseObj.getClass().getSuperclass().getDeclaredFields();
Field[] fields = responseObj.getClass().getDeclaredFields();
for (Field field : superfields) {
if("nonceStr".equals(field.getName())){
field.setAccessible(true);
// 生成随机字符串
nonceStr = KeyUtil.createNonceStr(16);
field.set(responseObj,nonceStr);
continue;
}
if("timeStamp".equals(field.getName())){
field.setAccessible(true);
// 生成时间戳
timeStamp = System.currentTimeMillis();
field.set(responseObj,timeStamp);
}
}
// 生成待签名串
String str = generateSignatureStr(responseObj);
log.debug("加签待签名串:{}",str);
// 签名
String signature = SM2Util.sign(smConfig.getPrivateKey(), Hex.toHexString(str.getBytes()));
// 放入对象
for (Field field : superfields) {
if("signature".equals(field.getName())){
field.setAccessible(true);
field.set(responseObj,signature);
break;
}
}
return Result.success(responseObj);
}
// 生成待签名串
private String generateSignatureStr(Object obj){
BeanMap beanMap = BeanMap.create(obj);
Map<String, Object> map = Maps.newHashMap(beanMap);
map.remove("sm4SymmetricKey");
map.remove("signature");
return KeyUtil.sortMapToSTring(map);
}
}
- sm工具类
package com.lxls.personalachievement.tools.sm;
import org.apache.commons.codec.binary.Base64;
import org.bouncycastle.asn1.ASN1EncodableVector;
import org.bouncycastle.asn1.ASN1Integer;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.asn1.gm.GMNamedCurves;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.crypto.params.ParametersWithRandom;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.jcajce.spec.SM2ParameterSpec;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.spec.ECParameterSpec;
import org.bouncycastle.jce.spec.ECPrivateKeySpec;
import org.bouncycastle.jce.spec.ECPublicKeySpec;
import org.bouncycastle.util.encoders.Hex;
import org.springframework.stereotype.Component;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.io.IOException;
import java.math.BigInteger;
import java.security.Key;
import java.security.SecureRandom;
import java.security.Security;
import java.security.Signature;
import java.util.Arrays;
@Component
public class SMUtil {
public static final int RS_LEN = 32;
private static final String SIGNATURE_PARAM = "SM3withSM2";
private static final String PROV_NAME = BouncyCastleProvider.PROVIDER_NAME;
//SM2曲线名称
private static final String CURVE_NAME = "sm2p256v1";
//SM2相关参数
private static final X9ECParameters x9ECParameters = GMNamedCurves.getByName(CURVE_NAME);
//椭圆曲线参数规格
private static final ECParameterSpec ecParameterSpec = new ECParameterSpec(x9ECParameters.getCurve(), x9ECParameters.getG(), x9ECParameters.getN(), x9ECParameters.getH());
private static final String CIPHER_PARAM = "SM4";
private static final String MODE_PARAM = "SM4/ECB/PKCS7Padding";
//只需加载一次
static {
if (Security.getProperty(PROV_NAME) == null) {
Security.addProvider(new BouncyCastleProvider());
}
}
private static BCECPublicKey getECPublicKeyByPublicKeyHex(String pubKeyHex) {
//截取64字节有效的SM2公钥(如果公钥首个字节为0x04)
if (pubKeyHex.length() > 128) {
pubKeyHex = pubKeyHex.substring(pubKeyHex.length() - 128);
}
//将公钥拆分为x,y分量(各32字节)
String stringX = pubKeyHex.substring(0, 64);
String stringY = pubKeyHex.substring(stringX.length());
//将公钥x、y分量转换为BigInteger类型
BigInteger x = new BigInteger(stringX, 16);
BigInteger y = new BigInteger(stringY, 16);
//通过公钥x、y分量创建椭圆曲线公钥规范
ECPublicKeySpec ecPublicKeySpec = new ECPublicKeySpec(x9ECParameters.getCurve().createPoint(x, y), ecParameterSpec);
//通过椭圆曲线公钥规范,创建出椭圆曲线公钥对象(可用于SM2加密及验签)
return new BCECPublicKey("EC", ecPublicKeySpec, BouncyCastleProvider.CONFIGURATION);
}
private static byte[] innerSM2Encrypt(BCECPublicKey publicKey, String data, int modeType) {
//加密模式
SM2Engine.Mode mode = SM2Engine.Mode.C1C3C2;
if (modeType != 1) {
mode = SM2Engine.Mode.C1C2C3;
}
//通过公钥对象获取公钥的基本域参数。
ECParameterSpec ecParameterSpec = publicKey.getParameters();
ECDomainParameters ecDomainParameters = new ECDomainParameters(ecParameterSpec.getCurve(),
ecParameterSpec.getG(), ecParameterSpec.getN());
//通过公钥值和公钥基本参数创建公钥参数对象
ECPublicKeyParameters ecPublicKeyParameters = new ECPublicKeyParameters(publicKey.getQ(), ecDomainParameters);
//根据加密模式实例化SM2公钥加密引擎
SM2Engine sm2Engine = new SM2Engine(mode);
//初始化加密引擎
sm2Engine.init(true, new ParametersWithRandom(ecPublicKeyParameters, new SecureRandom()));
byte[] arrayOfBytes = null;
try {
//将明文字符串转换为指定编码的字节串
byte[] in = data.getBytes("utf-8");
//通过加密引擎对字节数串行加密
arrayOfBytes = sm2Engine.processBlock(in, 0, in.length);
} catch (Exception e) {
System.out.println("SM2加密时出现异常:" + e.getMessage());
e.printStackTrace();
}
return arrayOfBytes;
}
/**
* SM2加密入口
*/
public String sm2Encrypt(String hexPublicKey, String plainText) {
//生产bc公钥对象
BCECPublicKey publicKey = getECPublicKeyByPublicKeyHex(hexPublicKey);
//加密
try {
byte[] encText = innerSM2Encrypt(publicKey, plainText,1);
return Hex.toHexString(encText);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
private static BCECPrivateKey getBCECPrivateKeyByPrivateKeyHex(String privateKeyHex) {
//将十六进制私钥字符串转换为BigInteger对象
BigInteger d = new BigInteger(privateKeyHex, 16);
//通过私钥和私钥域参数集创建椭圆曲线私钥规范
ECPrivateKeySpec ecPrivateKeySpec = new ECPrivateKeySpec(d, ecParameterSpec);
//通过椭圆曲线私钥规范,创建出椭圆曲线私钥对象(可用于SM2解密和签名)
return new BCECPrivateKey("EC", ecPrivateKeySpec, BouncyCastleProvider.CONFIGURATION);
}
private static byte[] innerSM2Decrypt(BCECPrivateKey privateKey, byte[] cipherData, int modeType) throws Exception {
//解密模式
SM2Engine.Mode mode = SM2Engine.Mode.C1C3C2;
if (modeType != 1)
mode = SM2Engine.Mode.C1C2C3;
//通过私钥对象获取私钥的基本域参数。
ECParameterSpec ecParameterSpec = privateKey.getParameters();
ECDomainParameters ecDomainParameters = new ECDomainParameters(ecParameterSpec.getCurve(),
ecParameterSpec.getG(), ecParameterSpec.getN());
//通过私钥值和私钥钥基本参数创建私钥参数对象
ECPrivateKeyParameters ecPrivateKeyParameters = new ECPrivateKeyParameters(privateKey.getD(),
ecDomainParameters);
//通过解密模式创建解密引擎并初始化
SM2Engine sm2Engine = new SM2Engine(mode);
sm2Engine.init(false, ecPrivateKeyParameters);
try {
//通过解密引擎对密文字节串进行解密
byte[] arrayOfBytes = sm2Engine.processBlock(cipherData, 0, cipherData.length);
//将解密后的字节串转换为utf8字符编码的字符串(需要与明文加密时字符串转换成字节串所指定的字符编码保持一致)
return arrayOfBytes;
} catch (Exception e) {
System.out.println("SM2解密时出现异常" + e.getMessage());
}
return null;
}
/**
* SM2解密入口
*/
public String sm2Decrypt(String hexPrivateKey, byte[] encBytes) {
try{
BCECPrivateKey privateKey = getBCECPrivateKeyByPrivateKeyHex(hexPrivateKey);
byte[] decResult = innerSM2Decrypt(privateKey, encBytes,1);
return new String(decResult);
}catch (Exception e){
e.printStackTrace();
return null;
}
}
private static byte[] signature(byte[] src, BCECPrivateKey sm2Key) throws Exception {
byte[] dest = null;
Signature signature = Signature.getInstance(SIGNATURE_PARAM, PROV_NAME);
signature.setParameter(new SM2ParameterSpec("lxls".getBytes()));
signature.initSign(sm2Key);
signature.update(src);
dest = signature.sign();
return ans1ToRS(dest);
}
private static byte[] ans1ToRS(byte[] rsDer) {
ASN1Sequence seq = ASN1Sequence.getInstance(rsDer);
byte[] r = bigIntToFixexLengthBytes(ASN1Integer.getInstance(seq.getObjectAt(0)).getValue());
byte[] s = bigIntToFixexLengthBytes(ASN1Integer.getInstance(seq.getObjectAt(1)).getValue());
byte[] result = new byte[RS_LEN * 2];
System.arraycopy(r, 0, result, 0, r.length);
System.arraycopy(s, 0, result, RS_LEN, s.length);
return result;
}
private static byte[] bigIntToFixexLengthBytes(BigInteger rOrS) {
// for sm2p256v1, n is 00fffffffeffffffffffffffffffffffff7203df6b21c6052b53bbf40939d54123,
// r and s are the result of mod n, so they should be less than n and have length<=32
byte[] rs = rOrS.toByteArray();
if (rs.length == RS_LEN) return rs;
else if (rs.length == RS_LEN + 1 && rs[0] == 0) return Arrays.copyOfRange(rs, 1, RS_LEN + 1);
else if (rs.length < RS_LEN) {
byte[] result = new byte[RS_LEN];
Arrays.fill(result, (byte) 0);
System.arraycopy(rs, 0, result, RS_LEN - rs.length, rs.length);
return result;
} else {
throw new RuntimeException("err rs: " + Hex.toHexString(rs));
}
}
/**
* SM2加签入口
*/
public byte[] sm2Sign(String hexPrivateKey, String sortedString) {
try{
BCECPrivateKey privateKey = getBCECPrivateKeyByPrivateKeyHex(hexPrivateKey);
byte[]signResult = signature(sortedString.getBytes(), privateKey);
return signResult;
}catch (Exception e){
e.printStackTrace();
return null;
}
}
private static byte[] rsPlainByteArrayToAsn1(byte[] sign) {
if (sign.length != RS_LEN * 2) throw new RuntimeException("err rs. ");
BigInteger r = new BigInteger(1, Arrays.copyOfRange(sign, 0, RS_LEN));
BigInteger s = new BigInteger(1, Arrays.copyOfRange(sign, RS_LEN, RS_LEN * 2));
ASN1EncodableVector v = new ASN1EncodableVector();
v.add(new ASN1Integer(r));
v.add(new ASN1Integer(s));
try {
return new DERSequence(v).getEncoded("DER");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private static boolean verifySignature(byte[] src, byte[] sign, BCECPublicKey sm2Key) throws Exception {
boolean res;
try {
byte[] sign_asn1 = rsPlainByteArrayToAsn1(sign);
Signature signature = Signature.getInstance(SIGNATURE_PARAM, PROV_NAME);
signature.setParameter(new SM2ParameterSpec("lxls".getBytes()));
signature.initVerify(sm2Key);
signature.update(src);
res = signature.verify(sign_asn1);
}catch (Exception e){
System.out.println("发生异常:"+e);
return false;
}
return res;
}
/**
* SM2验签入口
*/
public boolean sm2SignValidate(String hexPublicKey, byte[] value, String sortedString) {
try{
BCECPublicKey publicKey = getECPublicKeyByPublicKeyHex(hexPublicKey);
return verifySignature(sortedString.getBytes(), value, publicKey);
}catch (Exception e){
e.printStackTrace();
return false;
}
}
private static Key generateSm4Key(byte[] key) {
Key sm4Key = new SecretKeySpec(key, CIPHER_PARAM);
return sm4Key;
}
private static byte[] innerSM4Encrypt(byte[] src, byte[] key) throws Exception{
byte[] dest = null;
Cipher cipher = Cipher.getInstance(MODE_PARAM, PROV_NAME);
Key sm4Key = generateSm4Key(key);
cipher.init(Cipher.ENCRYPT_MODE, sm4Key);
dest = cipher.doFinal(src);
return dest;
}
private static byte[] innerSM4Decrypt(byte[] key,byte[] src) throws Exception{
byte[] dest = null;
Cipher cipher = Cipher.getInstance(MODE_PARAM, PROV_NAME);
Key sm4Key = generateSm4Key(key);
cipher.init(Cipher.DECRYPT_MODE, sm4Key);
dest = cipher.doFinal(src);
return dest;
}
/**
* SM4加密入口
*/
public byte[] sm4Encrypt(String sm4Key, String plainText) {
try{
return innerSM4Encrypt(plainText.getBytes(), sm4Key.getBytes());
}catch (Exception e){
e.printStackTrace();
return null;
}
}
/**
* SM4解密入口
*/
public String sm4Decrypt(String sm4Key, byte[] encBytes) {
try{
return new String(innerSM4Decrypt(sm4Key.getBytes(), encBytes));
}catch (Exception e){
e.printStackTrace();
return null;
}
}
}
- 签名验签上面的工具类和前端通不过,所以又找了一个工具类,因此,签名和验签使用的是下面的工具类
package com.lxls.personalachievement.tools.smnew;
import lombok.extern.slf4j.Slf4j;
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.engines.SM2Engine;
import org.bouncycastle.crypto.generators.ECKeyPairGenerator;
import org.bouncycastle.crypto.params.*;
import org.bouncycastle.crypto.signers.SM2Signer;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.jce.spec.ECParameterSpec;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.util.Strings;
import org.bouncycastle.util.encoders.Hex;
import java.math.BigInteger;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
/**
* 1. @description: SM2工具类
* 2. @author: xh
* 3. @time: 2022/3/18
*/
@Slf4j
public class SM2Util {
/**
* 生成SM2公私钥对
* @return
*/
private static AsymmetricCipherKeyPair genKeyPair0() {
//获取一条SM2曲线参数
X9ECParameters sm2ECParameters = GMNamedCurves.getByName("sm2p256v1");
//构造domain参数
ECDomainParameters domainParameters = new ECDomainParameters(sm2ECParameters.getCurve(),
sm2ECParameters.getG(), sm2ECParameters.getN());
//1.创建密钥生成器
ECKeyPairGenerator keyPairGenerator = new ECKeyPairGenerator();
//2.初始化生成器,带上随机数
try {
keyPairGenerator.init(new ECKeyGenerationParameters(domainParameters, SecureRandom.getInstance("SHA1PRNG")));
} catch (NoSuchAlgorithmException e) {
log.error("生成公私钥对时出现异常:", e);
// e.printStackTrace();
}
//3.生成密钥对
AsymmetricCipherKeyPair asymmetricCipherKeyPair = keyPairGenerator.generateKeyPair();
return asymmetricCipherKeyPair;
}
/**
* 生成公私钥对(默认压缩公钥)
* @return
*/
public static SMKeyPair genKeyPair() {
return genKeyPair(true);
}
/**
* 生成公私钥对
* @param compressedPubKey 是否压缩公钥
* @return
*/
public static SMKeyPair genKeyPair(boolean compressedPubKey) {
AsymmetricCipherKeyPair asymmetricCipherKeyPair = genKeyPair0();
//提取公钥点
ECPoint ecPoint = ((ECPublicKeyParameters) asymmetricCipherKeyPair.getPublic()).getQ();
//公钥前面的02或者03表示是压缩公钥,04表示未压缩公钥,04的时候,可以去掉前面的04
String pubKey = Hex.toHexString(ecPoint.getEncoded(compressedPubKey));
BigInteger privatekey = ((ECPrivateKeyParameters) asymmetricCipherKeyPair.getPrivate()).getD();
String priKey = privatekey.toString(16);
SMKeyPair keyPair = new SMKeyPair(priKey, pubKey);
return keyPair;
}
/**
* 私钥签名
* @param privateKey 私钥
* @param content 待签名内容
* @return
*/
public static String sign(String privateKey, String content) throws CryptoException {
//待签名内容转为字节数组
byte[] message = Hex.decode(content);
//获取一条SM2曲线参数
X9ECParameters sm2ECParameters = GMNamedCurves.getByName("sm2p256v1");
//构造domain参数
ECDomainParameters domainParameters = new ECDomainParameters(sm2ECParameters.getCurve(),
sm2ECParameters.getG(), sm2ECParameters.getN());
BigInteger privateKeyD = new BigInteger(privateKey, 16);
ECPrivateKeyParameters privateKeyParameters = new ECPrivateKeyParameters(privateKeyD, domainParameters);
//创建签名实例
SM2Signer sm2Signer = new SM2Signer();
//初始化签名实例,带上ID,国密的要求,ID默认值:1234567812345678
try {
sm2Signer.init(true, new ParametersWithID(new ParametersWithRandom(privateKeyParameters, SecureRandom.getInstance("SHA1PRNG")), Strings.toByteArray("1234567812345678")));
} catch (NoSuchAlgorithmException e) {
log.error("签名时出现异常:", e);
}
sm2Signer.update(message, 0, message.length);
//生成签名,签名分为两部分r和s,分别对应索引0和1的数组
byte[] signBytes = sm2Signer.generateSignature();
String sign = Hex.toHexString(signBytes);
return sign;
}
/**
* 将R或者S修正为固定字节数
* @param rs
* @return
*/
private static byte[] modifyRSFixedBytes(byte[] rs) {
int length = rs.length;
int fixedLength = 32;
byte[] result = new byte[fixedLength];
if (length < 32) {
System.arraycopy(rs, 0, result, fixedLength - length, length);
} else {
System.arraycopy(rs, length - fixedLength, result, 0, fixedLength);
}
return result;
}
/**
* 验证签名
* @param publicKey 公钥
* @param content 待签名内容
* @param sign 签名值
* @return
*/
public static boolean verify(String publicKey, String content, String sign) {
//待签名内容
byte[] message = Hex.decode(content);
byte[] signData = Hex.decode(sign);
// 获取一条SM2曲线参数
X9ECParameters sm2ECParameters = GMNamedCurves.getByName("sm2p256v1");
// 构造domain参数
ECDomainParameters domainParameters = new ECDomainParameters(sm2ECParameters.getCurve(),
sm2ECParameters.getG(),
sm2ECParameters.getN());
//提取公钥点
ECPoint pukPoint = sm2ECParameters.getCurve().decodePoint(Hex.decode(publicKey));
// 公钥前面的02或者03表示是压缩公钥,04表示未压缩公钥, 04的时候,可以去掉前面的04
ECPublicKeyParameters publicKeyParameters = new ECPublicKeyParameters(pukPoint, domainParameters);
//创建签名实例
SM2Signer sm2Signer = new SM2Signer();
ParametersWithID parametersWithID = new ParametersWithID(publicKeyParameters, Strings.toByteArray("1234567812345678"));
sm2Signer.init(false, parametersWithID);
sm2Signer.update(message, 0, message.length);
//验证签名结果
boolean verify = sm2Signer.verifySignature(signData);
return verify;
}
/**
* SM2加密算法
* @param publicKey 公钥
* @param data 数据
* @return
*/
public static String encrypt(String publicKey, String data){
// 获取一条SM2曲线参数
X9ECParameters sm2ECParameters = GMNamedCurves.getByName("sm2p256v1");
// 构造domain参数
ECDomainParameters domainParameters = new ECDomainParameters(sm2ECParameters.getCurve(),
sm2ECParameters.getG(),
sm2ECParameters.getN());
//提取公钥点
ECPoint pukPoint = sm2ECParameters.getCurve().decodePoint(Hex.decode(publicKey));
// 公钥前面的02或者03表示是压缩公钥,04表示未压缩公钥, 04的时候,可以去掉前面的04
ECPublicKeyParameters publicKeyParameters = new ECPublicKeyParameters(pukPoint, domainParameters);
SM2Engine sm2Engine = new SM2Engine();
sm2Engine.init(true, new ParametersWithRandom(publicKeyParameters, new SecureRandom()));
byte[] arrayOfBytes = null;
try {
byte[] in = data.getBytes("utf-8");
arrayOfBytes = sm2Engine.processBlock(in, 0, in.length);
} catch (Exception e) {
log.error("SM2加密时出现异常:", e);
}
return Hex.toHexString(arrayOfBytes);
}
/**
* SM2加密算法
* @param publicKey 公钥
* @param data 明文数据
* @return
*/
public static String encrypt(PublicKey publicKey, String data) {
ECPublicKeyParameters ecPublicKeyParameters = null;
if (publicKey instanceof BCECPublicKey) {
BCECPublicKey bcecPublicKey = (BCECPublicKey) publicKey;
ECParameterSpec ecParameterSpec = bcecPublicKey.getParameters();
ECDomainParameters ecDomainParameters = new ECDomainParameters(ecParameterSpec.getCurve(),
ecParameterSpec.getG(), ecParameterSpec.getN());
ecPublicKeyParameters = new ECPublicKeyParameters(bcecPublicKey.getQ(), ecDomainParameters);
}
SM2Engine sm2Engine = new SM2Engine();
sm2Engine.init(true, new ParametersWithRandom(ecPublicKeyParameters, new SecureRandom()));
byte[] arrayOfBytes = null;
try {
byte[] in = data.getBytes("utf-8");
arrayOfBytes = sm2Engine.processBlock(in,0, in.length);
} catch (Exception e) {
log.error("SM2加密时出现异常:", e);
}
return Hex.toHexString(arrayOfBytes);
}
/**
* SM2解密算法
* @param privateKey 私钥
* @param cipherData 密文数据
* @return
*/
public static String decrypt(String privateKey, String cipherData) {
byte[] cipherDataByte = Hex.decode(cipherData);
//获取一条SM2曲线参数
X9ECParameters sm2ECParameters = GMNamedCurves.getByName("sm2p256v1");
//构造domain参数
ECDomainParameters domainParameters = new ECDomainParameters(sm2ECParameters.getCurve(),
sm2ECParameters.getG(), sm2ECParameters.getN());
BigInteger privateKeyD = new BigInteger(privateKey, 16);
ECPrivateKeyParameters privateKeyParameters = new ECPrivateKeyParameters(privateKeyD, domainParameters);
SM2Engine sm2Engine = new SM2Engine();
sm2Engine.init(false, privateKeyParameters);
String result = null;
try {
byte[] arrayOfBytes = sm2Engine.processBlock(cipherDataByte, 0, cipherDataByte.length);
return new String(arrayOfBytes, "utf-8");
} catch (Exception e) {
log.error("SM2解密时出现异常:", e);
}
return result;
}
/**
* SM2解密算法
* @param privateKey 私钥
* @param cipherData 密文数据
* @return
*/
public static String decrypt(PrivateKey privateKey, String cipherData) {
byte[] cipherDataByte = Hex.decode(cipherData);
BCECPrivateKey bcecPrivateKey = (BCECPrivateKey) privateKey;
ECParameterSpec ecParameterSpec = bcecPrivateKey.getParameters();
ECDomainParameters ecDomainParameters = new ECDomainParameters(ecParameterSpec.getCurve(),
ecParameterSpec.getG(), ecParameterSpec.getN());
ECPrivateKeyParameters ecPrivateKeyParameters = new ECPrivateKeyParameters(bcecPrivateKey.getD(),
ecDomainParameters);
SM2Engine sm2Engine = new SM2Engine();
sm2Engine.init(false, ecPrivateKeyParameters);
String result = null;
try {
byte[] arrayOfBytes = sm2Engine.processBlock(cipherDataByte, 0, cipherDataByte.length);
return new String(arrayOfBytes, "utf-8");
} catch (Exception e) {
log.error("SM2解密时出现异常:", e);
}
return result;
}
/**
* 将未压缩公钥压缩成压缩公钥
* 公钥前面的02或者03表示是压缩公钥,04表示未压缩公钥, 04的时候,可以去掉前面的04
* @param pubKey 未压缩公钥(16进制,不要带头部04)
* @return
*/
public static String compressPubKey(String pubKey) {
pubKey = "04" + pubKey; //将未压缩公钥加上未压缩标识.
// 获取一条SM2曲线参数
X9ECParameters sm2ECParameters = GMNamedCurves.getByName("sm2p256v1");
//提取公钥点
ECPoint pukPoint = sm2ECParameters.getCurve().decodePoint(Hex.decode(pubKey));
String compressPubKey = Hex.toHexString(pukPoint.getEncoded(Boolean.TRUE));
return compressPubKey;
}
/**
* 将压缩的公钥解压为非压缩公钥
* @param compressKey 压缩公钥
* @return
*/
public static String unCompressPubKey(String compressKey) {
// 获取一条SM2曲线参数
X9ECParameters sm2ECParameters = GMNamedCurves.getByName("sm2p256v1");
//提取公钥点
ECPoint pukPoint = sm2ECParameters.getCurve().decodePoint(Hex.decode(compressKey));
String pubKey = Hex.toHexString(pukPoint.getEncoded(Boolean.FALSE));
//去掉前面的04 (04的时候,可以去掉前面的04)
pubKey = pubKey.substring(2);
return pubKey;
}
public static void main(String[] args) {
SMKeyPair smKeyPair = genKeyPair(false);
String priKey = smKeyPair.getPriKey();
System.out.println("私钥"+ priKey);
String pubKey = smKeyPair.getPubKey();
System.out.println("公钥"+ pubKey);
//公钥解压缩
String substring = pubKey.substring(2, pubKey.length());
String s = compressPubKey(substring);
System.out.println("压缩后" + s);
String s1 = unCompressPubKey(s);
System.out.println("解压后" + s1);
//明文
String text = "123123123";
System.out.println("测试明文文本" + text);
//签名验签测试
String sign = "";
try {
sign = sign(priKey, Hex.toHexString(text.getBytes()));
} catch (CryptoException e) {
e.printStackTrace();
}
System.out.println("生成签名" + sign);
boolean verify = verify(pubKey, Hex.toHexString(text.getBytes()), sign);
System.out.println("验签结果" + verify);
//加解密测试
String encryptData = encrypt(pubKey, text);
System.out.println("加密结果" + encryptData);
String decryptData = decrypt(priKey, encryptData);
System.out.println("解密结果" + decryptData);
}
}
package com.lxls.personalachievement.tools.smnew;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 1. @description: 钥匙对
* 2. @author: xh
* 3. @time: 2022/3/18
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class SMKeyPair {
//私钥
private String priKey;
//公钥
private String pubKey;
}
到了这里,关于终于把前后端sm加解密以及加签验证调通了。的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!