常用密码技术初探
记得前几年有一部电影叫做《解除好友2:暗网》,它讲述了主角捡到一台电脑,并用它与好友进行视频通讯,但一名黑客通过网络技术篡改了通讯内容,最终导致所有参与视频通话的人都遭遇不测。
电影当然存在夸张成分,但其展现出的个人隐私数据泄露带来的严重后果,从侧面反映出了网络安全的重要性。
在当今时代,数字资产对个人和企业都至关重要,而保护数字资产所需的密码学技术,应当成为我们普通人,尤其是技术从业者的必备知识。
本文旨在介绍密码学中常用的几种工具,并给出相应的示例代码辅助理解,希望能够对大家有所帮助。
一、总览
在密码学领域,主要关注四个核心问题:
- 信息被窃听
- 信息被篡改
- 伪装成真正的发送者
- 事后否认行为
这四个问题分别对应密码学中的四个安全领域:机密性、完整性、身份认证和抗抵赖(不可否认)。为了应对这些安全挑战,密码专家们开发了五个基本工具,分别是:对称密钥、非对称密钥、单向散列函数、消息认证码、数字签名。
常见术语说明
- PKCS:Public Key Cryptography Standards,公钥密码学标准,将RSA和单向散列函数相结合来进行数字签名
- 证书:就是将公钥当作一条消息,由一个可信的第三方对其签名后所得到的公钥。
- PKC:Public-Key Certificat,公钥证书
- CA:Certification Authority,认证机构,能够认定“公钥确实属于此人”并能够生成数字签名的个人或者组织
- X.509:证书的一种标准规范
- PKI:公钥基础设施,为了能够更有效地运用公钥而制定的一系列规范和规格的总称。
- truststore(证书仓库)——存放一个或多个CA,这里的truststore就像你自己电脑的证书管理器一样,如果你打开Chrome的设置,找HTTP SSL,就可以看到里面有很多CA,truststore就是干这个活儿的。
二、密码工具
2.1 对称加密算法
对称加密算法,又称为私钥密码或对称密钥,是最基础且历史最悠久的加密技术之一,具体是指在加密和解密过程中使用相同密钥的一类密码系统,这也就是"对称"一词的由来。如下图所示
优点
简单、快速
局限
- 密钥分发复杂(如何保证密钥安全发往对端?)
- 多人通信场景下,密钥量激增
- 安全性依赖于密钥保密
适用场景
混合加密 加密中作为 会话秘钥:非对称加密用于安全地传输对称加密密钥,然后使用对称密钥加密实际数据。比如:数字信封
常见算法
AES、SM4
示例代码(AES)
package com.ahrcu.operation.encryption;
import cn.hutool.crypto.digest.DigestUtil;
import cn.hutool.crypto.symmetric.AES;
import cn.hutool.crypto.Mode;
import cn.hutool.crypto.Padding;
import cn.hutool.crypto.symmetric.SymmetricCrypto;
import com.google.common.base.Preconditions;import java.util.Arrays;/*** @author Luke.ye*/
public class AesWithCustomKey {public static void main(String[] args) {// 示例非固定长度密钥String originalKey = "lukeye-custom-key";// 使用SHA-256生成16字节密钥byte[] key = DigestUtil.sha256(originalKey.getBytes());// 截断为16字节,也可以24或32字节key = Arrays.copyOf(key, 16);// 初始化向量(16字节)byte[] iv = "1234567890123456".getBytes();// 使用自定义密钥和初始化向量创建AES对象SymmetricCrypto aes = new AES(Mode.CBC, Padding.PKCS5Padding, key, iv);// 加密和解密操作String plaintext = "AES对称加密测试";// 进行10000次加密解密String encrypted = null;String decrypted = null;long startTime = System.currentTimeMillis();for (int i = 0; i < 10000; i++) {encrypted = aes.encryptBase64(plaintext);decrypted = aes.decryptStr(encrypted);}long endTime = System.currentTimeMillis();long duration = endTime - startTime;// 验证解密结果Preconditions.checkArgument(plaintext.equals(decrypted), "加密解密失败");System.out.println("原始密钥: " + originalKey);System.out.println("原始文本: " + plaintext);System.out.println("加密结果: " + encrypted);System.out.println("解密结果: " + decrypted);System.out.println("解密后结果与原始文本对比结果:" + (plaintext.equals(decrypted) ? "一致" : "不一致"));System.out.println("加密解密10000次耗时:" + duration + "毫秒");}
}
其运行结果如下
2.2 非对称加密算法
非对称加密算法,又称为公钥密码或非对称密钥,是现代密码学中的一项重要技术。它的核心特点是加密和解密使用不同的密钥——公钥和私钥。这种设计使得非对称加密不需要共享私钥,从根本上解决了对称加密中的密钥分发问题。如下图所示:
优点
安全
局限
- 速度慢,适合加密小数据块(比如 会话秘钥)(是对称加密耗时的几百倍)
- 复杂度高
适用场景
- 密钥交换:用来安全传输对称加密的密钥(例如 SSL/TLS 协议)。
- 数字签名与身份验证:验证通信双方身份,确保数据未被篡改。
- 数据加密:适用于对高价值数据的小范围加密。
例如,在混合加密中,非对称加密用于加密对称加密的会话密钥,实际数据则由对称加密完成,这种方式兼具安全性和高效性。
常见算法
RSA-2048、SM2
示例代码(RSA-2048)
package com.ahrcu.operation.encryption;import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.RSA;
import com.google.common.base.Preconditions;import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;/*** @author Luke.ye*/
public class Rsa2048Example {public static void main(String[] args) throws NoSuchAlgorithmException {// 生成 2048 位密钥对KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA");keyPairGen.initialize(2048); // 设置密钥长度KeyPair keyPair = keyPairGen.generateKeyPair();// 使用生成的密钥对创建 RSA 实例RSA rsa = new RSA(keyPair.getPrivate(), keyPair.getPublic());// 原始待加密文本String plaintext = "RSA 2048 非对称加密测试";// 获取公钥和私钥String publicKey = rsa.getPublicKeyBase64();String privateKey = rsa.getPrivateKeyBase64();// 加密和解密操作String encrypted = null;String decrypted = null;// 性能测试:进行 10,000 次加密解密long startTime = System.currentTimeMillis();for (int i = 0; i < 10000; i++) {encrypted = rsa.encryptBase64(plaintext, KeyType.PublicKey);decrypted = rsa.decryptStr(encrypted, KeyType.PrivateKey);}long endTime = System.currentTimeMillis();long duration = endTime - startTime;// 验证加密解密结果Preconditions.checkArgument(plaintext.equals(decrypted), "加密解密失败");// 打印结果System.out.println("原始文本: " + plaintext);System.out.println("加密结果: " + encrypted);System.out.println("解密结果: " + decrypted);System.out.println("加密解密10000次耗时:" + duration + "毫秒");System.out.println("公钥: " + publicKey);System.out.println("私钥: " + privateKey);}
}
运行结果如下
2.3 单向散列函数
单向散列函数,又名,散列值、哈希值、密码校验、指纹、消息摘要,是密码学中的一种基本工具。它将任意长度的数据映射为固定长度的值,且映射结果不可逆。这种特性使其在数据完整性校验、数字签名等领域有着广泛的应用。典型的应用场景如下图所示:
适用场景
- 数据完整性校验:用散列值验证数据传输或存储是否被篡改。
- 密码存储:存储用户密码的散列值,避免明文泄露。
- 数字签名:结合非对称加密,生成数据摘要供签名验证。
常见算法
MD5**(**已被证实不安全,但仍在非安全领域广泛应用)、SHA-256/SHA-512、SM3
示例代码(SHA256)
package com.ahrcu.operation.encryption;import cn.hutool.crypto.digest.DigestUtil;/*** @author Luke.ye*/
public class SHA256Example {public static void main(String[] args) {// 原始输入数据String input1 = "这是一个单向散列函数的测试-lukeye1";String input2 = "这是一个单向散列函数的测试-lukeye2";// 使用 SHA-256 算法计算散列值String sha256Hash1 = DigestUtil.sha256Hex(input1);String sha256Hash2 = DigestUtil.sha256Hex(input2);// 打印散列值System.out.println("原始数据: " + input1);System.out.println("SHA-256 散列值长度(16进制下): " + sha256Hash1.length() + ", 其值为" + sha256Hash1);System.out.println("不同原始数据的SHA-256散列值是否相同: " + (sha256Hash1.equals(sha256Hash2) ? "一致" : "不一致"));}
}
运行结果如下
2.4 消息认证码
消息认证码(Message Authentication Code,简称 MAC),是一种基于密钥的消息摘要技术。它通过将消息和密钥结合计算生成一个固定长度的认证码,用于验证消息的完整性和真实性**(它与单向散列函数的区别在于引入了对称密钥)**。如下图所示:
优点
- 防篡改性:只有持有密钥的一方才能生成正确的 MAC,防止消息被篡改。
- 高效性:计算简单,速度快,适合高性能需求的场景。
- 验证身份:通过密钥的独占性,确保消息来源可信。
局限
由于MAC使用的密钥,在发送者和接收者之间是共享的,因此无法解决下面的两个问题
- 对第三方证明:接收端 收到 发送端的消息,想要向第三方验证者证明这条消息的确是发送端发送的。第三方验证者收到MAC后,无法判断是发送端计算出来的,还是接收端自己伪造的。
- 防止否认(抗抵赖):接收端拿到MAC后,发送端可以说这个MAC不是自己计算的。
适用场景
- 消息完整性验证:确保传输的数据未被篡改。
- 认证与授权:验证消息是否来自可信方(无需考虑第三方证明 和 抗抵赖的情形)
常见算法
HMAC、SM3-MAC
示例代码(HMAC256)
package com.ahrcu.operation.encryption;import cn.hutool.crypto.digest.HMac;
import cn.hutool.crypto.digest.HmacAlgorithm;/*** @author Luke.ye*/
public class HMACExample {public static void main(String[] args) {// 密钥String secretKey = "lukeye-shared-key";// 发送端System.out.println("=== 发送端 ===");String requestPayload = "这是发送的请求报文";String macValue = calculateMac(requestPayload, secretKey); // 计算MAC值// 封装消息为对象Message messageToSend = new Message(requestPayload, macValue);System.out.println("发送的消息对象: " + messageToSend);// 模拟消息传输(发送端 -> 接收端)String receivedPayload = messageToSend.getPayload();String receivedMac = messageToSend.getMac();// 接收端System.out.println("\\n=== 接收端 ===");System.out.println("接收到的请求报文: " + receivedPayload);System.out.println("接收到的MAC值: " + receivedMac);// 验证MAC值String recalculatedMac = calculateMac(receivedPayload, secretKey);boolean isValid = recalculatedMac.equals(receivedMac);System.out.println("重新计算的MAC值: " + recalculatedMac);System.out.println("消息认证结果: " + (isValid ? "通过" : "失败"));}// 使用HMAC计算消息认证码private static String calculateMac(String message, String secretKey) {HMac hmac = new HMac(HmacAlgorithm.HmacSHA256, secretKey.getBytes());return hmac.digestHex(message); // 返回消息的MAC值}// 消息类,包含请求报文和MAC值static class Message {private final String payload; // 请求报文private final String mac; // 消息认证码public Message(String payload, String mac) {this.payload = payload;this.mac = mac;}public String getPayload() {return payload;}public String getMac() {return mac;}@Overridepublic String toString() {return "Message{" +"payload='" + payload + '\\'' +", mac='" + mac + '\\'' +'}';}}
}
运行结果如下
2.5 数字签名
数字签名是一种基于非对称加密的技术,用于验证消息的完整性、真实性,并确保不可抵赖性。它是现代信息安全的核心技术之一,通过使用私钥进行签名、使用公钥进行验证,数字签名可以在互联网上实现可信的身份验证和信息保护。如下图所示:
关于数字签名,有两点需要注意
💡
- 数字签名对签名密钥和验证密钥进行了区分,使用验证密钥是无法生成签名的,这一点非常重要。此外,签名密钥只能由签名的人持有,而验证密钥则是任何需要验证签名的人都可以持有。
- 由于数字签名使用的是非对称加密技术,因此性能较差,如果对全报文做签名,性能太差,一般较少使用。更多的是选择 先对全报文 做 单向散列函数,得到消息摘要,再对摘要做签名。
💡 数字签名 Vs 非对称加密
私钥 | 公钥 | |
---|---|---|
数字签名 | 发送者 生成签名 时使用 | 接收者 验证签名 时使用 |
非对称加密 | 接收者 解密 时使用 | 发送者 加密 时使用 |
谁持有密钥? | 个人持有 | 只要需要,任何人都可以持有 |
优点
同时实现了 完整性、身份认证和抗抵赖。
局限
- 无法判断公钥合法性:无法判断接收到的公钥是否合法,它可能并不是来自于真正的发送者。此时,需要PKI(公钥基础设施)参与。详见
- 性能较低:非对称加密计算复杂,效率较低。(可以只加密 摘要 来提升性能)
适用场景
同时需要保证 完整性、身份认证和抗抵赖。
常见算法
其底层使用的是非对称加密技术,因此算法与非对称加密一致,比如:RSA、SM2等。
如果考虑到优化性能,还可以使用单向散列函数生成报文摘要,再进行加密。
示例代码(SHA256 + RSA2048)
package com.ahrcu.operation.encryption;import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.RSA;
import cn.hutool.crypto.digest.DigestUtil;import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;/*** @author Luke.ye*/
public class DigitalSignatureExample {public static void main(String[] args) throws NoSuchAlgorithmException {// 1. 生成 2048 位密钥对KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA");keyPairGen.initialize(2048); // 设置密钥长度KeyPair keyPair = keyPairGen.generateKeyPair();// 使用生成的密钥对创建 RSA 实例RSA rsa = new RSA(keyPair.getPrivate(), keyPair.getPublic());String publicKey = rsa.getPublicKeyBase64();String privateKey = rsa.getPrivateKeyBase64();System.out.println("公钥: " + publicKey);System.out.println("私钥: " + privateKey);// === 发送端 ===System.out.println("\\n=== 发送端 ===");String messagePayload = "这是需要签名的请求报文,Hello Lukeye";// 计算消息摘要(使用 SHA-256)String hash = DigestUtil.sha256Hex(messagePayload);System.out.println("消息摘要 (SHA-256): " + hash);// 使用私钥对摘要进行签名RSA senderRSA = new RSA(privateKey, null);String signature = senderRSA.encryptBase64(hash, KeyType.PrivateKey);System.out.println("生成的数字签名: " + signature);// 封装为 Message 对象Message sentMessage = new Message(messagePayload, signature);System.out.println("发送的消息内容: " + sentMessage.getPayload());System.out.println("发送的数字签名: " + sentMessage.getSignature());// === 接收端 ===System.out.println("\\n=== 接收端 ===");// 模拟接收到消息Message receivedMessage = sentMessage;// 提取消息内容和签名String receivedPayload = receivedMessage.getPayload();String receivedSignature = receivedMessage.getSignature();// 计算接收到的消息的摘要String receivedHash = DigestUtil.sha256Hex(receivedPayload);System.out.println("接收到的消息摘要 (SHA-256): " + receivedHash);// 使用公钥验证签名RSA receiverRSA = new RSA(null, publicKey);String decryptedHash = receiverRSA.decryptStr(receivedSignature, KeyType.PublicKey);System.out.println("解密得到的摘要: " + decryptedHash);// 验证摘要是否一致boolean isVerified = receivedHash.equals(decryptedHash);System.out.println("签名验证结果: " + (isVerified ? "通过" : "失败"));}// 自定义消息类static class Message {private final String payload; // 请求报文private final String signature; // 数字签名public Message(String payload, String signature) {this.payload = payload;this.signature = signature;}public String getPayload() {return payload;}public String getSignature() {return signature;}}
}
输出结果如下
三、常用技术
3.1 混合加密应用——数字信封
数字信封技术是混合加密技术的核心应用,它结合了对称加密和非对称加密的优势。在数据传输中:
- 对称加密:用于加密大规模数据,速度快,效率高。
- 非对称加密:用于加密对称密钥,安全性高。
其大致流程如下:
实践-生成证书
生成秘钥和证书
# 生成服务端私钥
openssl genrsa -out server_private_key.pem 2048# 使用私钥生成证书签名请求 (CSR)
openssl req -new -key server_private_key.pem -out server.csr# 生成 X.509 证书
openssl x509 -req -in server.csr -signkey server_private_key.pem -out server_cert.pem -days 365
执行情况如下
生成服务端私钥
使用私钥生成证书签名请求(CSR)
生成X.509证书
实践-运行主类
package com.ahrcu.operation.encryption.digitalenvolop;/*** @author Luke.ye*/
public class DigitalEnvelopeMessage {private final byte[] encryptedKey; // 加密的对称密钥private final byte[] encryptedData; // 加密的报文数据private final byte[] iv; // 加密的初始化向量public DigitalEnvelopeMessage(byte[] encryptedKey, byte[] encryptedData, byte[] iv) {this.encryptedKey = encryptedKey;this.encryptedData = encryptedData;this.iv = iv;}public byte[] getEncryptedKey() {return encryptedKey;}public byte[] getEncryptedData() {return encryptedData;}public byte[] getIv() {return iv;}
}
package com.ahrcu.operation.encryption.digitalenvolop;/*** @author Luke.ye*/
public class DigitalEnvelopeMessage {private final byte[] encryptedKey; // 加密的对称密钥private final byte[] encryptedData; // 加密的报文数据private final byte[] iv; // 加密的初始化向量public DigitalEnvelopeMessage(byte[] encryptedKey, byte[] encryptedData, byte[] iv) {this.encryptedKey = encryptedKey;this.encryptedData = encryptedData;this.iv = iv;}public byte[] getEncryptedKey() {return encryptedKey;}public byte[] getEncryptedData() {return encryptedData;}public byte[] getIv() {return iv;}
}
实践-客户端代码
package com.ahrcu.operation.encryption.digitalenvolop;import org.bouncycastle.jce.provider.BouncyCastleProvider;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import java.io.FileInputStream;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.Security;
import java.security.cert.CertificateFactory;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;/*** @author Luke.ye*/
public class DigitalEnvelopeClient {private final PublicKey publicKey;static {Security.addProvider(new BouncyCastleProvider());}public DigitalEnvelopeClient(String certificatePath) throws Exception {this.publicKey = loadPublicKeyFromCertificate(certificatePath);}private PublicKey loadPublicKeyFromCertificate(String certificatePath) throws Exception {System.out.println("客户端:从证书中读取服务端公钥...");CertificateFactory certFactory = CertificateFactory.getInstance("X.509", "BC");FileInputStream fis = new FileInputStream(certificatePath);PublicKey key = certFactory.generateCertificate(fis).getPublicKey();fis.close();return key;}public DigitalEnvelopeMessage createEnvelope(String message) throws Exception {System.out.println("客户端:生成对称密钥...");KeyGenerator keyGen = KeyGenerator.getInstance("AES");keyGen.init(256); // AES 256 位密钥SecretKey symmetricKey = keyGen.generateKey();System.out.println("客户端:加密原始报文...");Cipher aesCipher = Cipher.getInstance("AES/CBC/PKCS5Padding", "BC");byte[] iv = new byte[16];IvParameterSpec ivSpec = new IvParameterSpec(iv);aesCipher.init(Cipher.ENCRYPT_MODE, symmetricKey, ivSpec);byte[] encryptedData = aesCipher.doFinal(message.getBytes());System.out.println("客户端:加密对称密钥...");Cipher rsaCipher = Cipher.getInstance("RSA/ECB/PKCS1Padding", "BC");rsaCipher.init(Cipher.ENCRYPT_MODE, publicKey);byte[] encryptedKey = rsaCipher.doFinal(symmetricKey.getEncoded());return new DigitalEnvelopeMessage(encryptedKey, encryptedData, iv);}
}
实践-服务端代码
package com.ahrcu.operation.encryption.digitalenvolop;import org.bouncycastle.jce.provider.BouncyCastleProvider;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.Security;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;
import java.io.BufferedReader;
import java.io.FileReader;
import java.util.stream.Collectors;/*** @author Luke.ye*/
public class DigitalEnvelopeServer {private final PrivateKey privateKey;static {Security.addProvider(new BouncyCastleProvider());}public DigitalEnvelopeServer(String privateKeyPath) throws Exception {this.privateKey = loadPrivateKey(privateKeyPath);}private PrivateKey loadPrivateKey(String privateKeyPath) throws Exception {System.out.println("服务端: 从文件中读取(私钥管理模块)服务端私钥...");try (BufferedReader reader = new BufferedReader(new FileReader(privateKeyPath))) {String privateKeyPEM = reader.lines().filter(line -> !line.startsWith("-----")).collect(Collectors.joining());byte[] keyBytes = Base64.getDecoder().decode(privateKeyPEM);PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);KeyFactory keyFactory = KeyFactory.getInstance("RSA", "BC");return keyFactory.generatePrivate(keySpec);}}public String decryptEnvelope(DigitalEnvelopeMessage envelope) throws Exception {System.out.println("服务端:解密对称密钥...");Cipher rsaCipher = Cipher.getInstance("RSA/ECB/PKCS1Padding", "BC");rsaCipher.init(Cipher.DECRYPT_MODE, privateKey);byte[] symmetricKeyBytes = rsaCipher.doFinal(envelope.getEncryptedKey());SecretKey symmetricKey = new SecretKeySpec(symmetricKeyBytes, "AES");System.out.println("服务端:解密消息...");Cipher aesCipher = Cipher.getInstance("AES/CBC/PKCS5Padding", "BC");aesCipher.init(Cipher.DECRYPT_MODE, symmetricKey, new IvParameterSpec(envelope.getIv()));byte[] decryptedData = aesCipher.doFinal(envelope.getEncryptedData());return new String(decryptedData);}
}
实践-运行结果
3.2 通道加密应用——HTTPS
在前面的内容中,已经介绍了报文层面的加密、签名等技术,通过这些技术,我们可以保证单条消息的安全性,确保只有预期的接收者能够阅读和验证消息的完整性。但这种方式依然存在不足,主要有两点
- 它不能保护消息的元数据(如发送者、接收者、时间戳等)。
- 传输信道安全性不足:如果攻击者拦截了通信信道中的数据,即使有加密报文,他们仍可通过中间人攻击等方式替换或重放数据。
- 身份验证与信任问题:数字证书解决了公钥分发的问题,但需要一种通用协议确保双方通信前建立信任关系。
正式由于这些局限,通道加密技术应运而生,HTTPS就是其中的代表。
通道加密,也称为传输层加密,是指对在网络上传输的所有数据进行加密,无论内容是什么。它创建一个安全的通道,确保通过该通道传输的所有数据都是加密的。它提供的安全保障包括
💡
- 传输通道的端到端加密
- 防止中间人攻击
- 保护传输过程中的元数据 </aside>
💡 HTTPS通信机制
以HTTPS为例(HTTPS也是混合密码技术的典型应用),其安全通信机制如下所示[2]
💡
- 步骤 1: 客户端通过发送 Client Hello 报文开始 SSL 通信。报文中包含客户端支持的 SSL的指定版本、加密组件(CipherSuite)列表(所使用的加密算法及密钥长度等)。
- 步骤 2:服务器可进行 SSL 通信时,会以 Server Hello 报文作为应答。和客户端一样,在报文中包含 SSL 版本以及加密组件。服务器的加密组件内容是从接收到的客户端加密组件内筛选出来的。
- 步骤 3:之后服务器发送 Certificate 报文。报文中包含公开密钥证书。
- 步骤 4: 最后服务器发送 Server Hello Done 报文通知客户端,最初 阶段的 SSL 握手协商部分结束。
- 步骤 5: SSL 第一次握手结束之后,客户端以 Client Key Exchange 报文作为回应。报文中包含通信加密中使用的一种被称为 Pre-master secret 的随机密码串。该报文已用步骤 3 中的公 开密钥进行加密。
- 步骤 6: 接着客户端继续发送 Change Cipher Spec 报文。该报文会 提示服务器,在此报文之后的通信会采用 Pre - master secret 密钥加密。
- 步骤 7: 客户端发送 Finished 报文。该报文包含连接至今全部报文 的整体校验值。这次握手协商是否能够成功,要以服务器 是否能够正确解密该报文作为判定标准。
- 步骤 8:服务器同样发送 Change Cipher Spec 报文。
- 步骤 9:服务器同样发送 Finished 报文。
- 步骤 10: 服务器和客户端的 Finished 报文交换完毕之后,SSL 连接 就算建立完成。当然,通信会受到 SSL 的保护。从此处开 始进行应用层协议的通信,即发送 HTTP 请求。
- 步骤 11:应用层协议通信,即发送 HTTP 响应。
- 步骤 12:最后由客户端断开连接。 断开连接时,发送 close_notify报文。 </aside>
实践-生成p12公钥库
第一步:生成 CA 私钥和自签名根证书
openssl req -x509 -newkey rsa:2048 -nodes -keyout ca_key.pem -out ca_cert.pem -days 365 -subj "/C=CN/ST=Beijing/L=Beijing/O=ExampleOrg/OU=CA/CN=ExampleRootCA"# 校验命令
openssl x509 -in ca_cert.pem -text -noout
-req
: 生成证书请求。-x509
: 生成自签名证书。-newkey rsa:2048
: 使用 RSA 算法生成 2048 位密钥。-nodes
: 不加密私钥文件。-keyout ca_key.pem
: 输出私钥文件。-out ca_cert.pem
: 输出根证书。-days 365
: 证书有效期 365 天。-subj
: 直接指定证书的主体信息。
第二步:生成服务端私钥和CSR
openssl req -newkey rsa:2048 -nodes -keyout server_key.pem -out server_csr.pem -subj "/C=CN/ST=Beijing/L=Beijing/O=ExampleOrg/OU=Server/CN=localhost"
# 校验命令
openssl req -in server_csr.pem -text -noout
-req
: 生成证书请求。-newkey rsa:2048
: 使用 RSA 算法生成 2048 位密钥。-nodes
: 不加密私钥文件。-keyout server_key.pem
: 输出服务端私钥文件。-out server_csr.pem
: 输出 CSR 文件。-subj
: 指定证书请求的主体信息。
第三步:使用CA签署服务端证书
openssl x509 -req -in server_csr.pem -CA ca_cert.pem -CAkey ca_key.pem -CAcreateserial -out server_cert.pem -days 365 -sha256# 校验命令
openssl x509 -in server_cert.pem -text -noout
-x509
: 生成 X.509 格式证书。-req
: 使用 CSR 文件签名。-in server_csr.pem
: 指定 CSR 文件。-CA ca_cert.pem
: 指定 CA 根证书。-CAkey ca_key.pem
: 指定 CA 私钥。-CAcreateserial
: 自动生成序列号文件。-out server_cert.pem
: 输出签署后的服务端证书。-days 365
: 证书有效期 365 天。-sha256
: 使用 SHA-256 算法签名。
第四步:生成服务端密钥库
openssl pkcs12 -export -inkey server_key.pem -in server_cert.pem -out server_keystore.p12 -name server -passout pass:password# 校验命令
keytool -list -v -keystore server_keystore.p12 -storetype PKCS12 -storepass password
-pkcs12
: 使用 PKCS12 格式。-export
: 导出密钥库文件。-inkey server_key.pem
: 指定私钥文件。-in server_cert.pem
: 指定证书文件。-out server_keystore.p12
: 输出密钥库文件。-name server
: 密钥库条目别名。-passout pass:password
: 密钥库密码。
第五步:生成客户端信任库
keytool -importcert -trustcacerts -file ca_cert.pem -keystore client_truststore.p12 -storetype PKCS12 -storepass password -alias rootca# 校验命令
keytool -list -v -keystore client_truststore.p12 -storetype PKCS12 -storepass password
-keytool
: Java 提供的密钥和证书管理工具。-importcert
: 导入证书。-trustcacerts
: 指定信任此证书为 CA。-file ca_cert.pem
: 指定要导入的 CA 根证书文件。-keystore client_truststore.p12
: 指定生成的信任库文件。-storetype PKCS12
: 指定密钥库类型为 PKCS12。-storepass password
: 设置密钥库的密码。-alias rootca
: 为证书设置别名。
实践-客户端代码
package com.ahrcu.operation.encryption.https;import cn.hutool.core.date.LocalDateTimeUtil;
import org.bouncycastle.jce.provider.BouncyCastleProvider;import javax.net.ssl.*;
import java.io.*;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.security.KeyStore;
import java.security.Security;
import java.time.format.DateTimeFormatter;/*** @author Luke.ye*/
public class HttpsClient {private static int logCounter = 1;public static void main(String[] args) throws Exception {Security.addProvider(new BouncyCastleProvider());log("初始化客户端环境");// 加载客户端信任库String trustStorePath = "/certs/https/client_truststore.p12";String trustStorePassword = "password";KeyStore trustStore = KeyStore.getInstance("PKCS12");try (InputStream trustStoreStream = HttpsClient.class.getResourceAsStream(trustStorePath)) {if (trustStoreStream == null) {log("客户端信任库文件未找到");return;}trustStore.load(trustStoreStream, trustStorePassword.toCharArray());}log("成功加载客户端信任库");// 创建SSL上下文TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("SunX509");trustManagerFactory.init(trustStore);SSLContext sslContext = SSLContext.getInstance("TLSv1.2");sslContext.init(null, trustManagerFactory.getTrustManagers(), null);log("成功初始化SSL上下文");// 建立HTTPS连接URL url = new URL("<https://localhost:8443>");HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();connection.setSSLSocketFactory(sslContext.getSocketFactory());connection.setRequestMethod("POST");connection.setDoOutput(true);log("成功建立HTTPS连接");// 客户端发送消息到服务端String clientMessage = "Hello, Lukeye";byte[] body = clientMessage.getBytes(StandardCharsets.UTF_8);connection.setRequestProperty("Content-Length", String.valueOf(body.length));connection.setRequestProperty("Content-Type", "text/plain; charset=UTF-8");try (OutputStream os = connection.getOutputStream()) {os.write(body);os.flush();log("发送消息到服务端:" + clientMessage);}// 客户端读取服务端响应int responseCode = connection.getResponseCode();log("收到响应码:" + responseCode);try (BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8))) {StringBuilder responseBody = new StringBuilder();String line;while ((line = in.readLine()) != null) {responseBody.append(line).append("\\n");}String serverMessage = responseBody.toString().trim();log("收到服务端响应:【" + serverMessage+"】");}}private static void log(String message) {String nowTime = LocalDateTimeUtil.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss-SSS"));System.out.println(nowTime + " 客户端日志-" + "[" + (logCounter++) + "]:" + message);}
}
实践-服务端代码
package com.ahrcu.operation.encryption.https;import cn.hutool.core.date.LocalDateTimeUtil;
import org.bouncycastle.jce.provider.BouncyCastleProvider;import javax.net.ssl.*;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.security.KeyStore;
import java.security.Security;
import java.time.format.DateTimeFormatter;/*** @author Luke.ye*/
public class HttpsServer {private static int logCounter = 1;public static void main(String[] args) throws Exception {Security.addProvider(new BouncyCastleProvider());log("初始化服务端环境");// 加载服务端密钥库String keyStorePath = "/certs/https/server_keystore.p12";String keyStorePassword = "password";KeyStore keyStore = KeyStore.getInstance("PKCS12");try (InputStream keyStoreStream = HttpsServer.class.getResourceAsStream(keyStorePath)) {if (keyStoreStream == null) {log("服务端密钥库文件未找到");return;}keyStore.load(keyStoreStream, keyStorePassword.toCharArray());}log("成功加载服务端密钥库");// 创建SSL上下文KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509");keyManagerFactory.init(keyStore, keyStorePassword.toCharArray());SSLContext sslContext = SSLContext.getInstance("TLSv1.2");sslContext.init(keyManagerFactory.getKeyManagers(), null, null);log("成功初始化SSL上下文");// 启动HTTPS服务端SSLServerSocketFactory factory = sslContext.getServerSocketFactory();try (SSLServerSocket serverSocket = (SSLServerSocket) factory.createServerSocket(8443)) {log("服务端启动成功,监听端口 8443");while (true) {SSLSocket clientSocket = (SSLSocket) serverSocket.accept();log("接收到客户端连接");// 服务端接收客户端的请求try (InputStream inputStream = clientSocket.getInputStream();OutputStream out = clientSocket.getOutputStream();BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {// 读取请求头,获取 Content-LengthString line;int contentLength = 0;StringBuilder sb = new StringBuilder("\\r\\n");while ((line = reader.readLine()) != null && !line.isEmpty()) {sb.append(line + "\\r\\n");if (line.toLowerCase().startsWith("content-length:")) {contentLength = Integer.parseInt(line.split(":")[1].trim());}}log("收到请求头:" + sb.toString());// 读取请求体(严格按照 Content-Length)byte[] bodyBytes = new byte[contentLength];int totalRead = 0;while (totalRead < contentLength) {int read = inputStream.read(bodyBytes, totalRead, contentLength - totalRead);if (read == -1) break;totalRead += read;}String clientMessage = new String(bodyBytes, StandardCharsets.UTF_8).trim();log("接收到客户端消息:【" + clientMessage + "】");// 构造响应String responseMessage = "Echo " + clientMessage;String httpResponse = "HTTP/1.1 200 OK\\r\\n" +"Content-Type: text/plain; charset=UTF-8\\r\\n" +"Content-Length: " + responseMessage.getBytes(StandardCharsets.UTF_8).length + "\\r\\n" +"\\r\\n" +responseMessage;out.write(httpResponse.getBytes(StandardCharsets.UTF_8));out.flush();log("发送响应消息:" + responseMessage);} catch (IOException e) {log("处理客户端连接时发生异常:" + e.getMessage());}}}}private static void log(String message) {String nowTime = LocalDateTimeUtil.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss-SSS"));System.out.println(nowTime + " 服务端日志-" + "[" + (logCounter++) + "]:" + message);}
}
实践-输出结果
客户端输出结果
服务端输出结果
实践-WireShark抓包结果
对照 💡 HTTPS通信机制 中的过程,我们知道一次HTTPS请求,是从Client Hello开始,HTTP的报文内容在Application Data中,查看Application Data,可以看出请求和响应报文中,其都已经是密文了,说明实现了通道加密。
3.3 数字证书
为了解决数字签名无法确认公钥合法性问题,引入了数字证书(公钥证书)技术,该技术通过第三方背书的方式确保公钥的合法性。
数字证书加密
其过程如下
数字证书签名验签
其过程如下
实践-数字证书实现加密&验签
结合数字证书的加密(数字信封)和签名验签,可以得到如下的交易流程,该流程可以实现:机密性、完整性、身份鉴别和抗抵赖。
注:相关算法可以替换成国密算法,对应关系如下
- AES:SM4
- RSA-2048:SM2
- SHA-256:SM3
- SHA256WithRSA:SM3WithSM2
思路
- 混合加密
- 签名基于摘要
流程
- 客户端生成消息摘要并签名。
- 消息通过 AES 加密,AES 密钥通过 RSA 加密。
- 服务端解密 AES 密钥,解密消息。
- 验签消息摘要,验证消息的完整性和身份。
配置修改
💡
由于需要使用bouncycastle实现证书相关逻辑,因此,需要在jdk的java.security文件中新增Provider。示例如下
<dependency><groupId>org.bouncycastle</groupId><artifactId>bcprov-jdk15on</artifactId><version>1.70</version>
</dependency>
<dependency><groupId>org.bouncycastle</groupId><artifactId>bcpkix-jdk15on</artifactId><version>1.70</version>
</dependency>
实践-运行主类
package com.ahrcu.operation.encryption;import com.ahrcu.operation.encryption.digitalcert.Client;
import com.ahrcu.operation.encryption.digitalcert.Server;
import com.google.common.eventbus.EventBus;
import java.security.*;
import java.security.cert.X509Certificate;
import java.util.concurrent.atomic.AtomicInteger;
import static com.ahrcu.operation.encryption.digitalcert.CertManager.*;/*** @author Luke.ye*/
public class DigitalCertificateDemo {public static AtomicInteger sequence = new AtomicInteger(1); // 时序计数器public static void main(String[] args) throws Exception {String clientKeystorePath = "certs/client_keystore.jks";String clientCertPath = "certs/client_cert.crt";String serverKeystorePath = "certs/server_keystore.jks";String serverCertPath = "certs/server_cert.crt";String password = "password";// 生成证书generateAndStoreCertificate(clientKeystorePath, clientCertPath, "client", password);generateAndStoreCertificate(serverKeystorePath, serverCertPath, "server", password);// 加载证书和密钥对KeyPair clientKeyPair = loadKeyPairFromKeystore(clientKeystorePath, "client", password);KeyPair serverKeyPair = loadKeyPairFromKeystore(serverKeystorePath, "server", password);X509Certificate clientCertificate = loadCertificateFromFile(clientCertPath);X509Certificate serverCertificate = loadCertificateFromFile(serverCertPath);// 初始化 EventBus 和交互EventBus eventBus = new EventBus();Client client = new Client(serverCertificate, clientKeyPair, eventBus);Server server = new Server(clientCertificate, serverKeyPair, eventBus);eventBus.register(server);eventBus.register(client);client.sendMessage("Hello lukeye, 这是一条测试消息!");}
}
实践-证书管理器
package com.ahrcu.operation.encryption.digitalcert;import org.bouncycastle.cert.X509v3CertificateBuilder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;import javax.security.auth.x500.X500Principal;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.math.BigInteger;
import java.security.*;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Date;import static com.ahrcu.operation.encryption.DigitalCertificateDemo.sequence;/*** @author Luke.ye*/
public class CertManager {// 生成自签名证书并存储到本地文件public static void generateAndStoreCertificate(String keystorePath, String certPath, String alias, String password)throws Exception {// 创建密钥对KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");keyGen.initialize(2048);KeyPair keyPair = keyGen.generateKeyPair();// 生成自签名证书ContentSigner signer = new JcaContentSignerBuilder("SHA256withRSA").build(keyPair.getPrivate());X509v3CertificateBuilder certBuilder = new JcaX509v3CertificateBuilder(new X500Principal("CN=" + alias),BigInteger.valueOf(System.currentTimeMillis()),new Date(System.currentTimeMillis() - 1000 * 60 * 60 * 24), // 有效期起始new Date(System.currentTimeMillis() + 365L * 24 * 60 * 60 * 1000), // 有效期结束new X500Principal("CN=" + alias),keyPair.getPublic());X509Certificate certificate = new JcaX509CertificateConverter().setProvider("BC").getCertificate(certBuilder.build(signer));// 保存密钥对到 keystoreKeyStore keyStore = KeyStore.getInstance("JKS");keyStore.load(null, null); // 初始化空的 keystorekeyStore.setKeyEntry(alias, keyPair.getPrivate(), password.toCharArray(), new Certificate[]{certificate});try (FileOutputStream fos = new FileOutputStream(keystorePath)) {keyStore.store(fos, password.toCharArray());}// 保存证书到文件try (FileOutputStream fos = new FileOutputStream(certPath)) {fos.write(certificate.getEncoded());}System.out.printf("[%d] 生成并存储证书完成,路径:%s%n", sequence.getAndIncrement(), certPath);}// 从本地加载证书和密钥对public static KeyPair loadKeyPairFromKeystore(String keystorePath, String alias, String password) throws Exception {KeyStore keyStore = KeyStore.getInstance("JKS");try (FileInputStream fis = new FileInputStream(keystorePath)) {keyStore.load(fis, password.toCharArray());}PrivateKey privateKey = (PrivateKey) keyStore.getKey(alias, password.toCharArray());PublicKey publicKey = keyStore.getCertificate(alias).getPublicKey();return new KeyPair(publicKey, privateKey);}public static X509Certificate loadCertificateFromFile(String certPath) throws Exception {try (InputStream in = new FileInputStream(certPath)) {CertificateFactory factory = CertificateFactory.getInstance("X.509");return (X509Certificate) factory.generateCertificate(in);}}
}
package com.ahrcu.operation.encryption.digitalcert;/*** @author Luke.ye*/
public class EncryptedMessage {/*** 共享密钥加密后的数据*/private final String encryptedData;/*** 加密的共享密钥*/private final String encryptedKey;/*** 签名*/private final String signature;public EncryptedMessage(String encryptedData, String encryptedKey, String signature) {this.encryptedData = encryptedData;this.encryptedKey = encryptedKey;this.signature = signature;}public String getEncryptedData() {return encryptedData;}public String getEncryptedKey() {return encryptedKey;}public String getSignature() {return signature;}
}
实践-客户端代码
package com.ahrcu.operation.encryption.digitalcert;import com.ahrcu.operation.encryption.DigitalCertificateDemo;
import com.google.common.eventbus.EventBus;import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import java.security.KeyPair;
import java.security.MessageDigest;
import java.security.Signature;
import java.security.cert.X509Certificate;
import java.util.Base64;import static com.ahrcu.operation.encryption.DigitalCertificateDemo.sequence;/*** @author Luke.ye*/
public class Client {private final X509Certificate serverCertificate;private final KeyPair clientKeyPair;private final EventBus eventBus;public Client(X509Certificate serverCertificate, KeyPair clientKeyPair, EventBus eventBus) {this.serverCertificate = serverCertificate;this.clientKeyPair = clientKeyPair;this.eventBus = eventBus;}public void sendMessage(String message) throws Exception {System.out.printf("[%d] 客户端:准备发送消息,消息内容为【%s】%n", sequence.getAndIncrement(), message);// 使用 AES 加密消息KeyGenerator keyGen = KeyGenerator.getInstance("AES");keyGen.init(256);SecretKey aesKey = keyGen.generateKey();Cipher aesCipher = Cipher.getInstance("AES");aesCipher.init(Cipher.ENCRYPT_MODE, aesKey);byte[] encryptedMessage = aesCipher.doFinal(message.getBytes());System.out.printf("[%d] 客户端:使用对称加密 (AES) 加密消息完成%n", sequence.getAndIncrement());// 使用服务端公钥加密 AES 密钥Cipher rsaCipher = Cipher.getInstance("RSA");rsaCipher.init(Cipher.ENCRYPT_MODE, serverCertificate.getPublicKey());byte[] encryptedAesKey = rsaCipher.doFinal(aesKey.getEncoded());System.out.printf("[%d] 客户端:使用 服务端证书的公钥 加密对称密钥完成%n", sequence.getAndIncrement());// 生成消息摘要并签名MessageDigest digest = MessageDigest.getInstance("SHA-256");byte[] messageHash = digest.digest(message.getBytes());Signature signature = Signature.getInstance("SHA256withRSA");signature.initSign(clientKeyPair.getPrivate());signature.update(messageHash);byte[] digitalSignature = signature.sign();System.out.printf("[%d] 客户端:适用 客户端私钥 完成对消息摘要的签名%n", sequence.getAndIncrement());// 发送消息EncryptedMessage event = new EncryptedMessage(Base64.getEncoder().encodeToString(encryptedMessage),Base64.getEncoder().encodeToString(encryptedAesKey),Base64.getEncoder().encodeToString(digitalSignature));eventBus.post(event);}
}
实践-服务端代码
package com.ahrcu.operation.encryption.digitalcert;import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.security.KeyPair;
import java.security.MessageDigest;
import java.security.Signature;
import java.security.cert.X509Certificate;
import java.util.Base64;import static com.ahrcu.operation.encryption.DigitalCertificateDemo.sequence;/*** @author Luke.ye*/
public class Server {private final X509Certificate clientCertificate;private final KeyPair serverKeyPair;private final EventBus eventBus;public Server(X509Certificate clientCertificate, KeyPair serverKeyPair, EventBus eventBus) {this.clientCertificate = clientCertificate;this.serverKeyPair = serverKeyPair;this.eventBus = eventBus;}@Subscribepublic void handleEncryptedMessage(EncryptedMessage message) throws Exception {System.out.printf("[%d] 服务端:收到加密消息%n", sequence.getAndIncrement());// 解密 AES 密钥Cipher rsaCipher = Cipher.getInstance("RSA");rsaCipher.init(Cipher.DECRYPT_MODE, serverKeyPair.getPrivate());byte[] aesKeyBytes = rsaCipher.doFinal(Base64.getDecoder().decode(message.getEncryptedKey()));SecretKey aesKey = new SecretKeySpec(aesKeyBytes, "AES");System.out.printf("[%d] 服务端:使用私钥解密对称密钥完成%n", sequence.getAndIncrement());// 使用 AES 密钥解密消息Cipher aesCipher = Cipher.getInstance("AES");aesCipher.init(Cipher.DECRYPT_MODE, aesKey);byte[] decryptedMessageBytes = aesCipher.doFinal(Base64.getDecoder().decode(message.getEncryptedData()));String decryptedMessage = new String(decryptedMessageBytes);System.out.printf("[%d] 服务端:使用 解密后的对称密钥 完成对消息的解密【%s】%n", sequence.getAndIncrement(), decryptedMessage);// 验证签名MessageDigest digest = MessageDigest.getInstance("SHA-256");byte[] messageHash = digest.digest(decryptedMessageBytes);Signature signature = Signature.getInstance("SHA256withRSA");signature.initVerify(clientCertificate.getPublicKey());signature.update(messageHash);boolean isVerified = signature.verify(Base64.getDecoder().decode(message.getSignature()));System.out.printf("[%d] 服务端:使用 客户端证书中的公钥 签名验证%s%n", sequence.getAndIncrement(), isVerified ? "成功" : "失败");}
}
实践-运行结果
运行结果如下
3.4 ⭐️ 最佳实践
从安全的角度出发,结合通道加密、报文加密,我们有以下最佳实践方式
四、其它密码技术
协同签名
使用数字证书技术时,客户端会存储签名使用的私钥,此时会存在秘钥泄露的风险。为了应对该风险,就有了协同签名技术。
落地方案:门限ECDSA、Schnorr签名
相关代码感兴趣可上网搜索、自行实现。
五、参考资料&附件
[1] 《图解密码技术》
[2] 《图解HTTP》
[3] chatgpt-4o 对话,链接详见:https://chatgpt.com/share/67fe08a3-d8d8-8007-bcd3-1ddea9e941d0
相关代码及证书详见
ahrcu-encryption-demo.zip
相关文章:
常用密码技术初探
记得前几年有一部电影叫做《解除好友2:暗网》,它讲述了主角捡到一台电脑,并用它与好友进行视频通讯,但一名黑客通过网络技术篡改了通讯内容,最终导致所有参与视频通话的人都遭遇不测。 电影当然存在夸张成分ÿ…...
电脑知识 | TCP通俗易懂详解 <二>tcp首部
目录 一、👋🏻前言 二、🖃TCP快递单填写(必填部分) 1.🌸TCP快递单样式 2.🏢填写名称 3.🔢TCP序号 4. ✔️TCP确认号 编辑5.✅️确认号的确认号 6.📏首部长度 …...
09-RocketMQ 深度解析:从原理到实战,构建可靠消息驱动微服务
RocketMQ 深度解析:从原理到实战,构建可靠消息驱动微服务 一、RocketMQ 核心定位与架构探秘 1.1 分布式消息领域的中流砥柱 在分布式系统中,消息队列是实现异步通信、解耦服务、削峰填谷的关键组件。RocketMQ 作为阿里巴巴开源的分布式消息…...
MyBatis 如何使用
1. 环境准备 添加依赖(Maven) 在 pom.xml 中添加 MyBatis 和数据库驱动依赖: <dependencies><!-- MyBatis 核心库 --><dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId&g…...
AI日报 - 2025年04月17日
🌟 今日概览(60秒速览) ▎🤖 AGI突破 | OpenAI新模型或证人类未解定理,研究达Level 4 OpenAI安全博客暗示模型将创造新科学,能连接概念提新实验。CEO预测AI将证明人类未解定理,研究员称已达AGI第四层级。 ▎Ǵ…...
【Leetcode-Hot100】缺失的第一个正数
题目 解答 有一处需要注意,我使用注释部分进行交换值,报错:超出时间限制。有人知道是为什么吗?难道是先给nums[i]赋值后,从而改变了后一项的索引? class Solution(object):def firstMissingPositive(sel…...
Servlet简单示例
Servlet简单示例 文章说明 Servlet 虽然是一门旧技术了,但是它的基础性和广泛性仍然不可忽视;我在实践中发现不少同学经常会被它的一些特性给困惑住;时常出现404等错误,这里我写下这篇文章,介绍Servlet的不同版本的特…...
spring:注解@Component、@Controller、@Service、@Reponsitory
背景 spring框架的一个核心功能是IOC,就是将Bean初始化加载到容器中,Bean是如何加载到容器的,可以使用spring注解方式或者spring XML配置方式。 spring注解方式直接对项目中的类进行注解,减少了配置文件内容,更加便于…...
LLM做逻辑推理题 - 野鸭蛋的故事
题目: 四个旅游家(张虹、印玉、东晴、西雨)去不同的岛屿去旅行,每个人都在岛上发现了野鸡蛋(1个到3个)。4人的年龄各不相同,是由18岁到21岁。已知: ①东晴是18岁。 ②印玉去了A岛。 ③21岁的女…...
Linux的目录结构(介绍,具体目录结构)
目录 介绍 具体目录结构 简洁的目录解释 详细的目录解释 介绍 Linux的文件系统是采用级层式的树状目录结构,在此结构的最上层是根目录“/”。Linux的世界中,一切皆文件(比如:Linux会把硬件映射成文件来管理) 具体目…...
C++Cherno 学习笔记day21 [86]-[90] 持续集成、静态分析、参数计算顺序、移动语义、stdmove与移动赋值操作符
b站Cherno的课[86]-[90] 一、C持续集成二、C静态分析三、C的参数计算顺序四、C移动语义五、stdmove与移动赋值操作符 一、C持续集成 Jenkins 商业软件 二、C静态分析 静态分析器会检查你的代码,并尝试检测各种错误,这些错误 可能是你无意中编写的&am…...
python学习 -- 综合案例1:设计一款基于python的飞机大战小游戏
本文目录 pygame模块介绍核心模块与功能开发流程 本文案例 - 飞机大战开发流程1. 导入必要的库2. 定义常量3. 创建精灵类4. 主程序 运行游戏 总结 pygame模块介绍 Pygame 是基于 Python 的开源、跨平台游戏开发库,依托 SDL(Simple DirectMedia Layer&am…...
开启 Python 编程之旅:基础入门实战班全解析
重要的东西放前面 开启 Python 编程之旅:基础入门实战班全解析 开启Python编程之旅:基础入门实战班全解析 在当下热门的编程语言中,Python凭借简洁易读的语法、强大的功能和丰富的库,在数据科学、人工智能、Web开发等诸多领域大…...
Linux笔记---动静态库(原理篇)
1. ELF文件格式 动静态库文件的构成是什么样的呢?或者说二者的内容是什么? 实际上,可执行文件,目标文件,静态库文件,动态库文件都是使用ELF文件格式进行组织的。 ELF(Executable and Linkable…...
SpringBoot整合Logback日志框架深度实践
一、依赖与默认集成机制 SpringBoot从2.x版本开始默认集成Logback日志框架,无需手动添加额外依赖。当项目引入spring-boot-starter-web时,该组件已包含spring-boot-starter-logging,其底层实现基于LogbackSLF4J组合。这种设计使得开发者只需…...
Spring Boot中接入DeepSeek的流式输出
第一步,添加依赖: <!-- WebFlux 响应式支持 --> <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-webflux</artifactId> </dependency> 第二步,配置We…...
路由交换网络专题 | 第四章 | 生成树 | VRRP | 边缘端口
拓扑图 (1)SW1、SW2、SW3 三台交换机之间存在环路问题,需要通过生成树协议破环,请简述二层环路可能导致的问题。 因为交换机在收到一个广播帧之后,会对非接收端口进行转发。每台交换机都转发的话,就行形成一…...
SFOS2:常用容器(布局)介绍
一、前言 最近在进行sailfish os的开发,由于在此之前并没有从事过QT开发的工作,所以对这一套颇为生疏,以此记录一下。以下内容不一定完全准确,开发所使用的是Qt Quick 2.6与Sailfish.Silica 1.0两个库。 二、布局 1.Qt Quick 2.…...
VS qt 联合开发环境下的多国语言翻译
添加Linguist 文件方法,如同添加类文件的方式,那样: 其他跟QT的一样的流程,另外在main函数里要注册一下, QTextCodec::setCodecForLocale(textCodec); QTranslator translator5; QString trans5 fi…...
基于 Python 的 ROS2 应用开发全解析
引言 在机器人操作系统(ROS)不断发展的进程中,ROS2 作为新一代的机器人框架,带来了诸多显著的改进与新特性。Python 作为一种简洁、高效且具有强大数据处理能力的编程语言,在 ROS2 应用开发中占据着重要地位。本文将深…...
AI分析师
01 实操 人工 公司需要开发了一个XX系统,在文件夹中包含了XX.csv,其中每一行表示一个XX样本,最后一列为每个样本的标签,现需要设计模型与系统,请按照以下要求完成算法测试。根据要求完成以下任务,将完成的…...
Redis核心数据类型在实际项目中的典型应用场景解析
精心整理了最新的面试资料和简历模板,有需要的可以自行获取 点击前往百度网盘获取 点击前往夸克网盘获取 Redis作为高性能的键值存储系统,在现代软件开发中扮演着重要角色。其多样化的数据结构为开发者提供了灵活的解决方案,本文将通过真实项…...
LLamaIndex中经常使用的三个模块
from aiostream import stream from fastapi import Request from fastapi.responses import StreamingResponse from llama_index.core.chat_engine.types import StreamingAgentChatResponse这四个模块每一个都很实用,在实际开发中经常用到,下面我就详…...
Idea集成AI:CodeGeeX开发
当入职新公司,或者调到新项目组进行开发时,需要快速熟悉项目代码 而新的项目代码,可能有很多模块,很多的接口,很复杂的业务逻辑,更加有与之前自己的代码风格不一致的现有复杂代码 更别提很多人写代码不喜…...
软考 中级软件设计师 考点知识点笔记总结 day12 计算机网络基础知识
文章目录 计算机网络基础5.1、计算机网络基础知识5.1.1 计算机网络分类5.1.2 七层网络体系结构5.1.3 网络标准5.1.4 TCP/IP协议族5.1.5 IP地址和IPv6简介5.1.6 Internet服务 计算机网络基础 要求掌握以下内容 5.1、计算机网络基础知识 网络体系结构 传输介质 传输技术 传输…...
【扩散模型(十三)】Break-A-Scene 可控生成,原理与代码详解(中)Cross Attn Loss 代码篇
系列文章目录 【扩散模型(一)】中介绍了 Stable Diffusion 可以被理解为重建分支(reconstruction branch)和条件分支(condition branch)【扩散模型(二)】IP-Adapter 从条件分支的视…...
C语言数字图像处理---2.31统计滤波器
本文介绍空域滤波器中的一种:统计滤波器 [定义与算法] 统计滤波(Statistic Filter)定义:基于图像处理中的邻域统计方法,对邻域内的像素信息进行统计,如基于均值和方差的信息,用于平滑或去噪图像,同时保留边缘信息。 算法步骤如下: 统计滤波器的优点和缺点主要包…...
流程设计实战:流程架构设计六步法
目录 简介 1、梳理业务模式及场景 2、甄别核心业务能力 3、搭建差异化的业务流程框架 4、定义L4流程能力 5、L4流程串联 6、展开L5业务流程 作者简介 简介 以往在设计流程的时候,我多数都是采用的自下而上的方式,从具体场景、具体问题出发去做流…...
SDK游戏盾如何接入?复杂吗?
接入SDK游戏盾(通常指游戏安全防护类SDK,如防DDoS攻击、防作弊、防外挂等功能)的流程和复杂度取决于具体的服务商(如腾讯云、上海云盾等)以及游戏类型和技术架构。以下是一般性的接入步骤、复杂度评估及注意事项&#…...
STM32F103C8T6 单片机入门基础知识及点亮第一个 LED 灯
目录 一、引言 二、STM32F103C8T6 基本特性 1. 内核与性能 2. 存储器 3. 时钟系统 4. GPIO(通用输入输出) 5. 外设 三、开发环境搭建 1. 硬件准备 2. 软件安装 四、点亮第一个 LED 灯 1. 硬件连接 2. 软件实现 (1)创…...
JavaScript Worker池实现教程
JavaScript Worker池实现教程 Worker池是一种管理和复用Web Workers的有效方法,可以在不频繁创建和销毁Worker的情况下,充分利用多线程能力提升应用性能。下面我将详细介绍如何在JavaScript中实现一个功能完善的Worker池。 为什么需要Worker池…...
【统信UOS操作系统】python3.11安装numpy库及导入问题解决
一、安装Python3.11.4 首先来安装Python3.11.4。所用操作系统:统信UOS 前提是准备好Python3.11.4的安装包(可从官网下载(链接)),并解压到本地: 右键,选择“在终端中打开”ÿ…...
Navicat导入JSON数据到MySQL表
🤟致敬读者 🟩感谢阅读🟦笑口常开🟪生日快乐⬛早点睡觉 📘博主相关 🟧博主信息🟨博客首页🟫专栏推荐🟥活动信息 文章目录 Navicat导入JSON数据到MySQL表1. 导入入口2.…...
体育比分小程序怎么提示日活
要提高体育比分小程序的日活跃用户(DAU),您可以考虑以下几个方面的策略: 一、核心功能优化 1.实时推送:确保比分更新真正实时,延迟不超过2秒,推荐接入熊猫比分API体育数据,比分实时更新 2.个性化订阅&am…...
【星海随笔】Python-JSON数据的处理
JSON 是一种轻量级的数据交换格式,主要用于在客户端和服务器之间传输数据。 JSON 在 python 里是一个标准库 https://www.jyshare.com/compile/9/ import json data {name: Alice, age: 30, city: New York} json_string json.dumps(data) print(json_string)js…...
Tomcat与Servlet
目录 1 Tomcat 1.1 目录结构 1.2 启动服务器 1.3 部署 2 Servlet 2.1 创建项目 (1)创建Maven项目 (2)目录结构 (3)引入依赖 (4)创建必要的目录结构 (5…...
MySQL MVCC工作流程详解
MySQL MVCC工作流程详解 1. 基础概念 MVCC(多版本并发控制)是通过在每行记录后面保存多个版本来实现并发控制的技术,主要用于提供并发事务访问数据库时的读一致性。 2. 核心要素 2.1 事务ID(DB_TRX_ID) 每个事务都…...
unityTEngine 框架学习记录1
目前项目再用QF框架其中的UI部分,突然有天想学习一下其他好用的框架UI,根据我多年网友胖菊大佬的推荐TE映入眼帘,网上找了一下发现学习教程没有几个,不太适合啥都不会的小白,然后我就加入了ET官方群,里面人长得又帅又有…...
算法的时间复杂度
整理了下算法的时间复杂度,跟大家一起分享下。 时间复杂度O是表示算法运行时间与输入数据规模(通常用 n 表示)之间的关系。算法执行时间随输入数据规模增长的变化趋势。 1、O(1) — 常数时间 无论输入数据多大,执行时间固定不变…...
深度学习 从入门到精通 day_01
Pytorch安装 torch安装 python版本3.9.0 在官方文档里面找到适合你设备的PyTorch版本及对应的安装指令执行即可:https://pytorch.org/get-started/previous-versions/ 针对我的网络及设备情况,我复制了如下指令完成了Torch的安装: …...
AutoToM:让AI像人类一样“读心”的突破性方法
引言:AI如何理解人类的“内心世界”? 如何让AI像人类一样理解他人的意图、情感和动机?这一问题的核心是心智理论(Theory of Mind, ToM),即通过观察行为推断心理状态的能力。近日,约翰霍普金斯大…...
Java实现Redis
String类型 代码 package com.whop.changyuan2.redisTest;import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.redis.cor…...
DAY09:【pytorch】nn网络层
1、卷积层 1.1 Convolution 1.1.1 卷积操作 卷积运算:卷积核在输入信号(图像)上滑动,相应位置上进行乘加卷积核:又称为滤波器、过滤器,可认为是某种模式、某种特征 1.1.2 卷积维度 一般情况下…...
河南普瑞维升企业案例:日事清SOP流程与目标模块实现客户自主简报功能落地
公司简介: 河南普瑞维升企业管理咨询有限公司成立于2017年,目前公司主营业务是为加油站提供全方面咨询管理服务,目前公司成功运营打造河南成品油,运营站点15座,会员数量已达几十万,在加油站周边辐射区域内…...
LeetCode面试热题150中19-22题学习笔记(用Java语言描述)
Day 04 19、最后一个单词的长度 需求:给你一个字符串 s,由若干单词组成,单词前后用一些空格字符隔开。返回字符串中 最后一个 单词的长度。 单词 是指仅由字母组成、不包含任何空格字符的最大子字符串。 代码表示 public class Q19_1 {p…...
车载刷写架构 --- 刷写流程中重复擦除同一地址的问题分析
我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 周末洗了一个澡,换了一身衣服,出了门却不知道去哪儿,不知道去找谁,漫无目的走着,大概这就是成年人最深的孤独吧! 旧人不知我近况,新人不知我过…...
一个测试GPU可用的测试实例
一个测试GPU可用的测试实例: import torch import torch.nn as nn import torch.optim as optim import time import gc import numpy as np from torch.cuda.amp import autocast, GradScalerclass LargeNN(nn.Module):def __init__(self, use_attentionTrue):sup…...
chili3d调试笔记2+添加web ui按钮
onclick 查找 打个断点看看 挺可疑的,打个断点看看 挺可疑的,打个断点看看 打到事件监听上了 加ui了 加入成功 新建弹窗-------------------------------------- 可以模仿这个文件,写弹窗 然后在这里注册一下,外部就能调用了 对了…...
Go-zero:JWT鉴权方式
1.简述 用于记录在go-zero的后端项目中如何添加jwt中间件鉴权 2.流程 配置api.yaml Auth:AccessSecret: "secret_key"AccessExpire: 604800config中添加Auth结构体 Auth struct {AccessSecret stringAccessExpire int64 }types定义jwt token的自定义数据结构&#…...
MySQL的MVCC机制详解
1. 什么是MVCC? MVCC(Multi-Version Concurrency Control,多版本并发控制)是数据库系统中用于实现并发控制的一种技术。它通过保存数据在某个时间点的快照来实现,使得在同一个数据行上可以同时存在多个版本࿰…...