@TOC
概述
可以把 HTTPS 理解为 "穿着 SSL/TLS 盔甲的 HTTP"。
它的核心目标很简单:
在不安全的网络(如互联网)上建立一个安全的通信通道,确保数据传输的三大安全属性:
- 机密性:内容不会被窃听者解密
- 完整性:内容在传输过程中未被篡改
- 身份真实性:你连接的服务器就是你以为的那个服务器
一、非对称加密与对称加密的结合
HTTPS 的安全并非依赖单一技术,而是巧妙地结合了两种加密方式。
1. 对称加密
Symmetric Encryption
javascript
// 示例:AES 对称加密(简化版)
const crypto = require('crypto');
const secretKey = 'my-super-secret-key-32'; // 同一把密钥用于加/解密
const message = "这是要加密的敏感信息";
// 加密
const encrypted = crypto.AES.encrypt(message, secretKey);
console.log("密文:", encrypted); // 输出乱码字符串
// 解密
const decrypted = crypto.AES.decrypt(encrypted, secretKey);
console.log("明文:", decrypted); // 恢复原文
- 概念:加密和解密使用同一把密钥。
- 优点:速度快,适合加密大量数据(如网页内容)。
- 缺点 :如何安全地把
secretKey发送给对方?如果密钥在网络中明文传输,攻击者可截获并解密所有消息。
2. 非对称加密
Asymmetric Encryption
javascript
// 示例:RSA 非对称加密(Node.js crypto 模拟)
const { generateKeyPairSync } = require('crypto');
// 生成公钥/私钥对(仅演示用,生产环境需更强参数)
const { publicKey, privateKey } = generateKeyPairSync('rsa', {
modulusLength: 2048,
});
const data = "只有私钥能解开的信息";
// 客户端用公钥加密
const encryptedData = crypto.publicEncrypt(publicKey, Buffer.from(data));
console.log("公钥加密后:", encryptedData.toString('base64'));
// 服务器用私钥解密
const decryptedData = crypto.privateDecrypt(privateKey, encryptedData);
console.log("私钥解密后:", decryptedData.toString()); // 输出原文
- 概念 :一对密钥 ------ 公钥 和 私钥
- 公钥加密 → 私钥解密(用于保密)
- 私钥签名 → 公钥验证(用于认证)
- 优点:解决了密钥分发问题。公钥可公开传播,即使被窃听也无法反向推导出私钥。
- 缺点:运算慢,不适合频繁加密大块数据。
3. HTTPS 的智慧:混合加密机制
用非对称加密交换密钥,用对称加密传输数据
javascript
// TLS 握手阶段:客户端生成预主密钥,并用服务器公钥加密发送
let preMasterSecret = generateRandomBytes(48); // 如 48 字节随机数
// 使用从证书中提取的服务器公钥进行加密
let encryptedPreMaster = RSA.encrypt(preMasterSecret, serverPublicKey);
// 发送给服务器
sendToServer('/tls-handshake', { encryptedPreMaster });
// 后续通信全部使用基于此生成的对称密钥
let sessionKey = deriveSessionKey(clientRandom, serverRandom, preMasterSecret);
useSymmetricEncryption(sessionKey); // 开始 AES 加密通信
最终效果:
- 初始阶段通过非对称加密安全传递"会话密钥"
- 后续通信采用高速对称加密(如 AES-GCM),兼顾安全性与性能
二、数字证书(Digital Certificate)
1. 问题引入
如何确保你拿到的公钥是真实服务器的,而不是中间人伪造的?
危险场景:
text
客户端 ──→ 攻击者(伪装成服务器)──→ 真实服务器
↑ 截获并替换公钥 ↑
攻击者用自己的公钥冒充服务器,即可完成"中间人攻击"。
2. 解决方案:数字证书
数字证书就像服务器的"网络身份证",由可信第三方------CA 颁发。
证书的数据结构示例(JSON 形式)
json
{
"subject": {
"commonName": "www.example.com",
"organization": "Example Inc.",
"country": "US"
},
"issuer": {
"commonName": "DigiCert TLS RSA SHA256 2020 CA1",
"organization": "DigiCert Inc",
"country": "US"
},
"publicKey": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...\n-----END PUBLIC KEY-----",
"validFrom": "2025-01-01T00:00:00Z",
"validTo": "2026-01-01T23:59:59Z",
"signatureAlgorithm": "sha256WithRSAEncryption",
"fingerprint": "A1:B2:C3:D4:E5:F6:78:90...",
"extensions": [
{ "name": "subjectAltName", "altNames": ["example.com", "*.example.com"] }
],
"caSignature": "3045022100b9d...fcda" // CA 对整个证书内容的数字签名
}
浏览器收到该证书后,会使用内置的 CA 根证书公钥来验证
caSignature是否有效。
3.信任链机制(Certificate Chain)
javascript
// 伪代码:浏览器验证证书流程
function validateCertificate(cert, rootCAs) {
// 1. 检查有效期
if (new Date() < cert.validFrom || new Date() > cert.validTo) {
throw new Error("证书已过期");
}
// 2. 检查域名匹配
if (!cert.extensions.altNames.includes(currentDomain)) {
throw new Error("域名不匹配");
}
// 3. 验证签名:使用签发者(Issuer)的公钥验证签名
const issuerPublicKey = getTrustedCAPublicKey(cert.issuer);
const dataToVerify = serializeWithoutSignature(cert);
if (!RSA.verifySignature(dataToVerify, cert.caSignature, issuerPublicKey)) {
throw new Error("证书签名无效,可能被篡改");
}
console.log(" 证书验证通过!");
return true;
}
若任一检查失败 → 浏览器弹出警告:"您的连接不是私密连接"
三、HTTPS 通信全过程(TLS 握手详解)
这是 HTTPS 安全通信的核心环节。以下每一步都配有模拟代码或数据交互示意。
步骤 1:Client Hello
客户端发起连接请求:
http
GET / HTTP/1.1
Host: www.example.com
Connection: Upgrade
Upgrade: tls/1.3
同时发送 TLS 层信息(不在 HTTP 头中):
json
{
"tlsVersion": ["TLSv1.2", "TLSv1.3"],
"cipherSuites": [
"TLS_AES_128_GCM_SHA256",
"TLS_RSA_WITH_AES_256_CBC_SHA"
],
"clientRandom": "a1b2c3d4e5f6...", // 32 字节随机数
"extensions": {
"supported_groups": ["secp256r1", "x25519"],
"signature_algorithms": ["rsa_pkcs1_sha256"]
}
}
这些信息告诉服务器:"我能支持哪些加密方式,请你选一个。"
步骤 2:Server Hello
服务器选择最优配置并回应:
json
{
"selectedTlsVersion": "TLSv1.3",
"selectedCipherSuite": "TLS_AES_128_GCM_SHA256",
"serverRandom": "f6e5d4c3b2a1...", // 服务器生成的随机数
"certificate": {
"subject": "www.example.com",
"publicKey": "-----BEGIN PUBLIC KEY-----...",
"caSignature": "3045022100b9d..."
}
}
至此,客户端已获得服务器证书和两个随机数。
步骤 3:证书验证
客户端执行自动验证(无需用户干预,除非出错):
javascript
try {
verifyCertificate(serverCert);
} catch (error) {
showBrowserWarning(` 安全警告:${error.message}`);
blockPageLoad();
}
常见错误提示:
- "此网站的安全证书有问题"
- "NET::ERR_CERT_DATE_INVALID"
- "无法验证此证书是由受信任的机构颁发的"
步骤 4:生成预主密钥(Pre-Master Secret)
javascript
// 客户端行为
const preMasterSecret = generateRandomBytes(48); // 如 ECDHE 中的共享密钥
// 从证书中提取公钥
const serverPublicKey = parsePublicKeyFromCert(serverCert);
// 用公钥加密预主密钥(TLS 1.2 及以前)
const encryptedPreMaster = RSA.encrypt(preMasterSecret, serverPublicKey);
// 发送给服务器
sendEncryptedPreMaster(encryptedPreMaster);
注意:现代 TLS(如 1.3)通常使用 ECDHE 密钥交换,不再需要加密预主密钥,而是通过椭圆曲线计算达成共识,具备前向安全性。
步骤 5:服务器解密预主密钥
javascript
// 服务器行为(TLS 1.2)
const encryptedPreMaster = receiveFromClient();
// 使用自己的私钥解密
const preMasterSecret = RSA.decrypt(encryptedPreMaster, privateKey);
if (!preMasterSecret) {
terminateConnection("预主密钥解密失败");
}
私钥始终保存在服务器本地,绝不外泄!
步骤 6:生成会话密钥(Session Key)
双方使用三个随机数生成相同的会话密钥:
javascript
// 伪函数:PRF 是伪随机函数(Pseudo-Random Function)
function generateMasterSecret(preMasterSecret, clientRandom, serverRandom) {
return PRF(preMasterSecret, 'master secret', clientRandom + serverRandom, 48);
}
function deriveSessionKey(masterSecret, label = 'key expansion') {
return PRF(masterSecret, label, serverRandom + clientRandom, 32); // 例如 AES-256 密钥
}
// 双方独立计算,结果一致
const masterSecret = generateMasterSecret(preMasterSecret, clientRandom, serverRandom);
const sessionKey = deriveSessionKey(masterSecret);
console.log("🎯 会话密钥生成完成:", sessionKey.toString('hex'));
只要输入相同,输出就相同 ------ 这是数学的魅力!
步骤 7:握手完成,开始安全通信
javascript
// 客户端发送加密的 Finished 消息
const finishedMessage = "handshake_finished";
const encryptedFinished = AES.encrypt(finishedMessage, sessionKey, iv);
send(encryptedFinished);
// 服务器解密并验证
const received = AES.decrypt(encryptedFinished, sessionKey, iv);
if (received === "handshake_finished") {
sendAck(); // 回应确认
startSecureHTTP(); // 正式进入 HTTPS 通信
}
从此以后:
所有 HTTP 请求和响应都被封装在 TLS 记录层中,经过对称加密传输:
http[TLS RECORD LAYER] Content-Type: ApplicationData Encrypted Payload: a1b2c3d4e5f6...
四、HTTPS 如同寄密信(带代码类比)
想象你要通过一个不安全的邮递系统(互联网)给朋友(服务器)寄一封密信。
1. 获取公钥(证书)
朋友先通过权威公证处(CA)公证了他的公开信箱(公钥),并把公证书寄给你。
javascript
// 你收到证书后验证它是否可信
const cert = downloadCertificate("https://friend.com");
validateCertificate(cert, trustedCAList); // 返回 true 表示可信
2. 传递密钥(握手)
你写一封信,里面装着一把保险箱的钥匙(会话密钥),然后放进朋友的公开信箱(用公钥加密),寄出去。
javascript
const sessionKey = generateSessionKey();
const encryptedKey = RSA.encrypt(sessionKey, friendPublicKey);
mail.send("公开信箱", encryptedKey);
即使信被截获,别人没有朋友的私人钥匙(私钥),也打不开信箱。
3. 朋友拿到钥匙
朋友用他的私人钥匙打开信箱,取出保险箱钥匙。
javascript
const encryptedKey = mail.receive();
const sessionKey = RSA.decrypt(encryptedKey, friendPrivateKey);
console.log("🔑 会话密钥已就位!");
4. 安全通信开始
现在你们都有了同一把保险箱钥匙。之后所有的信件都放进这个保险箱邮寄,既安全又快捷。
javascript
// 日常通信
function sendSecureMessage(msg, key) {
const encrypted = AES.encrypt(msg, key);
return transmitOverInternet(encrypted);
}
function readSecureMessage(encryptedMsg, key) {
const plainText = AES.decrypt(encryptedMsg, key);
return plainText;
}
五、关键要点回顾(附代码说明)
| 要点 | 说明 | 关键代码体现 |
|---|---|---|
| 混合加密 | 非对称加密用于交换密钥,对称加密用于传输数据 | RSA.encrypt(preMaster) + AES.encrypt(payload) |
| 数字证书 | 包含公钥 + CA 签名,防止伪造 | verifySignature(cert.data, cert.signature, caPubKey) |
| TLS 握手 | 建立安全连接的关键过程 | ClientHello → ServerHello → Certificate → PreMaster → SessionKey |
| 三重随机数 | 客户端随机 + 服务器随机 + 预主密钥 → 唯一会话密钥 | PRF(preMaster, randomA + randomB) |
| 前向安全性 | 即使未来私钥泄露,也无法解密过去的通信 | 使用 ECDHE 密钥交换,每次会话密钥独立 |
现代 TLS 1.3 已默认启用前向安全性,废弃静态 RSA 密钥交换。
六、总结
HTTPS 并不是一种全新的协议,而是 HTTP + TLS/SSL 的安全组合。
它通过:
- 数字证书验证身份( 我连的是真的银行网站)
- 非对称加密建立信任( 安全传递密钥)
- 对称加密保障性能( 快速加密网页内容)
在开放网络中构建出一条"加密隧道",让我们可以安全地浏览网页、登录账号、进行支付。
下次当你看到浏览器地址栏的小锁图标时,就知道:
背后正有一场精密而优雅的加密握手在悄然完成。
本文参考 TLS 1.2/1.3 协议设计原理,结合 Node.js crypto API 风格编写示例代码,旨在帮助初学者理解 HTTPS 底层机制。实际实现涉及 C/C++ 和底层协议栈,此处为高度简化模型。