终于把前后端sm加解密以及加签验证调通了!
领导要求我对项目的数据传输安全考虑下,因此就想到了对敏感字段做加密和对请求、响应做数字签名验证。网上看了很多文章,可能是因为我对加密这块不了解,感觉都比较乱。所以前前后后花了4天才把前后端调通。特地写一篇文章记录下流程。这里使用的是sm国密算法。不对的地方请读者评论指出。
1.简单说明:
- 前端使用sm-crypto库
- 后端加密库使用bc库,架构上使用aop,注解等实现
2.具体实现-前端
- 加密流程:生成一个对称密钥,对每个字段使用sm4对称加密,然后进行base64编码。对称密钥使用sm2非对称加密
// 生成对称密钥exportfunctiongenSM4SymmetricKey(){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加密再转base64exportfunctionencryptovalue(value,symmetricKey){let key=StringToHex(symmetricKey)return Base64.btoa(HexToString(sm4.encrypt( value,key)));}
// 对象添加加密后的对称密钥属性exportfunctionaddSm4SymmetricKey(symmetricKey,obj){// 加密对称密钥,前面加04
sm2.doEncrypt(symmetricKey,publicKey,cipherMode);
obj["sm4SymmetricKey"]="04"+symmetricKey
return obj
}
- 解密流程:对后端响应的数据对象中的加密字段进行base64解码,sm2解密解出对称密钥,再使用对称密钥进行sm4解密
// 解密对称密钥exportfunctiondecryptSm4SymmetricKey(sm4SymmetricKey){// 去除开头的04
sm4SymmetricKey = sm4SymmetricKey.substr(2)return sm2.doDecrypt(sm4SymmetricKey,privateKey);}
// 字段sm4解密exportfunctiondecryptovalue(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数字签名。
exportfunctionsm2Signature(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签名验证
exportfunctionsm2VerifySignature(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@interfaceCrypt{}
@DatapublicclassUserextendsBaseObject{privateString name;@CryptprivateString password;privateString age;@CryptprivateString address;}
@Api(tags ="用户控制器")@ApiModel@RestController@ResponseBody@RequestMapping("/pa")publicclassUserController{@Crypt@PostMapping("test")publicResult<User>test(@RequestBodyUser user){User newUser =newUser();
newUser.setName("后端返回");
newUser.setPassword(user.getPassword());
newUser.setAddress(user.getAddress());
newUser.setAge(user.getAge());returnResult.success(newUser);}@Crypt@GetMapping("testget")publicResult<String>testget(){returnResult.success("hello");}}
创建切面类
packagecom.lxls.personalachievement.common.safe.aop;importcn.hutool.json.JSONUtil;importcom.alibaba.fastjson2.JSONObject;importcom.google.common.collect.Maps;importcom.google.common.io.BaseEncoding;importcom.lxls.personalachievement.common.result.Result;importcom.lxls.personalachievement.common.result.ResultCode;importcom.lxls.personalachievement.common.safe.SMConfig;importcom.lxls.personalachievement.common.safe.annotation.Crypt;importcom.lxls.personalachievement.tools.sm.BytesUtil;importcom.lxls.personalachievement.tools.sm.KeyUtil;importcom.lxls.personalachievement.tools.sm.SMUtil;importcom.lxls.personalachievement.tools.smnew.SM2Util;importorg.apache.commons.codec.binary.Base64;importorg.aspectj.lang.ProceedingJoinPoint;importorg.aspectj.lang.Signature;importorg.aspectj.lang.annotation.Around;importorg.aspectj.lang.annotation.Aspect;importorg.aspectj.lang.annotation.Pointcut;importorg.aspectj.lang.reflect.MethodSignature;importorg.bouncycastle.crypto.CryptoException;importorg.bouncycastle.util.encoders.Hex;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.cglib.beans.BeanMap;importorg.springframework.core.Ordered;importorg.springframework.core.annotation.Order;importorg.springframework.stereotype.Component;importorg.springframework.util.StringUtils;importorg.springframework.web.context.request.RequestAttributes;importorg.springframework.web.context.request.RequestContextHolder;importorg.springframework.web.context.request.ServletRequestAttributes;importjavax.servlet.http.HttpServletRequest;importjava.io.UnsupportedEncodingException;importjava.lang.reflect.Field;importjava.lang.reflect.Method;importjava.util.*;/**
* @author: WangQC
* @date 2023/2/10 13:34
*/@Order(Ordered.HIGHEST_PRECEDENCE)@Aspect@ComponentpublicclassAspectSafe{Logger log =LoggerFactory.getLogger(this.getClass());@AutowiredprivateSMConfig smConfig;@AutowiredprivateSMUtil smUtil;// 定义切点(在Crypt切点或者controller方法上)@Pointcut("@annotation(com.lxls.personalachievement.common.safe.annotation.Crypt)")publicvoidpt(){}//环绕通知,方法前后都可以进行增强@Around("pt()")publicObjectcrypt(ProceedingJoinPoint joinPoint)throwsThrowable{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]instanceofResult){// 返回对象为Result,则表示出错应当返回前端return decryptObj[0];}// 验签if(!handleSignatureVerificate(requestObj)){// 验签失败返回returnResult.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);returnResult.failed(ResultCode.CRYPTOERROR,ResultCode.CRYPTOERROR.getMsg());}catch(Throwable throwable){
throwable.printStackTrace();
log.error("SecureFieldAop处理出现异常{}", throwable);returnResult.failed(ResultCode.CRYPTOERROR,ResultCode.CRYPTOERROR.getMsg());}return responseObj;}// 解密处理privateObject[]handleDecrypt(Object requsetObj)throwsIllegalAccessException{// 如果是post或put才执行
log.debug("待解密参数:{}",requsetObj);if(Objects.isNull(requsetObj)){returnnull;}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=newObject[]{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=newObject[]{requsetObj};return objects;}// 加密处理privateObjecthandleEncrypt(Object responseObj)throwsIllegalAccessException,UnsupportedEncodingException{
log.debug("待加密参数,{}",responseObj);if(Objects.isNull(responseObj)){returnnull;}// 获取请求参数对象Class<?> requestObjClass = responseObj.getClass();// 获取类上的注解,如果存在,则整个对象属性都进行加密Crypt annotation = requestObjClass.getAnnotation(Crypt.class);// 获取字段Field[] fields = requestObjClass.getDeclaredFields();Field[] superFields = requestObjClass.getSuperclass().getDeclaredFields();// 生成sm4 keyString 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;}// 验签处理privateBooleanhandleSignatureVerificate(Object requsetObj)throwsIllegalAccessException{/**
* 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;}// 加签处理privateResulthandleSign(Object responseObj)throwsIllegalAccessException,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;}}returnResult.success(responseObj);}// 生成待签名串privateStringgenerateSignatureStr(Object obj){BeanMap beanMap =BeanMap.create(obj);Map<String,Object> map =Maps.newHashMap(beanMap);
map.remove("sm4SymmetricKey");
map.remove("signature");returnKeyUtil.sortMapToSTring(map);}}
- sm工具类
packagecom.lxls.personalachievement.tools.sm;importorg.apache.commons.codec.binary.Base64;importorg.bouncycastle.asn1.ASN1EncodableVector;importorg.bouncycastle.asn1.ASN1Integer;importorg.bouncycastle.asn1.ASN1Sequence;importorg.bouncycastle.asn1.DERSequence;importorg.bouncycastle.asn1.gm.GMNamedCurves;importorg.bouncycastle.asn1.x9.X9ECParameters;importorg.bouncycastle.crypto.engines.SM2Engine;importorg.bouncycastle.crypto.params.ECDomainParameters;importorg.bouncycastle.crypto.params.ECPrivateKeyParameters;importorg.bouncycastle.crypto.params.ECPublicKeyParameters;importorg.bouncycastle.crypto.params.ParametersWithRandom;importorg.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;importorg.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;importorg.bouncycastle.jcajce.spec.SM2ParameterSpec;importorg.bouncycastle.jce.provider.BouncyCastleProvider;importorg.bouncycastle.jce.spec.ECParameterSpec;importorg.bouncycastle.jce.spec.ECPrivateKeySpec;importorg.bouncycastle.jce.spec.ECPublicKeySpec;importorg.bouncycastle.util.encoders.Hex;importorg.springframework.stereotype.Component;importjavax.crypto.Cipher;importjavax.crypto.spec.SecretKeySpec;importjava.io.IOException;importjava.math.BigInteger;importjava.security.Key;importjava.security.SecureRandom;importjava.security.Security;importjava.security.Signature;importjava.util.Arrays;@ComponentpublicclassSMUtil{publicstaticfinalintRS_LEN=32;privatestaticfinalStringSIGNATURE_PARAM="SM3withSM2";privatestaticfinalStringPROV_NAME=BouncyCastleProvider.PROVIDER_NAME;//SM2曲线名称privatestaticfinalStringCURVE_NAME="sm2p256v1";//SM2相关参数privatestaticfinalX9ECParameters x9ECParameters =GMNamedCurves.getByName(CURVE_NAME);//椭圆曲线参数规格privatestaticfinalECParameterSpec ecParameterSpec =newECParameterSpec(x9ECParameters.getCurve(), x9ECParameters.getG(), x9ECParameters.getN(), x9ECParameters.getH());privatestaticfinalStringCIPHER_PARAM="SM4";privatestaticfinalStringMODE_PARAM="SM4/ECB/PKCS7Padding";//只需加载一次static{if(Security.getProperty(PROV_NAME)==null){Security.addProvider(newBouncyCastleProvider());}}privatestaticBCECPublicKeygetECPublicKeyByPublicKeyHex(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 =newBigInteger(stringX,16);BigInteger y =newBigInteger(stringY,16);//通过公钥x、y分量创建椭圆曲线公钥规范ECPublicKeySpec ecPublicKeySpec =newECPublicKeySpec(x9ECParameters.getCurve().createPoint(x, y), ecParameterSpec);//通过椭圆曲线公钥规范,创建出椭圆曲线公钥对象(可用于SM2加密及验签)returnnewBCECPublicKey("EC", ecPublicKeySpec,BouncyCastleProvider.CONFIGURATION);}privatestaticbyte[]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 =newECDomainParameters(ecParameterSpec.getCurve(),
ecParameterSpec.getG(), ecParameterSpec.getN());//通过公钥值和公钥基本参数创建公钥参数对象ECPublicKeyParameters ecPublicKeyParameters =newECPublicKeyParameters(publicKey.getQ(), ecDomainParameters);//根据加密模式实例化SM2公钥加密引擎SM2Engine sm2Engine =newSM2Engine(mode);//初始化加密引擎
sm2Engine.init(true,newParametersWithRandom(ecPublicKeyParameters,newSecureRandom()));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加密入口
*/publicStringsm2Encrypt(String hexPublicKey,String plainText){//生产bc公钥对象BCECPublicKey publicKey =getECPublicKeyByPublicKeyHex(hexPublicKey);//加密try{byte[] encText =innerSM2Encrypt(publicKey, plainText,1);returnHex.toHexString(encText);}catch(Exception e){
e.printStackTrace();returnnull;}}privatestaticBCECPrivateKeygetBCECPrivateKeyByPrivateKeyHex(String privateKeyHex){//将十六进制私钥字符串转换为BigInteger对象BigInteger d =newBigInteger(privateKeyHex,16);//通过私钥和私钥域参数集创建椭圆曲线私钥规范ECPrivateKeySpec ecPrivateKeySpec =newECPrivateKeySpec(d, ecParameterSpec);//通过椭圆曲线私钥规范,创建出椭圆曲线私钥对象(可用于SM2解密和签名)returnnewBCECPrivateKey("EC", ecPrivateKeySpec,BouncyCastleProvider.CONFIGURATION);}privatestaticbyte[]innerSM2Decrypt(BCECPrivateKey privateKey,byte[] cipherData,int modeType)throwsException{//解密模式SM2Engine.Mode mode =SM2Engine.Mode.C1C3C2;if(modeType !=1)
mode =SM2Engine.Mode.C1C2C3;//通过私钥对象获取私钥的基本域参数。ECParameterSpec ecParameterSpec = privateKey.getParameters();ECDomainParameters ecDomainParameters =newECDomainParameters(ecParameterSpec.getCurve(),
ecParameterSpec.getG(), ecParameterSpec.getN());//通过私钥值和私钥钥基本参数创建私钥参数对象ECPrivateKeyParameters ecPrivateKeyParameters =newECPrivateKeyParameters(privateKey.getD(),
ecDomainParameters);//通过解密模式创建解密引擎并初始化SM2Engine sm2Engine =newSM2Engine(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());}returnnull;}/**
* SM2解密入口
*/publicStringsm2Decrypt(String hexPrivateKey,byte[] encBytes){try{BCECPrivateKey privateKey =getBCECPrivateKeyByPrivateKeyHex(hexPrivateKey);byte[] decResult =innerSM2Decrypt(privateKey, encBytes,1);returnnewString(decResult);}catch(Exception e){
e.printStackTrace();returnnull;}}privatestaticbyte[]signature(byte[] src,BCECPrivateKey sm2Key)throwsException{byte[] dest =null;Signature signature =Signature.getInstance(SIGNATURE_PARAM,PROV_NAME);
signature.setParameter(newSM2ParameterSpec("lxls".getBytes()));
signature.initSign(sm2Key);
signature.update(src);
dest = signature.sign();returnans1ToRS(dest);}privatestaticbyte[]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 =newbyte[RS_LEN*2];System.arraycopy(r,0, result,0, r.length);System.arraycopy(s,0, result,RS_LEN, s.length);return result;}privatestaticbyte[]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<=32byte[] rs = rOrS.toByteArray();if(rs.length ==RS_LEN)return rs;elseif(rs.length ==RS_LEN+1&& rs[0]==0)returnArrays.copyOfRange(rs,1,RS_LEN+1);elseif(rs.length <RS_LEN){byte[] result =newbyte[RS_LEN];Arrays.fill(result,(byte)0);System.arraycopy(rs,0, result,RS_LEN- rs.length, rs.length);return result;}else{thrownewRuntimeException("err rs: "+Hex.toHexString(rs));}}/**
* SM2加签入口
*/publicbyte[]sm2Sign(String hexPrivateKey,String sortedString){try{BCECPrivateKey privateKey =getBCECPrivateKeyByPrivateKeyHex(hexPrivateKey);byte[]signResult =signature(sortedString.getBytes(), privateKey);return signResult;}catch(Exception e){
e.printStackTrace();returnnull;}}privatestaticbyte[]rsPlainByteArrayToAsn1(byte[] sign){if(sign.length !=RS_LEN*2)thrownewRuntimeException("err rs. ");BigInteger r =newBigInteger(1,Arrays.copyOfRange(sign,0,RS_LEN));BigInteger s =newBigInteger(1,Arrays.copyOfRange(sign,RS_LEN,RS_LEN*2));ASN1EncodableVector v =newASN1EncodableVector();
v.add(newASN1Integer(r));
v.add(newASN1Integer(s));try{returnnewDERSequence(v).getEncoded("DER");}catch(IOException e){thrownewRuntimeException(e);}}privatestaticbooleanverifySignature(byte[] src,byte[] sign,BCECPublicKey sm2Key)throwsException{boolean res;try{byte[] sign_asn1 =rsPlainByteArrayToAsn1(sign);Signature signature =Signature.getInstance(SIGNATURE_PARAM,PROV_NAME);
signature.setParameter(newSM2ParameterSpec("lxls".getBytes()));
signature.initVerify(sm2Key);
signature.update(src);
res = signature.verify(sign_asn1);}catch(Exception e){System.out.println("发生异常:"+e);returnfalse;}return res;}/**
* SM2验签入口
*/publicbooleansm2SignValidate(String hexPublicKey,byte[] value,String sortedString){try{BCECPublicKey publicKey =getECPublicKeyByPublicKeyHex(hexPublicKey);returnverifySignature(sortedString.getBytes(), value, publicKey);}catch(Exception e){
e.printStackTrace();returnfalse;}}privatestaticKeygenerateSm4Key(byte[] key){Key sm4Key =newSecretKeySpec(key,CIPHER_PARAM);return sm4Key;}privatestaticbyte[]innerSM4Encrypt(byte[] src,byte[] key)throwsException{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;}privatestaticbyte[]innerSM4Decrypt(byte[] key,byte[] src)throwsException{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加密入口
*/publicbyte[]sm4Encrypt(String sm4Key,String plainText){try{returninnerSM4Encrypt(plainText.getBytes(), sm4Key.getBytes());}catch(Exception e){
e.printStackTrace();returnnull;}}/**
* SM4解密入口
*/publicStringsm4Decrypt(String sm4Key,byte[] encBytes){try{returnnewString(innerSM4Decrypt(sm4Key.getBytes(), encBytes));}catch(Exception e){
e.printStackTrace();returnnull;}}}
- 签名验签上面的工具类和前端通不过,所以又找了一个工具类,因此,签名和验签使用的是下面的工具类
packagecom.lxls.personalachievement.tools.smnew;importlombok.extern.slf4j.Slf4j;importorg.bouncycastle.asn1.gm.GMNamedCurves;importorg.bouncycastle.asn1.x9.X9ECParameters;importorg.bouncycastle.crypto.AsymmetricCipherKeyPair;importorg.bouncycastle.crypto.CryptoException;importorg.bouncycastle.crypto.engines.SM2Engine;importorg.bouncycastle.crypto.generators.ECKeyPairGenerator;importorg.bouncycastle.crypto.params.*;importorg.bouncycastle.crypto.signers.SM2Signer;importorg.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;importorg.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;importorg.bouncycastle.jce.spec.ECParameterSpec;importorg.bouncycastle.math.ec.ECPoint;importorg.bouncycastle.util.Strings;importorg.bouncycastle.util.encoders.Hex;importjava.math.BigInteger;importjava.security.NoSuchAlgorithmException;importjava.security.PrivateKey;importjava.security.PublicKey;importjava.security.SecureRandom;/**
* 1. @description: SM2工具类
* 2. @author: xh
* 3. @time: 2022/3/18
*/@Slf4jpublicclassSM2Util{/**
* 生成SM2公私钥对
* @return
*/privatestaticAsymmetricCipherKeyPairgenKeyPair0(){//获取一条SM2曲线参数X9ECParameters sm2ECParameters =GMNamedCurves.getByName("sm2p256v1");//构造domain参数ECDomainParameters domainParameters =newECDomainParameters(sm2ECParameters.getCurve(),
sm2ECParameters.getG(), sm2ECParameters.getN());//1.创建密钥生成器ECKeyPairGenerator keyPairGenerator =newECKeyPairGenerator();//2.初始化生成器,带上随机数try{
keyPairGenerator.init(newECKeyGenerationParameters(domainParameters,SecureRandom.getInstance("SHA1PRNG")));}catch(NoSuchAlgorithmException e){
log.error("生成公私钥对时出现异常:", e);// e.printStackTrace();}//3.生成密钥对AsymmetricCipherKeyPair asymmetricCipherKeyPair = keyPairGenerator.generateKeyPair();return asymmetricCipherKeyPair;}/**
* 生成公私钥对(默认压缩公钥)
* @return
*/publicstaticSMKeyPairgenKeyPair(){returngenKeyPair(true);}/**
* 生成公私钥对
* @param compressedPubKey 是否压缩公钥
* @return
*/publicstaticSMKeyPairgenKeyPair(boolean compressedPubKey){AsymmetricCipherKeyPair asymmetricCipherKeyPair =genKeyPair0();//提取公钥点ECPoint ecPoint =((ECPublicKeyParameters) asymmetricCipherKeyPair.getPublic()).getQ();//公钥前面的02或者03表示是压缩公钥,04表示未压缩公钥,04的时候,可以去掉前面的04String pubKey =Hex.toHexString(ecPoint.getEncoded(compressedPubKey));BigInteger privatekey =((ECPrivateKeyParameters) asymmetricCipherKeyPair.getPrivate()).getD();String priKey = privatekey.toString(16);SMKeyPair keyPair =newSMKeyPair(priKey, pubKey);return keyPair;}/**
* 私钥签名
* @param privateKey 私钥
* @param content 待签名内容
* @return
*/publicstaticStringsign(String privateKey,String content)throwsCryptoException{//待签名内容转为字节数组byte[] message =Hex.decode(content);//获取一条SM2曲线参数X9ECParameters sm2ECParameters =GMNamedCurves.getByName("sm2p256v1");//构造domain参数ECDomainParameters domainParameters =newECDomainParameters(sm2ECParameters.getCurve(),
sm2ECParameters.getG(), sm2ECParameters.getN());BigInteger privateKeyD =newBigInteger(privateKey,16);ECPrivateKeyParameters privateKeyParameters =newECPrivateKeyParameters(privateKeyD, domainParameters);//创建签名实例SM2Signer sm2Signer =newSM2Signer();//初始化签名实例,带上ID,国密的要求,ID默认值:1234567812345678try{
sm2Signer.init(true,newParametersWithID(newParametersWithRandom(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
*/privatestaticbyte[]modifyRSFixedBytes(byte[] rs){int length = rs.length;int fixedLength =32;byte[] result =newbyte[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
*/publicstaticbooleanverify(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 =newECDomainParameters(sm2ECParameters.getCurve(),
sm2ECParameters.getG(),
sm2ECParameters.getN());//提取公钥点ECPoint pukPoint = sm2ECParameters.getCurve().decodePoint(Hex.decode(publicKey));// 公钥前面的02或者03表示是压缩公钥,04表示未压缩公钥, 04的时候,可以去掉前面的04ECPublicKeyParameters publicKeyParameters =newECPublicKeyParameters(pukPoint, domainParameters);//创建签名实例SM2Signer sm2Signer =newSM2Signer();ParametersWithID parametersWithID =newParametersWithID(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
*/publicstaticStringencrypt(String publicKey,String data){// 获取一条SM2曲线参数X9ECParameters sm2ECParameters =GMNamedCurves.getByName("sm2p256v1");// 构造domain参数ECDomainParameters domainParameters =newECDomainParameters(sm2ECParameters.getCurve(),
sm2ECParameters.getG(),
sm2ECParameters.getN());//提取公钥点ECPoint pukPoint = sm2ECParameters.getCurve().decodePoint(Hex.decode(publicKey));// 公钥前面的02或者03表示是压缩公钥,04表示未压缩公钥, 04的时候,可以去掉前面的04ECPublicKeyParameters publicKeyParameters =newECPublicKeyParameters(pukPoint, domainParameters);SM2Engine sm2Engine =newSM2Engine();
sm2Engine.init(true,newParametersWithRandom(publicKeyParameters,newSecureRandom()));byte[] arrayOfBytes =null;try{byte[] in = data.getBytes("utf-8");
arrayOfBytes = sm2Engine.processBlock(in,0, in.length);}catch(Exception e){
log.error("SM2加密时出现异常:", e);}returnHex.toHexString(arrayOfBytes);}/**
* SM2加密算法
* @param publicKey 公钥
* @param data 明文数据
* @return
*/publicstaticStringencrypt(PublicKey publicKey,String data){ECPublicKeyParameters ecPublicKeyParameters =null;if(publicKey instanceofBCECPublicKey){BCECPublicKey bcecPublicKey =(BCECPublicKey) publicKey;ECParameterSpec ecParameterSpec = bcecPublicKey.getParameters();ECDomainParameters ecDomainParameters =newECDomainParameters(ecParameterSpec.getCurve(),
ecParameterSpec.getG(), ecParameterSpec.getN());
ecPublicKeyParameters =newECPublicKeyParameters(bcecPublicKey.getQ(), ecDomainParameters);}SM2Engine sm2Engine =newSM2Engine();
sm2Engine.init(true,newParametersWithRandom(ecPublicKeyParameters,newSecureRandom()));byte[] arrayOfBytes =null;try{byte[] in = data.getBytes("utf-8");
arrayOfBytes = sm2Engine.processBlock(in,0, in.length);}catch(Exception e){
log.error("SM2加密时出现异常:", e);}returnHex.toHexString(arrayOfBytes);}/**
* SM2解密算法
* @param privateKey 私钥
* @param cipherData 密文数据
* @return
*/publicstaticStringdecrypt(String privateKey,String cipherData){byte[] cipherDataByte =Hex.decode(cipherData);//获取一条SM2曲线参数X9ECParameters sm2ECParameters =GMNamedCurves.getByName("sm2p256v1");//构造domain参数ECDomainParameters domainParameters =newECDomainParameters(sm2ECParameters.getCurve(),
sm2ECParameters.getG(), sm2ECParameters.getN());BigInteger privateKeyD =newBigInteger(privateKey,16);ECPrivateKeyParameters privateKeyParameters =newECPrivateKeyParameters(privateKeyD, domainParameters);SM2Engine sm2Engine =newSM2Engine();
sm2Engine.init(false, privateKeyParameters);String result =null;try{byte[] arrayOfBytes = sm2Engine.processBlock(cipherDataByte,0, cipherDataByte.length);returnnewString(arrayOfBytes,"utf-8");}catch(Exception e){
log.error("SM2解密时出现异常:", e);}return result;}/**
* SM2解密算法
* @param privateKey 私钥
* @param cipherData 密文数据
* @return
*/publicstaticStringdecrypt(PrivateKey privateKey,String cipherData){byte[] cipherDataByte =Hex.decode(cipherData);BCECPrivateKey bcecPrivateKey =(BCECPrivateKey) privateKey;ECParameterSpec ecParameterSpec = bcecPrivateKey.getParameters();ECDomainParameters ecDomainParameters =newECDomainParameters(ecParameterSpec.getCurve(),
ecParameterSpec.getG(), ecParameterSpec.getN());ECPrivateKeyParameters ecPrivateKeyParameters =newECPrivateKeyParameters(bcecPrivateKey.getD(),
ecDomainParameters);SM2Engine sm2Engine =newSM2Engine();
sm2Engine.init(false, ecPrivateKeyParameters);String result =null;try{byte[] arrayOfBytes = sm2Engine.processBlock(cipherDataByte,0, cipherDataByte.length);returnnewString(arrayOfBytes,"utf-8");}catch(Exception e){
log.error("SM2解密时出现异常:", e);}return result;}/**
* 将未压缩公钥压缩成压缩公钥
* 公钥前面的02或者03表示是压缩公钥,04表示未压缩公钥, 04的时候,可以去掉前面的04
* @param pubKey 未压缩公钥(16进制,不要带头部04)
* @return
*/publicstaticStringcompressPubKey(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
*/publicstaticStringunCompressPubKey(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;}publicstaticvoidmain(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);}}
packagecom.lxls.personalachievement.tools.smnew;importlombok.AllArgsConstructor;importlombok.Data;importlombok.NoArgsConstructor;/**
* 1. @description: 钥匙对
* 2. @author: xh
* 3. @time: 2022/3/18
*/@Data@AllArgsConstructor@NoArgsConstructorpublicclassSMKeyPair{//私钥privateString priKey;//公钥privateString pubKey;}
本文转载自: https://blog.csdn.net/qq_41846013/article/details/129144821
版权归原作者 清风顾孤客 所有, 如有侵权,请联系我们删除。
版权归原作者 清风顾孤客 所有, 如有侵权,请联系我们删除。