数字信封+数字签名工具类测试样例(Java实现)

news2025/2/24 14:12:20

加解密过程

加密:

  1. 生成加密方SM2密钥对用于签名
  2. 使用生成的SM2私钥生成数字签名
  3. 生成SM4对称密钥对明文进行对称加密
  4. 使用与解密方提前约定好的SM2公钥对第三步中的SM4对称密钥进行非对称加密
  5. 把【加密方SM2公钥】、【数字签名】、【SM4对称加密后的密文】和【SM2非对称加密后的SM4密钥密文】四个参数封装成数字信封发送给解密方

解密:

  1. 获取加密方发送过来的数字信封
  2. 使用与加密方提前约定好的SM2私钥解密数字信封中的【SM2非对称加密后的SM4密钥密文】,得到SM4密钥
  3. 使用SM4密钥解密数字信封中的【SM4对称加密后的密文】,得到数据明文
  4. 使用数字信封中的【数字签名】、【加密方SM2公钥】对得到的数据明文进行数字签名验证,得到验证结果
  5. 如为true,表明数据未被篡改;否则,数据可能已被篡改

运行结果截图
在这里插入图片描述

下面为具体实现代码
DigitalEnvelopeUtilsTest.java

package utils;

import utils.crypto.Constant;
import utils.crypto.exception.BadRequestException;
import org.bouncycastle.crypto.engines.SM4Engine;
import org.bouncycastle.crypto.modes.CBCBlockCipher;
import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher;
import org.bouncycastle.crypto.params.*;
import org.bouncycastle.jce.ECNamedCurveTable;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.crypto.*;
import java.math.BigInteger;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Arrays;
import java.util.Base64;

/**
 * @className: DigitalEnvelopeUtilsTest
 * @description: 数字信封工具类测试
 * @author: liuzijian
 * @date: 2024/7/27 18:28
 * @version: 1.0
 **/
public class DigitalEnvelopeUtilsTest {

    private static final Logger logger = LoggerFactory.getLogger(DigitalEnvelopeUtilsTest.class);
    
    private static String privateKey = "ANmPM+GwxdM7Yo/YhftxmfEqqjiHl9qKuMU55+lEfcEW";
    private static String publicKey = "BBQgpX+nrdNbyVmhQYlcED631eIYGKFj05+AoPMthp3pnum04Na8LG8sMul4Ro2W+DZaO/7XZvRievGY8DG2OoU=";

    static {
        Security.addProvider(new BouncyCastleProvider());
    }

//    /**
//     * 数字信封加密
//     * @param originalData
//     * @return encParam
//     */
//    public static EncParam enc(byte[] originalData){
//        try {
//            // 1. 生成数字签名和签名公钥
//            // 生成SM2密钥对用于签名
//            KeyPair keyPair = generateSm2KeyPair();
//            PrivateKey signPrivateKey = keyPair.getPrivate();
//            PublicKey signPublicKey = keyPair.getPublic();
//            // 生成数字签名
//            byte[] signature = signWithSm3(originalData, signPrivateKey);
//
//            // 2. 数字信封(生成数据密文+对称密钥密文)
//            // SM4对称加密
//            byte[] sm4Key = generateKey();
//            byte[] iv = generateIV();
//            byte[] encryptedData = encryptWithSm4(originalData, sm4Key, iv);
//            byte[] encryptionSm4Key = SM2Utils.asymmetricKeyEncrypt(Base64.getDecoder().decode(publicKey), Constant.SM2_ALGORITHM, sm4Key);
//
//            return new EncParam(signature, signPublicKey, encryptedData, encryptionSm4Key, iv);
//        }catch (Exception e){
//            logger.error("数字信封加密失败", e);
//            return null;
//        }
//    }
//
//    /**
//     * 数字信封解密
//     * @param encParam
//     * @return decParam
//     */
//    public static DecParam dec(EncParam encParam) throws Exception {
//        try {
//            // 1. SM2解密获取SM4密钥
//            byte[] deserializeSm4Key = SM2Utils.asymmetricKeyDecrypt(Base64.getDecoder().decode(privateKey), Constant.SM2_ALGORITHM, encParam.getEncryptionSm4Key());
//
//            // 2. SM4解密
//            byte[] decryptedData = decryptWithSm4(encParam.getEncryptedData(), deserializeSm4Key, encParam.getIv());
//
//            // 3. 验证签名
//            boolean isCorrect = verifyWithSm3(decryptedData, encParam.getSignature(), encParam.getSignPublicKey());
//            return new DecParam(decryptedData, isCorrect);
//        }catch (Exception e){
//            logger.error("数字信封解密失败", e);
//            throw new Exception("数字信封解密失败", e);
//        }
//    }

    /**
     * 生成SM2密钥对
     * @return SM2密钥对
     */
    private static KeyPair generateSm2KeyPair() throws Exception{
        try {
            KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(Constant.EC_ALGORITHM, Constant.BC_ALGORITHM);
            ECNamedCurveParameterSpec ecSpec = ECNamedCurveTable.getParameterSpec(Constant.SM2_CURVES_NAME);
            keyPairGenerator.initialize(ecSpec);
            return keyPairGenerator.generateKeyPair();
        } catch (Exception e) {
            logger.error("生成SM2密钥对失败", e);
            throw new Exception("生成SM2密钥对失败", e);
        }
    }

    /**
     * 使用SM3签名
     * @param data
     * @param privateKey
     * @return  签名
     */
    private static byte[] signWithSm3(String data, PrivateKey privateKey) throws Exception{
        try {
            Signature signature = Signature.getInstance(Constant.SIGN_ALGORITHM, Constant.BC_ALGORITHM);
            signature.initSign(privateKey);
            signature.update(data.getBytes());
            return signature.sign();
        } catch (Exception e) {
            logger.error("使用SM3签名失败", e);
            throw new Exception("使用SM3签名失败", e);
        }
    }

    /**
     * 使用SM3验证签名
     * @param input
     * @param signature
     * @param publicKey
     * @return 验证成功返回true
     */
    private static boolean verifyWithSm3(String input, byte[] signature,PublicKey publicKey) throws Exception{
        try {
            Signature signature2 = Signature.getInstance(Constant.SIGN_ALGORITHM, Constant.BC_ALGORITHM);
            signature2.initVerify(publicKey);
            signature2.update(input.getBytes());
            return signature2.verify(signature);
        }catch (Exception e) {
            logger.error("使用SM3验证签名失败", e);
            throw new Exception("使用SM3验证签名失败", e);
        }
    }

    /**
     * SM4加密
     * @param plainText
     * @param key
     * @param iv
     * @return 密文
     */
    private static String encryptWithSm4(String plainText, byte[] key, byte[] iv) throws Exception {
        try {
            SM4Engine engine = new SM4Engine();
            PaddedBufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new CBCBlockCipher(engine));
            cipher.init(true, new ParametersWithIV(new KeyParameter(key), iv));

            byte[] input = plainText.getBytes(Constant.UTF8_CHARSET);
            byte[] output = new byte[cipher.getOutputSize(input.length)];
            int len = cipher.processBytes(input, 0, input.length, output, 0);
            cipher.doFinal(output, len);

            return Hex.toHexString(output);
        } catch (Exception e) {
            logger.error("SM4加密失败", e);
            throw new Exception("SM4加密失败", e);
        }

    }

    /**
     * SM4解密
     * @param cipherText
     * @param key
     * @param iv
     * @return 明文
     */
    private static String decryptWithSm4(String cipherText, byte[] key, byte[] iv) throws Exception {
        try {
            SM4Engine engine = new SM4Engine();
            PaddedBufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new CBCBlockCipher(engine));
            cipher.init(false, new ParametersWithIV(new KeyParameter(key), iv));

            byte[] input = Hex.decode(cipherText);
            byte[] output = new byte[cipher.getOutputSize(input.length)];
            int len = cipher.processBytes(input, 0, input.length, output, 0);
            cipher.doFinal(output, len);

            return new String(output, Constant.UTF8_CHARSET).trim();
        }catch (Exception e) {
            logger.error("SM4解密失败", e);
            throw new Exception("SM4解密失败", e);
        }
    }

    /**
     * 生成SM4密钥
     * @return SM4密钥
     * @throws Exception
     */
    private static byte[] generateKey() throws Exception {
        try {
            KeyGenerator keyGenerator = KeyGenerator.getInstance(Constant.SM4_ALGORITHM, Constant.BC_ALGORITHM);
            // SM4密钥长度固定为128位
            keyGenerator.init(128, new SecureRandom());
            SecretKey secretKey = keyGenerator.generateKey();
            return secretKey.getEncoded();
        }catch (Exception e){
            logger.error("生成SM4密钥失败", e);
            throw new Exception("生成SM4密钥失败", e);
        }

    }

    /**
     * 生成IV
     * @return IV
     */
    private static byte[] generateIV() throws Exception {
        try {
            // SM4的块大小是128位(16字节)
            byte[] iv = new byte[16];
            new SecureRandom().nextBytes(iv);
            return iv;
        }catch (Exception e){
            logger.error("生成IV失败", e);
            throw new Exception("生成IV失败", e);
        }
    }

    public static PublicKey generatePublicKeyByKeyValue(String algorithm, KeySpec keySpec){
        try {
            return KeyFactory
                    .getInstance(algorithm, Constant.BOUNCY_CASTLE_PROVIDER)
                    .generatePublic(keySpec);
        } catch (InvalidKeySpecException e) {
            throw new BadRequestException(Constant.INVALID_KEY_SPEC_EXCEPTION_TIPS,e);
        } catch (NoSuchAlgorithmException e) {
            throw new BadRequestException(Constant.NO_SUCH_ALGORITHM_EXCEPTION_TIPS,e);
        }
    }

    public static PrivateKey generatePrivateKeyByKeyValue(String algorithm, KeySpec keySpec){
        try {
            return KeyFactory
                    .getInstance(algorithm, Constant.BOUNCY_CASTLE_PROVIDER)
                    .generatePrivate(keySpec);
        } catch (InvalidKeySpecException e) {
            throw new BadRequestException(Constant.INVALID_KEY_SPEC_EXCEPTION_TIPS,e);
        } catch (NoSuchAlgorithmException e) {
            throw new BadRequestException(Constant.NO_SUCH_ALGORITHM_EXCEPTION_TIPS,e);
        }
    }

    /**
     * 功能:获取 Cipher 实例
     *
     * @param transformations 转换信息
     * @return {@link Cipher}
     */
    private static Cipher getCipherInstance(String transformations) {
        try {

            return Cipher.getInstance(transformations, Constant.BOUNCY_CASTLE_PROVIDER);

        } catch (NoSuchAlgorithmException e) {
            throw new BadRequestException(Constant.NO_SUCH_ALGORITHM_EXCEPTION_TIPS, e);
        } catch (NoSuchPaddingException e) {
            throw new BadRequestException(Constant.NO_SUCH_PADDING_EXCEPTION_TIPS, e);
        }
    }

    /**
     * 功能:非对称加密
     * @param publicKeyValue 公钥值
     * @param keyAlgorithm 密钥算法
     * @param message 待加密数据
     * @return 密文
     */
    public static byte[] asymmetricKeyEncrypt(byte[] publicKeyValue, String keyAlgorithm, byte[] message) {
        PublicKey publicKey;
        String algorithm;
        if(Constant.EC_ALGORITHM.equalsIgnoreCase(keyAlgorithm)){
            algorithm = Constant.SM2_ALGORITHM;
        }else {
            algorithm = keyAlgorithm;
        }
        if ((Constant.SM2_ALGORITHM.equalsIgnoreCase(algorithm))) {
            final ECParameterSpec spec = ECNamedCurveTable.getParameterSpec(Constant.SM2_CURVES_NAME);
            final ECPublicKeySpec ecPublicKeySpec = new ECPublicKeySpec(spec.getCurve().decodePoint(publicKeyValue), spec);
            publicKey = generatePublicKeyByKeyValue(Constant.EC_ALGORITHM,ecPublicKeySpec);
        } else if (Constant.RSA_ALGORITHM.equalsIgnoreCase(algorithm)) {
            X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(publicKeyValue);
            publicKey = generatePublicKeyByKeyValue(Constant.RSA_ALGORITHM,x509EncodedKeySpec);
        }else {
            throw new BadRequestException(String.format("not support algorithm  %s",keyAlgorithm));
        }
        Cipher cipher = getCipherInstance(algorithm);
        return encryptForAsymmetric(cipher, publicKey, message);
    }

    /**
     * 功能:非对称解密
     * @param privateKeyValue 公钥值
     * @param keyAlgorithm 密钥算法
     * @param cipherText 密文
     * @return 明文
     */
    public static byte[] asymmetricKeyDecrypt(byte[] privateKeyValue, String keyAlgorithm, byte[] cipherText) {
        PrivateKey privateKey;
        String algorithm;
        if(Constant.EC_ALGORITHM.equalsIgnoreCase(keyAlgorithm)){
            algorithm = Constant.SM2_ALGORITHM;
        }else {
            algorithm = keyAlgorithm;
        }
        if ((Constant.SM2_ALGORITHM.equalsIgnoreCase(algorithm))) {
            final ECParameterSpec spec = ECNamedCurveTable.getParameterSpec(Constant.SM2_CURVES_NAME);
            final ECPrivateKeySpec ecPrivateKeySpec = new ECPrivateKeySpec(new BigInteger(1, privateKeyValue), spec);
            privateKey = generatePrivateKeyByKeyValue(Constant.EC_ALGORITHM,ecPrivateKeySpec);
        } else if (Constant.RSA_ALGORITHM.equalsIgnoreCase(algorithm)) {
            PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(privateKeyValue);
            privateKey = generatePrivateKeyByKeyValue(Constant.RSA_ALGORITHM,pkcs8EncodedKeySpec);
        }else {
            throw new BadRequestException(String.format("not support algorithm  %s",keyAlgorithm));
        }
        Cipher cipher = getCipherInstance(algorithm);
        return decryptForAsymmetric(cipher, privateKey, cipherText);

    }

    private static byte[] encryptForAsymmetric(Cipher cipher, Key key, byte[] message) {
        try {
            cipher.init(Cipher.ENCRYPT_MODE, key);
            return cipher.doFinal(message);
        } catch (InvalidKeyException e) {
            throw new BadRequestException(Constant.INVALID_KEY_EXCEPTION_TIPS, e);
        } catch (BadPaddingException e) {
            throw new BadRequestException(Constant.BAD_PADDING_EXCEPTION_TIPS, e);
        } catch (IllegalBlockSizeException e) {
            throw new BadRequestException(Constant.ILLEGAL_BLOCK_SIZE_EXCEPTION_TIPS, e);
        }
    }

    private static byte[] decryptForAsymmetric(Cipher cipher, Key key, byte[] cipherText) {
        try {
            cipher.init(Cipher.DECRYPT_MODE, key);
            return cipher.doFinal(cipherText);
        } catch (InvalidKeyException e) {
            throw new BadRequestException(Constant.INVALID_KEY_EXCEPTION_TIPS, e);
        } catch (BadPaddingException e) {
            throw new BadRequestException(Constant.BAD_PADDING_EXCEPTION_TIPS, e);
        } catch (IllegalBlockSizeException e) {
            throw new BadRequestException(Constant.ILLEGAL_BLOCK_SIZE_EXCEPTION_TIPS, e);
        }
    }

    public static void main(String[] args) throws Exception {

        // 敏感数据
        String originalText = "Hello, SM2 and SM3!";
        System.out.println("明文:"+ originalText);

        // 发送方
        System.out.println("=================发送方start====================");
        // 1. 生成数字签名和签名公钥
        // 生成SM2密钥对用于签名
        KeyPair keyPair = generateSm2KeyPair();
        PrivateKey signPrivateKey = keyPair.getPrivate();
        PublicKey signPublicKey = keyPair.getPublic();
        System.out.println("签名SM2私钥(base64):"+Base64.getEncoder().encodeToString(signPrivateKey.getEncoded()));
        System.out.println("签名SM2公钥(base64):"+Base64.getEncoder().encodeToString(signPublicKey.getEncoded()));
        // 生成数字签名
        byte[] signature = signWithSm3(originalText, signPrivateKey);
        // 2. 数字信封(生成数据密文+对称密钥密文)
        // SM4对称加密
        byte[] sm4Key = generateKey();
        byte[] iv = generateIV();
        String encryptedText = encryptWithSm4(originalText, sm4Key, iv);
        // SM2加密SM4密钥(base64)
//        String privateKey = "ANmPM+GwxdM7Yo/YhftxmfEqqjiHl9qKuMU55+lEfcEW";
//        String publicKey = "BBQgpX+nrdNbyVmhQYlcED631eIYGKFj05+AoPMthp3pnum04Na8LG8sMul4Ro2W+DZaO/7XZvRievGY8DG2OoU=";
        String privateKey = "bOoCZx5bcmoHDbJ+KKFqWBVLSBO7cCcV87cnJ1zCvs8=";
        String publicKey = "BJIv8Ctzj0Xb11a1OoeYNTqlDpeMFU0WLL2+vM6JuWqGGO1AseL2wW3lryMKX8sBqUmtXII/7+0QLc3Hb1sWf/8=";
        byte[] encryptionSm4Key = asymmetricKeyEncrypt(Base64.getDecoder().decode(publicKey), "SM2", sm4Key);

        // 得到以下四个数据
        System.out.println("数字签名:"+ Arrays.toString(signature));
        System.out.println("签名公钥:"+ signPublicKey);
        System.out.println("数据密文:"+ encryptedText);
        System.out.println("SM4对称密钥密文:"+ Arrays.toString(encryptionSm4Key));
        System.out.println("=================发送方end====================");

        // 接收方
        System.out.println("=================接收方start====================");
        // 1. SM2解密获取SM4密钥
        byte[] deserializeSm4Key = asymmetricKeyDecrypt(Base64.getDecoder().decode(privateKey), "SM2", encryptionSm4Key);

        // 2. SM4解密
        String decryptedText = decryptWithSm4(encryptedText, deserializeSm4Key, iv);

        // 3. 验证签名
        boolean isCorrect = verifyWithSm3(decryptedText, signature, signPublicKey);

        System.out.println("验证签名:"+isCorrect);
        System.out.println("解密结果:"+decryptedText);
        System.out.println("=================接收方end====================");

    }


}

Constant.java

package utils.crypto;

import org.bouncycastle.jce.provider.BouncyCastleProvider;

import java.util.Arrays;
import java.util.List;

public class Constant {

    private Constant(){
        throw new IllegalStateException("Utility class");
    }

    //commons
    public static final BouncyCastleProvider BOUNCY_CASTLE_PROVIDER = new BouncyCastleProvider();

    //curves
    public static final String SM2_CURVES_NAME = "sm2p256v1";

    //algorithm 算法
    public static final String RSA_ALGORITHM = "RSA";

    public static final String SM2_ALGORITHM = "SM2";

    public static final String EC_ALGORITHM = "EC";

    public static final String SM4_ALGORITHM = "SM4";

    public static final String BC_ALGORITHM = "BC";

    public static final String SIGN_ALGORITHM = "SM3withSM2";

    public static final String UTF8_CHARSET = "UTF-8";

    //需要确认一下具体使用哪种 hash 算法?
    public static final List<String> SUPPORT_HASH_ALGORITHM = Arrays.asList("SM3","SHA256","SHA512");
    //需要确认一下具体使用哪种 签名 算法?
    public static final List<String> SUPPORT_SIGNATURE_ALGORITHM = Arrays.asList("SM3-SM2","NONE-RSA","SHA256-RSA","SHA512-RSA");

    public static final List<String> PADDING = Arrays.asList("NoPadding","ISO10126Padding","PKCS5Padding");

    public static final List<String> MODE= Arrays.asList("ECB","CBC");

    // exception tips
    public static final String NOT_BE_NULL_OR_EMPTY_TIPS =" %s can not be null or empty !";

    public static final String NOT_BE_NULL_TIPS =" %s can not be null !";

    public static final String NOT_BE_EMPTY_TIPS =" %s can not be empty !";

    public static final String ONLY_SUPPORT_FOR_TIPS =" %s only support for %s ";

    public static final String KEY_SIZE_NOT_BE_ZERO_AND_MULTIPLE_OF_8_TIPS ="keySize can not be zero and must be a multiple of 8";

    public static final String NO_SUCH_ALGORITHM_EXCEPTION_TIPS ="No Such Algorithm Exception ";

    public static final String NO_SUCH_PADDING_EXCEPTION_TIPS ="No Such Padding Exception ";

    public static final String BAD_PADDING_EXCEPTION_TIPS ="Bad Padding Exception ";

    public static final String SIGNATURE_EXCEPTION_TIPS ="Signature Exception ";

    public static final String INVALID_KEY_EXCEPTION_TIPS ="Invalid CustomKey Exception ";

    public static final String INVALID_KEY_SPEC_EXCEPTION_TIPS ="Invalid CustomKey Spec Exception ";

    public static final String INVALID_ALGORITHM_PARAMETER_EXCEPTION_TIPS ="Invalid Algorithm Parameter Exception ";

    public static final String ILLEGAL_BLOCK_SIZE_EXCEPTION_TIPS ="Illegal Block Size Exception ";


}

BadRequestException.java

package utils.crypto.exception;


public class BadRequestException extends BasicException{



    private  static final String CODE = "400";

    public BadRequestException(String message){
        super(CODE,message);
    }

    public BadRequestException(String message,Throwable cause){
        super(cause,CODE,message);
    }

    public BadRequestException(String code, String message) {
        super(code, message);
    }

    public BadRequestException(Throwable cause, String code, String message) {
        super(cause, code, message);
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1954213.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux基础复习(四)

前言 上一篇文章配置了yum仓库和Tab自动补全&#xff0c;这一篇就开始详细介绍各命令 一、基础命令 pwd pwd命令全称Print Working Directory 打印工作目录&#xff0c;就是查看当前所在位置的目录路径 cd cd全称 Change Directory 切换目录&#xff0c;就是切换工作目录…

docker compose 安装rocketmq 4.x

官网参照 前置环境搭建 目录创建 创建两个目录 logs和store,用于存放日志和数据信息&#xff0c;位置随意&#xff0c;这里我放在/home/rockermq目录下 broker.conf 创建一个空文件broker.conf 在空文件中添加 # broker 对外提供服务的ip&#xff0c;如果是公网&#xff…

前端播放rtsp视频流(最后使用WebRtc)

前端播放rtsp视频流&#xff08;最后使用WebRtc&#xff09; 前言&#xff1a; ​ 项目需要将实验室里的摄像头画面引入到前端页面中&#xff0c;故对目前常见的几种方法进行了尝试&#xff0c;虽然过程坎坷但结局是好的。 一些尝试&#xff1a; RTSPtoWebRtc工具 由于RTSPt…

npm publish出错,‘proxy‘ config is set properly. See: ‘npm help config‘

问题&#xff1a;使用 npm publish发布项目依赖失败&#xff0c;报错 proxy config is set properly. See: npm help config 1、先查找一下自己的代理 npm config get proxy npm config get https-proxy npm config get registry2、然后将代理和缓存置空 方式一&#xff1a; …

相对位置偏置代码解析

1. 初始化相对位置偏置嵌入 self.rel_pos_bias nn.Embedding((2 * window_size - 1) ** 2, self.heads) 假设window_size7、slef.heads4,则 2 * window_size - 1 13&#xff1b;嵌入层的大小为13*13169&#xff0c;创建一个大小为169*4的嵌入矩阵。 2. 创建位置索引 pos …

OpenGL入门第六步:材质

目录 结果显示 材质介绍 函数解析 具体代码 结果显示 材质介绍 当描述一个表面时&#xff0c;我们可以分别为三个光照分量定义一个材质颜色(Material Color)&#xff1a;环境光照(Ambient Lighting)、漫反射光照(Diffuse Lighting)和镜面光照(Specular Lighting)。通过为每…

springboot整合 knife4j 接口文档

第一步&#xff1a;引入依赖 <dependency><groupId>com.github.xiaoymin</groupId><artifactId>knife4j-openapi2-spring-boot-starter</artifactId><version>4.4.0</version></dependency> 第二步&#xff1a;写入配置 方…

国内NAT服务器docker方式搭建rustdesk服务

前言 如果遇到10054,就不要设置id服务器!!! 由于遇到大带宽,但是又贵,所以就NAT的啦,但是只有ipv4共享和一个ipv6,带宽50MB(活动免费会升130MB~) https://bigchick.xyz/aff.php?aff322 月付-5 循环 &#xff1a;CM-CQ-Monthly-5 年付-60循环&#xff1a;CM-CQ-Annually-60官方…

2024后端开发面试题总结

一、前言 上一篇离职贴发布之后仿佛登上了热门&#xff0c;就连曾经阿里的师兄都看到了我的分享&#xff0c;这波流量真是受宠若惊&#xff01; 回到正题&#xff0c;文章火之后&#xff0c;一些同学急切想要让我分享一下面试内容&#xff0c;回忆了几个晚上顺便总结一下&#…

SQL数据库:通过在视频监控平台服务器上直接使用SQL存储过程,在海量记录中查询特定时间段内-某个摄像头的所有视频片段

目录 一、背景 1、存储过程 2、视频监控系统 二、需求和数据表 1、具体要求 2、数据表 3、部分数据 三、实现 1、目标 2、创建存储过程 &#xff08;1&#xff09;存储过程代码 &#xff08;2&#xff09;创建成功 3、存储过程的解释 4、SQL命令调用方式 5、调用…

【FunClip】阿里开源AI视频剪辑神器:全面体验与教程

目录 引言1. FunClip概览1.1 什么是FunClip1.2 FunClip的市场定位1.3 FunClip的创新意义 2. FunClip的功能特性3. FunClip的实际应用案例4. FunClip的使用教程4.1 在线体验FunClip4.2 本地部署Gradio版本4.3 命令行运行 结语参考引用 引言 随着数字媒体的蓬勃发展&#xff0c;…

OpenGL笔记十五之GLM叠加实验

OpenGL笔记十五之GLM叠加实验 —— 2024-07-27 晚上 bilibili赵新政老师的教程看后笔记 code review! 文章目录 OpenGL笔记十五之GLM叠加实验1.每一帧都旋转的三角形2.每一帧在旋转前&#xff0c;都重置为一次单位矩阵&#xff0c;这要只会旋转1度3.每一帧旋转前&#xff0c…

centos stream 9安装 Kubernetes v1.30 集群

1、版本说明&#xff1a; 系统版本&#xff1a;centos stream 9 Kubernetes版本&#xff1a;最新版(v1.30) docker版本&#xff1a;27.1.1 节点主机名ip主节点k8s-master172.31.0.10节点1k8s-node1172.31.0.11节点2k8s-node2172.31.0.12 2、首先&#xff0c;使用Vagrant和Virt…

XSS漏洞:xss.haozi.me靶场1-12 | A-F

目录 0x00&#xff08;无限制&#xff09; 0x01&#xff08;闭合标签绕过&#xff09; 0x02&#xff08;双引号闭合绕过&#xff09; 0x03&#xff08;过滤括号&#xff09; 0x04&#xff08;编码绕过&#xff09; 0x05&#xff08;注释闭合绕过&#xff09; 0x06&#…

【网络爬虫技术】(1·绪论)

&#x1f308; 个人主页&#xff1a;十二月的猫-CSDN博客 &#x1f525; 系列专栏&#xff1a; &#x1f3c0;网络爬虫开发技术入门_十二月的猫的博客-CSDN博客 &#x1f4aa;&#x1f3fb; 十二月的寒冬阻挡不了春天的脚步&#xff0c;十二点的黑夜遮蔽不住黎明的曙光 目录 …

【C语言】两个数组比较详解

目录 C语言中两个数组比较详解1. 逐元素比较1.1 示例代码1.2 输出结果1.3 分析 2. 内置函数的使用2.1 示例代码2.2 输出结果2.3 分析 3. 在嵌入式系统中的应用3.1 示例代码3.2 输出结果3.3 分析 4. 拓展技巧4.1 使用指针优化比较4.2 输出结果4.3 分析 5. 表格总结6. 结论7. 结束…

yolov8 训练模型

一、准备数据 1.1 收集数据 以拳皇为例&#xff0c;将录制的游戏视频进行抽帧。 import cv2 import os# 视频文件路径 video_path 1.mp4# 输出帧的保存目录 output_dir ./output_frames os.makedirs(output_dir, exist_okTrue)# 读取视频 cap cv2.VideoCapture(video_pa…

7-25学习笔记

一、锁对象 Lock接口 1、创建锁对象 ReentrantLock类 Lock locknew ReentrantLock(true); 默认创建的是非公平锁 在创建锁对象时传入一个true参数 便会创建公平锁 先来后到 是重入锁 排他锁 加锁后不允许其它线程进入 2、加锁、解锁 &#xff08;1&#xff09;loc…

服务器搭建总结

服务器搭建好初期要记得开放端口&#xff0c;配置安全组 &#xff0c;主要的有22&#xff0c;80&#xff0c;3389&#xff0c;8888等&#xff0c;宝塔连接的端口在8888上&#xff0c;不开放无法连接 由于时使用的腾讯云服务器&#xff0c;所以在宝塔选择上使用了Windows的面板…

Autodesk Revit v2025 激解锁版下载及安装教程 (三维建模软件)

前言 Revit是欧特克公司知名的三维建模软件&#xff0c;是建筑业BIM体系中使用最广泛的软件之一&#xff0c;其核心功能是三维建筑模型参数化设计、渲染效果图、算量&#xff0c;土建建模、机电建模、用来帮助工程师在施工前精确模拟阶段。 一、下载地址 下载链接&#xff1…