终于把前后端sm加解密以及加签验证调通了。

2023-05-16

终于把前后端sm加解密以及加签验证调通了!

	领导要求我对项目的数据传输安全考虑下,因此就想到了对敏感字段做加密和对请求、响应做数字签名验证。网上看了很多文章,可能是因为我对加密这块不了解,感觉都比较乱。所以前前后后花了4天才把前后端调通。特地写一篇文章记录下流程。这里使用的是sm国密算法。不对的地方请读者评论指出。

1.简单说明:

  • 前端使用sm-crypto库

  • 后端加密库使用bc库,架构上使用aop,注解等实现

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");
    }
}

创建切面类

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;

}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

终于把前后端sm加解密以及加签验证调通了。 的相关文章

  • 【论文写作】Word中公式快捷输入方式

    环境 Win10 64位 用到软件 Mirsoft Word MathType Mathpix snipping tool Quicker 说明 xff1a 点击链接可以直达官网 一 前言 针对Word中公式输入效率低的问题 xff0c 本
  • 练习7-10 查找指定字符 (15分)

    本题要求编写程序 xff0c 从给定字符串中查找某指定的字符 输入格式 xff1a 输入的第一行是一个待查找的字符 第二行是一个以回车结束的非空字符串 xff08 不超过80个字符 xff09 输出格式 xff1a 如果找到 xff0c 在
  • 用cropper.js裁剪图片并上传到服务器,解析base64转存图片到本地

    今天要写上传图片功能 xff0c 研究了一下cropper 将图片上传服务器并保存到本地 html lt html gt lt head gt lt title gt 基于cropper js的图片裁剪 lt title gt lt met
  • 通讯协议详解

    1 xff0c 概念 网络协议指的是计算机网络中互相通信的对等实体之间交换信息时所必须遵守的规则的集合 网络上的计算机之间是如何交换信息的呢 xff1f 就像我们说话用某种语言一样 xff0c 在网络上的各台计算机之间也有一种语言 xff0
  • 自动识别击打控制系统

    目录 摘 要 关键词 一 系统方案 1 1 系统基本方案 1 2 程序算法的具体流程 二 视觉程序识别框架 2 1多线程 2 2 opencv配置文件 2 3 主函数 三 装甲板识别算法 3 1 装甲板识别 3 2 识别函数介绍 四 目标位
  • 基于stm32风力摆控制系统(电赛获得省一)

    目录 需要源文档及程序进入主页 一 系统方案 完整文档以及代码可主页私 1 1 系统基本方案 1 1 1 控制方案设计 1 1 2 机械结构方案设计
  • 基于stm32的所有嵌入式项目代码

    nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp 本人本科和硕士阶段的专业都是嵌入式方向 做了许许多多的项目 包括51 stm32 freeRTOS linux操作系统 多进程线程实现功能 包括裸机开发 驱动开
  • 基于图像处理的水果自助售卖系统(自助水果售卖机)

    目录 第一章 nbsp 概述 1 1 发展概要 1 2 国内外研究现状 1 3 研究目的和意义 1 4 方案介绍
  • 基于stm32的无人机控制系统设计

    基于stm32的无人机控制系统设计 整篇文章有两万字左右 字数太多了 实在是懒得全部放在这上面来 太废时间了 需要完整论文可主页联系 第一章 前言 1 1项目背景和意义 1 2国内外发展现状 1 3本文研究的主要内容 第二章 设计方案论证与
  • 基于Robot Studio的工业机器人汽车喷涂仿真设计

    基于Robot Studio的工业机器人汽车喷涂仿真设计 整篇文章字数有一万四左右 图片太多了 实在是懒得全部放在这上面来 太废时间了 获得完整论文关注可查看主页私信我 摘要 关键词 1 绪论 1 1研究背景与意义 1 2国内外研究现状 2
  • 基于单片机的压力流量报警器(附代码+仿真+论文)

    基于单片机的压力流量报警器 附代码 仿真 论文 完整论文 代码 仿真可关注我在主页私我 摘要 关键字 第一章绪论 1 1课题背景及其意义 1 2 国内外的研究状况 1 3本文的主要研究内容及论文结构安排 第二章 方案的设计与论证 2 1控制
  • 基于STM32的微型电子琴设计

    基于STM32的微型电子琴设计 第一章 总体设计 1 1 系统功能 1 2 主要技术性能指标 第二章硬件设计 2 1 整体硬件图 2 2 按键模块 2 3 扬声器模块 2 4 显示模块 2 5 主控模块 第三章 软件设计 3 1 主要工作原
  • 百度2015校园招聘软件开发笔试题及答案

    简单题 xff08 本题共30分 xff09 请简述Tcp ip的3次握手以及4次挥手过程 xff1f 并解释为何关闭连接需要4次挥手 10分 详细答案参见TCP IP协议三次握手与四次握手流程解析 TCP三次握手 四次挥手过程如下 通常情
  • 智能算法实现PID智能车控制系统

    智能算法实现PID智能车控制系统 TOC 智能算法实现PID智能车控制系统 摘要 关键词 第一章绪论 1 1智能车概述 1 2智能PID研究现状 1 3本文工作 第二章 PID控制简介 第三章 内模PID简介 3 1 内模PID控制 第四章
  • esp8266WiFi模块通过MQTT连接华为云

    esp8266WiFi模块通过MQTT连接华为云 总结 xff1a 一 MQTT透传AT固件烧录二 串口调试2 1 设置模块为STA模式2 2 连接WiFi2 3 设置MQTT的登陆用户名与密码2 4 设置MQTT的ClientID2 5
  • tx2性能不够怎么办

    关闭pycharm xff0c 使用终端直接Python3 5 加路径脚本名运行
  • 瑞泰烧录捞取

    关于将pc主机上的镜像文件拷贝到tx2上的方法 一 给Linux主机搭建环境 2 2 解压 Linux Driver Package tar vxjf Tegra lt t arch ver gt Linux R aarch64 tbz2
  • Realsense D435i 在ubuntu上安装SDK与ROS Wrapper 运行ORB-SLAM2、RTAB和VINS-Mono

    前言 等了一个月的realsense d435i终于发货了 xff01 这款D435i xff08 见下图 xff09 在D435的基础上 xff0c 另外搭载了博世的惯性测量单元 xff08 IMU xff09 xff0c 可以作为研究V
  • 如何用Realsense D435i运行VINS-Mono等VIO算法 获取IMU同步数据

    前言 Intel Realsense D435i在D435的基础上硬件融合了IMU xff0c 然而目前网上关于这款摄像头的资料非常少 xff0c 本文主要介绍自己拿着d435i历经曲折最后成功运行VINS Mono的过程 重要 最近官方更
  • VINS-Mono代码解读——启动文件launch与参数配置文件yaml介绍

    前言 一般我们通过以下命令运行VINS Mono跑MH 01数据集 roslaunch vins estimator euroc launch roslaunch vins estimator vins rviz launch rosbag

随机推荐