引言
在之前的网络编程实践中,我们探讨了 HTTP 协议并实现了基于 TCP 的 Web 服务器。然而,HTTP 存在一个根本性的安全缺陷,即明文传输。客户端向服务端发送 GET 或 POST 请求时,数据往往以键值对形式附加在 URL 查询参数中,或者放在请求正文里。虽然 POST 请求的数据不会直接显示在地址栏,但报文在网络上传输时是未经加密的字节流。
这意味着,如果攻击者从运营商路由器等中间节点截获数据包,只需跨越网络层和传输层首部,就能直接读取应用层载荷中的敏感信息(如用户名、密码)。随着网络安全问题日益受到重视,绝大多数站点已转向 HTTPS。HTTPS 并非全新协议,而是在 HTTP 基础上增加了'S'(Security),核心在于对传输数据进行加密。
HTTPS 原理
为什么加密层在应用层?
数据传输遵循 TCP/IP 四层模型:应用层、传输层、网络层和数据链路层。接收方从下层开始解封装交付至上一层。此前我们的应用层协议主要关注数据的格式、序列化和反序列化。除了格式化,还需要增加一个环节——加密层。
将加密功能交给应用层而非传输层或网络层,是因为应用层协议由程序员定义,多种多样且不断更新,而传输层和网络层协议相对固定,适合操作系统处理。加密算法也会随时间更新,若交给操作系统,系统需频繁升级以支持新算法,且一旦出问题影响范围过大。因此,加密层应由程序员在应用层实现。
对称加密与非对称加密
对称加密的困境
加密的基本思想是生成密钥,将原文与密钥运算得到密文。例如,操作数统一加 5 作为加密规则,解密则减 5。理论上,只要双方知晓算法和密钥,即可安全通信。但实际场景中,算法难以手动设计得足够强大,且必须公开算法才能被多种客户端使用。一旦算法公开,黑客也能知晓方程形式。此时,安全性完全依赖于密钥。若密钥在握手阶段以明文协商,会被中间人截获,导致无限递归的'先有鸡还是先有蛋'困境。
非对称加密(RSA)
为了解决密钥分发问题,引入非对称加密。加密和解密使用不同的密钥:公钥和私钥。公钥公开,私钥保密。用公钥加密的内容,只能用对应的私钥解密。
以 RSA 算法为例,本质是模幂运算:
(m^e) % n = c
其中 (e, n) 为公钥,m 为明文,c 为密文。私钥 d 满足:
(d * e) % φ(n) = 1
解密过程为:
(c^d) % n = m
由于大整数分解在当前计算能力下不可行,即使攻击者获取公钥和密文,也无法推导出私钥或明文。
混合加密机制
非对称加密计算量大,不适合直接加密完整报文。实际应用中常结合对称加密。握手阶段通过非对称加密协商出一个对称密钥(如 AES 密钥),后续通信使用该对称密钥加密数据。AES 算法将报文分块进行矩阵变换、行移位、列混淆和轮密钥加,效率远高于非对称加密。
中间人攻击与数字证书
即便采用混合加密,仍存在中间人攻击风险。黑客可在握手阶段伪装成服务端,伪造公钥发送给客户端。客户端误以为这是合法公钥,用其加密对称密钥后返回,黑客利用自己的私钥解密获得对称密钥,随后用真正服务端的公钥重新加密转发给服务端。双方进入'裸奔'状态。
防范此攻击需引入数字证书。服务端向权威 CA 机构申请证书,CA 审核身份后,使用哈希算法生成数字摘要,并用 CA 私钥加密摘要形成数字签名。客户端内置 CA 公钥,收到证书后可验证签名是否匹配,从而确认公钥合法性。此外,证书中包含域名信息,可防止钓鱼网站冒用。
TLS 握手包含三次交互:协商算法、交换密钥、验证完整性。第三次握手中,客户端将所有握手报文汇总计算哈希并使用会话密钥加密发送给服务端,服务端比对哈希值,若不一致则判定握手被篡改,主动终止连接。
代码实现
掌握原理后,我们无需从头编写加密算法,可直接引入第三方库 OpenSSL。OpenSSL 包含 libcrypto(加密算法)和 libssl(TLS 协议逻辑)。
环境准备
我们需要自签名证书模拟真实环境。使用 openssl 命令生成 CA 私钥、服务器私钥及证书:
openssl req -x509 -newkey rsa:2048 -nodes -keyout server.key -out server.crt -days 365


