侧边栏

HTTPS原理

发布于 | 分类于 网络

HTTP使用明文发送报文,本身不具备加密的功能。这在危机四伏的互联网中是十分危险的。

在阅读了《图解HTTP》第七章之后,整理了一些关于HTTPS的内容,主要内容就是理解HTTPS的原理和工作机制。

参考:

HTTP安全问题

数据被窃听

因特网是一个世界范围内的计算机网络,数以亿记的设备通过分组交换机和链路构成的网状系统连接在一起。

在开发时我们可以通过代理抓包等方式监听网络请求;类似地,在某个传输环节中HTTP报文就可能遭受到恶意窥视,因为只需要收集在互联网上流动的数据包就可以知道发送的数据了。

通信伪装

HTTP协议本身是没有用户识别机制的,请求和响应都不会对通信方进行确认。

也就是说,任何客户端都可以发起请求,正常情况下服务端只要接受到请求就必然会返回一个响应。

这样就会存在下面几个问题

  • 客户端可能请求到伪装的服务端,或者服务器响应了伪装的客户端
  • 无法判定请求的来源,也无法确定客户端的访问权限
  • 无法拒绝无意义的请求,无法阻止DOS攻击

报文被纂改

HTTP协议无法证明通信报文的完整性,因此在传输过程中,即使请求或响应的内容遭受到纂改也无法获知。

在某些应用场景下,可以通过MD5,SHA-1等散列值校验的方法对报文的完整性进行确定,比如通信双方约定一个加密方式方式,在请求和响应时手动对数据进行加密。

虽然这种方式实现起来比较繁琐且并不是十分可靠,但如果解决了上面提到的数据窃听通信伪装的问题,报文被纂改的风险也会大大降低。

HTTPS

仅仅依靠HTTP自身来保证整个传输过程中报文的安全性是非常困难的,为了统一解决上面提到的问题,需要在HTTP上再加入加密处理和认证等机制,而这种添加了加密和认证机制的HTTP,即称为HTTPS

HTTPS全称为超文本传输安全协议(Hypertext Transfer Protocol Secure),相对HTTP提供了更安全的数据传输保障。

其安全性主要体现在三个方面:

  • 内容加密。客户端到服务器的内容都是以加密形式传输,中间者无法直接查看明文内容。
  • 身份认证。通过校验保证客户端访问的是自己的服务器。
  • 数据完整性。防止内容被第三方冒充或者篡改。

《图解HTTP》里面的结论是:

HTTP + 加密 + 认证 + 完整性保护 = HTTPS

其中的完整性保护可以看做是HTTPS的最终目的,因此,让我们从加密、认证这两个个方面,了解HTTPS的原理。

加密算法

首先是加密算法。

前面提到,为了防止传输过程中数据被篡改,或者被第三方窃听,可以对将通信内容(即报文)进行加密。

通信是服务器和客户端双方的问题,因此在某一方对报文加密的同时,必须保证另一方能够顺利解密,否则,整个加密就毫无意义。

下面主要探讨的是如何在服务器和客户端实现合理的加密传输方式,并简单探讨一些具体的加密技术实现细节。

对称加密

对称加密算法是应用较早的加密算法,技术成熟。

在对称加密算法中,数据发信方将明文和加密密钥一起经过特殊加密算法处理后,使其变成复杂的加密密文发送出去。

收信方收到密文后,若想解读原文,则需要使用加密用过的密钥及相同算法的逆算法对密文进行解密,才能使其恢复成可读明文。

在对称加密算法中,使用的密钥只有一个,发收信双方都使用这个密钥对数据进行加密和解密,这就要求解密方事先必须知道加密密钥。

简单来说,采用对称加密,服务端和客户端的加密和解密都使用相同的加密和解密方式,常见的对称加密算法有DESAES等。

对称加密算法效率比较高,下面使用Node.js内置的crypto演示一下AES对称加密算法

js
const crypto = require('crypto');

// 生成随机的对称加密密钥
const secretKey = '123456';

// 要加密的数据
const dataToEncrypt = 'Hello, World!';

// 创建加密器
const cipher = crypto.createCipher('aes-256-cbc', secretKey);

// 加密数据
let encryptedData = cipher.update(dataToEncrypt, 'utf-8', 'hex');
encryptedData += cipher.final('hex');

console.log('加密后的数据:\n', encryptedData);

// 创建解密器
const decipher = crypto.createDecipher('aes-256-cbc', secretKey);

// 解密数据
let decryptedData = decipher.update(encryptedData, 'hex', 'utf-8');
decryptedData += decipher.final('utf-8');

console.log('解密后的数据:\n', decryptedData);

在上面的代码中,只要知道了secretKey的值,就可以直接创建加密器和解密器。

整个流程看起来比较简单,问题在于:如何安全地约定加密方法和密钥?

对称加密传输时,在建立连接时的报文中,会将钥匙以未加密的方式进行传输,因此就存在钥匙被窃听的风险。

一旦密钥和加密方法被窃听,整个对称加密传输就失去了意义:中间人获取到了秘钥,就可以解密和伪造数据。

非对称加密

对称加密的缺点在于通信双方需要交换密钥,我们来看看非对称加密算法是如何避免这个问题的。

非对称加密算法是一种使用不同的密钥对进行加密和解密的加密算法,常见的非对称密钥加密算法有RSADSA等。

非对称加密算法基于一对相关的密钥,包括公钥私钥。公钥是公开的,可以被任何人获取;而私钥是保密的,只有密钥的拥有者可以访问。

公钥与私钥是一对,他们是两个不同的密钥,这也是非对称加密算法名字的由来

  • 用公钥对数据进行加密,只有用对应的私钥才能解密;
  • 用私钥加密的数据,只有对应的公钥才能解密

下面使用Node.js内置的crypto演示一下RSA非对称加密算法。

公钥加密私钥解密

首先是使用公钥进行加密的数据,可以通过私钥进行解密。

js
const crypto = require('crypto');

// 生成RSA密钥对
const { privateKey, publicKey } = crypto.generateKeyPairSync('rsa', {
  modulusLength: 2048,
  publicKeyEncoding: { type: 'spki', format: 'pem' },
  privateKeyEncoding: { type: 'pkcs8', format: 'pem' }
});

console.log('公钥:\n', publicKey);
console.log('私钥:\n', privateKey);

// 要加密的数据
const dataToEncrypt = 'Hello, World!';

// 使用公钥加密数据
const encryptedData = crypto.publicEncrypt(publicKey, Buffer.from(dataToEncrypt, 'utf-8'));
console.log('加密后的数据:\n', encryptedData.toString('base64'));

// 使用私钥解密数据
const decryptedData = crypto.privateDecrypt(privateKey, encryptedData);
console.log('解密后的数据:\n', decryptedData.toString('utf-8'));

私钥加密公钥解密

然后私钥也可以加密数据,这通常被成为签名,私钥签名后的数据可以使用公钥进行解密,这通常被成为验签

js
// 需要安装一下jsonwebtoken
const jwt = require('jsonwebtoken');

// 生成 RSA 密钥对
const { privateKey, publicKey } = require('crypto').generateKeyPairSync('rsa', {
    modulusLength: 2048,
    publicKeyEncoding: { type: 'spki', format: 'pem' },
    privateKeyEncoding: { type: 'pkcs8', format: 'pem' }
});

console.log('公钥:\n', publicKey);
console.log('私钥:\n', privateKey);

// 要签名的数据
const originalData = 'Hello, World!'

// 使用私钥生成签名
const token = jwt.sign(originalData, privateKey, { algorithm: 'RS256' });

console.log('生成的签名:\n', token);

// 使用公钥验证签名并获取原始数据
jwt.verify(token, publicKey, { algorithms: ['RS256'] }, (err, decoded) => {
    if (err) {
        console.error('验证失败:', err.message);
    } else {
        console.log('验证成功,原始数据:', decoded);
    }
});

可以看出,公钥和私钥本身都具备了加密和解密的功能,只是按照习惯私钥的加密被称为签名而已。

私钥推导公钥

根据生成规则,从私钥推导出公钥是比较简单的,而从公钥推导出私钥是一个困难的数学问题。

下面的代码演示了通过私钥推导出公钥的过程

js
const crypto = require('crypto');

// 生成 RSA 密钥对
const { privateKey, publicKey } = crypto.generateKeyPairSync('rsa', {
  modulusLength: 2048,
  publicKeyEncoding: { type: 'spki', format: 'pem' },
  privateKeyEncoding: { type: 'pkcs8', format: 'pem' }
});

console.log('私钥:\n', privateKey);
console.log('公钥:\n', publicKey);

// 从私钥推导出公钥
const derivedPublicKey = crypto.createPublicKey(privateKey);
const res = derivedPublicKey.export({ type: 'spki', format: 'pem' })
console.log('推导的公钥:\n', res);
console.log(publicKey === res);

因此,一般会将公钥暴露出去,而私钥要严格保密,只让自己知道(这也是他们取名的由来)。

加解密效率

在非对称加密过程中,服务器将公钥发送给浏览器,浏览器就可以用这个的公钥加密数据,由于私钥只有服务器知道,这样就不用担心浏览器->服务器传输过程中的数据被中间人破解了。

同理,浏览器如果也有自己的公钥和私钥,同样可以将公钥发给服务器,这样服务器也可以用公钥加密需要传输的数据,这样也保证了服务器->浏览器数据传输的保密性。

但实际上,一个服务器可能要面对数百万浏览器的访问,非对称加密需要消耗大量的性能,因此实际上HTTPS结合了对称和非对称两种加密方式:

  • 在握手阶段使用公开密钥加密选择一种对称加密算法和密钥
  • 然后在后续的通信阶段使用选择的对称加密算法和密钥进行通信。

OK,这样解决了效率的问题,但还有一个安全问题没有解决。

认证

安全地传输公钥

想象下面这种场景,假设服务器有一对非对称秘钥,包含公钥A和私钥A1,传输过程中有一个中间人,他自己也有一对非对称秘钥,包含公钥B和私钥B1

  • 服务器将公钥A发送给浏览器,被中间人拦截了,中间人实际上将他自己的公钥B发给了浏览器
  • 浏览器拿到了公钥B,加密了对称密钥X,发送给服务器,被中间人拦截了,中间人通过自己的私钥B1,解密获得了X,然后将X用服务器的公钥A进行加密,发送给服务器
  • 服务器使用自己的私钥A1解密获得了对称密钥X,开始通信

从这个过程中可以看出,即使中间人没有服务器的私钥A1,他也可以通过伪造拿到对称密钥X。

因此,浏览器必须要确认收到的公钥是目标网站服务器自己的,而不是中间人的!

如何确认呢?这就需要使用数字证书

数字证书

数字证书由可信的第三方机构(证书颁发机构,Certificate Authority,简称CA)签发,每个CA也都有一对公钥和私钥。

操作系统一般内置了常见的CA的公钥,不经过网络传输,可以认为:CA的公钥是可信的

证书包含签名发起者(也就是网站服务器拥有者)的公钥及相关信息,

  • 证书版本号:标识证书的版本信息。
  • 序列号:唯一标识证书的序列号,用于区分不同的证书。
  • 颁发者信息:包含颁发证书的证书颁发机构(CA)的相关信息,如颁发者的名称、机构信息等。
  • 有效期:指定证书的有效期限,包括起始日期和截止日期。
  • 主体信息:包含证书持有者(签名发起者)的相关信息,如名称、电子邮件地址等。
  • 公钥信息:包含证书持有者的公钥及其算法参数。
  • 扩展信息:可选的扩展字段,用于存储其他的证书相关信息,如主题备用名称、密钥用途等。

这些原始内容会由CA的私钥进行签名(就是私钥加密),称为数字签名,因此完整的证书内容还包括

  • 签名算法标识:指示用于生成证书签名的算法,如RSA、SHA-256等。
  • 数字签名值:使用证书颁发机构的私钥对证书的内容进行签名生成的签名值。

服务器会在响应请求的时候,将数字证书发送给浏览器。

中间人无法像替换公钥那样,将数字证书替换成自己的证书。因为证书是由CA发布的,包含固定的域名,浏览器收到证书之后,判断一下域名就行了。

浏览器收到证书之后,会使用对应的CA公钥,对数字证书上面的数字签名进行验签(就是公钥解密)。

那么只要验签通过,就可以认为证书里面的原始内容没有被修改————即数字证书内容里面的公钥确实是目标网站服务器自己的。

之后,就可以使用服务器的公钥加密对称算法的秘钥,发送给服务端,之后就可以愉快地进行对称加密消息传送了。

签名和验签

更详细的数字签名和验签过程如下

发送方:

  1. 发送方对原始内容进行摘要计算,通常使用哈希函数,如SHA-256,生成消息的唯一指纹。

    • 哈希摘要算法的的特点在于:只有输入相同的明文数据经过相同的数据摘要算法才能得到相同的摘要。如果内容有变化,则会得到完全不同的摘要。
    • 进行摘要计算的原因主要是缩短内容长度,优化验签性能
  2. 发送方使用自己的私钥对摘要进行加密,生成数字签名。

  3. 发送方将原始内容(未加密)和数字签名、以及对应的摘要算法一起发送给接收方。

接收方:

  1. 接收方接收到原始内容和数字签名。

  2. 接收方使用发送方的公钥对数字签名进行解密,得到解密后的摘要。

  3. 接收方对接收到的原始内容,按照约定的摘要算法进行计算,得到自己的摘要。

  4. 接收方将两个摘要进行比对,如果匹配,则消息的完整性得到验证,并且可以信任消息来自于发送方。

签名和验签的过程可以查看下面这张图,来源:cheapsslsecurity.com

为什么中间人替换不了证书

既然无法修改证书上的数据,那么中间人能不能像之前那样狸猫换太子,使用一个包含他自己公钥的证书来替代原始网站服务器的证书呢?

答案是:现在行不通了。

因为证书是不能被伪造的,在签发证书时就会验证域名及域名所有者的身份,浏览器收到证书后,除了上面的验签,还会进行下面的检查

  • 检查SSL证书是否由浏览器中“受信任的根证书颁发机构”颁发
  • 检查部署SSL证书的网站域名是否与证书中一致
  • 检查SSL证书中的证书吊销列表,证书是否被颁发机构吊销
  • 检查此SSL证书是否过期
  • 浏览器会到欺诈网站数据库查询此网站是否被列入黑名单

即使中间人换了一个其他域名的证书,浏览器对比一下请求的域名,一下就拒绝了。

替换完整的证书也不行(证书校验不通过),修改原始证书的部分内容也不行(证书验签不通过),那么,通过数字证书安全传输服务器公钥的目的就达到了。

为什么CA证书是可信的

上面提到的CA公钥保存在操作系统中,实际上就是我们常说的系统根证书

系统根证书是一种在计算机系统中用于验证数字证书有效性的证书。

根证书的存在是为了建立信任关系,确保其他数字证书的有效性。

根证书是数字证书体系的基础,它们由受信任的证书颁发机构(CA)签发,并被内置在操作系统或应用程序中,作为信任链的根节点。

信任链是由一系列数字证书构成的链条,链接了一个信任的根证书到最终要验证的证书。

验证证书的有效性时,系统会追溯信任链,确保每个证书都由直接或间接信任的根证书签发。

这也是抓包工具能够抓取HTTPS的原因,开发者需要将抓包工具的证书信任为根证书。这样抓包工具就可以充当一个中间人

  • 在与服务端的通信中,抓包工具作为正常的客户端接收和发送HTTPS消息
  • 在与本地应用的通信中,抓包工具作为服务端,发送和接收消息,这就要求其证书需要被系统认证,这样浏览器才可以完成与他的HTTPS连接

SSL连接过程

上面介绍了HTTPS的基本原理

  • 使用对称加密来加密通信内容,防止通信内容被窃听
  • 使用非对称加密来传输对称加密约定的密钥,防止密钥被泄漏
  • 使用证书认证的非对称加密的公钥,防止通信伪装

HTTPS并非是一种新的协议,而是在HTTP应用层与TCP传输层之间添加了SSLTSL安全层,为网络通信提供安全及数据完整性的一种安全协议

  • SSL(Secure Sockets Layer 安全套接层)
  • SSL的继任者:传输层安全(Transport Layer Security,TLS)

本章节,将简单梳理一下HTTPS建立SSL连接的通信流程

  • 客户端发起SSL通信请求,请求报文中包含客户端支持的SSL版本,加密组件列表(所使用的加密算法以及密钥长度)
  • 服务器接收到SSL通信,在响应报文中附带服务器支持的SSL版本,并从客户端的加密组件筛选出相关内容并返回给客户端,此外还携带经过认证的公钥证书
  • 客户端对公钥证书进行验证,如果合法,则生成一个叫做Pre-master secret的随机密码串(我的理解是这就是约定的对称加密算法密钥),然后使用服务器的公开密钥进行加密,并发送给服务器
  • 客户端还需要发送一条报文提醒服务器之后的报文都采用Pre-master secret进行加密。
  • 客户端还需要发送一条经过加密的Finished报文表示握手协商阶段的结束,当然,真正的握手协商成功还得看服务器能够顺利解密这条报文。
  • 服务器同样发送提醒报文和Finished报文。
  • 当服务器和客户端的Finished报文交换结束,则表示整个SSL连接建立完成,之后的通信都处于加密认证保护的阶段。

这个过程忽略了很多细节,了解大致流程就行

伪代码

下面是根据上述过程创建的SSL连接伪代码

首先是客户端发送SSL请求

js
var 客户端支持的算法 = client.获取支持的加密和hash算法();
client.发送(客户端支持的算法);

服务端收到SSL请求之后,会将经过认证后的数字证书返回给客户端

js
var { 客户端支持的算法} = server.解析响应()
var { 对称加密算法, hash算法 } = server.选择一种客户端支持的加密算法(客户端支持的算法);
var 证书 = server.获取证书(发证机构, 公钥); // CA证书一般是提前申请生成的
server.发送(证书, hash算法, 对称加密算法);

客户端接收到证书后,会进行验签,然后将约定的对称加密秘钥和消息验证值发送给服务端

js
var { 证书, hash算法, 对称加密算法 } = client.解析响应();
if(client.检测证书合法性(证书)){
    var {公钥} = client.获取证书公钥(证书)
    var 对称算法密钥 = client.生成对称算法密码(random())

    var 消息M = client.使用公钥加密密钥(公钥, 对称算法密钥)
    var 消息验证值V = client.生成hash验证值(hash算法, 对称算法密钥);

    client.发送(消息M, 消息验证值V)
}else {
    连接失败;
}

服务端对对称密钥和消息进行验证,发送SSL Finished报文

js
var { 消息M, 消息验证值V } = server.解析响应();
var 解密结果R = server.使用私钥解密(消息M)
var 消息验证值V1 = server.生成hash验证值(hash算法, 解密结果R);
if (消息验证值V1 == 消息验证值V){
    var 对称算法密钥 = 解密结果R; 

    var 握手终止报文 = server.生成报文(对称加密算法, 对称算法密钥, "握手终止消息");
    server.发送(握手终止报文);
}else {
    客户端发送的对称算法密钥 != 服务端收到的对称算法密钥
    连接失败
}

客户端收到Finished报文,确认可以使用SSL链接,开始传输具体消息

js
var { 报文 } = client.解析响应();
var { 握手终止报文 } = client.解析报文(对称加密算法, 对称算法密钥, 报文);
if (握手终止报文){
    对称算法密钥传输成功
    开始对称加解密传输
}else {
    对称算法使用对称算法密钥解密失败
}

mixed content警告

参考

一个网页可能会依赖很多个外部的网络资源,这些资源有可能是HTTP的,也有可能是HTTPS的。

Mixed content指的就是这种在 HTTPS 页面上同时加载了通过不安全的 HTTP 协议加载的内容。

因为这些HTTP的内容是不受加密保护的,恶意攻击者还是可以替换这部分内容用来攻击,相当于马奇诺防线,防住了,但又没完全防。

Mixed content分为被动混合内容和主动混合内容

  • 被动混合内容包括img、audio、video、object的subresource等标签嵌入的内容
  • 主动混合内容包括script、link、iframe、xhr、fetch、css中所有url引入的、object的data属性等地方引入的内容

开发者应该努力确保页面上所有资源都通过安全的 HTTPS 协议加载,以维护整个网站的安全性。

可以采用下面等机制来阻止加载不安全的资源

小结

本文

  • 首先整理了HTTP通信传输中可能遇见的几个安全问题,
  • 然后介绍了使用对称加密和非对称加密两种避免数据窃听和数据被篡改的问题,并给出了简单实现解释这两种算法的区别
  • 然后介绍了使用证书认证避免非对称加密中存在的通信伪装问题,然后介绍了服务端验证证书的生成和校验流程
  • 最后介绍了建立HTTPS连接时的具体流程,主要包括:约定对称加密算法和密钥、证书验证、使用公钥发送对称密钥和校验消息、对称密钥发送并校验成功后开始进入正式连接阶段

至此,整个HTTPS的学习到此结束,其中还有诸如具体的加密实现方式细节,数字签名的生成以及数字证书的等级等方面的知识,只是简单了解,并没有记录和深入,需要进一步学习和整理。

你要请我喝一杯奶茶?

版权声明:自由转载-非商用-保持署名和原文链接。

本站文章均为本人原创,参考文章我都会在文中进行声明,也请您转载时附上署名。