【计算机网络】HTTPHTTPS
HTTP&HTTPS
- HTTP协议
- 初识HTTP
- 如何抓包
- Fiddler的使用
- 抓包
- 查看包的信息
- 报文格式
- 请求报文
- 响应报文
- 报文对比
- URL
- HTTP方法
- 认识Header
- 初识状态码
- HTTPS协议
- 为什么需要 HTTPS
- 加密基础知识
- HTTPS的工作流程
- 引入对称加密
- 引入非对称加密
- 引入证书
- HTTPS 的工作流程
- 浏览器从输入URL到展示出页面经历了什么
HTTP协议
初识HTTP
HTTP协议是应用层中的一个较为重要的协议, 其使用场景也涵盖了我们生活中大部分使用网络的情景, 例如打开浏览器访问网站的时候, 还有手机APP访问各个服务器的时候也会大概率使用HTTP协议
HTTP协议, 一般来说是基于 TCP 来实现的. 不过在 HTTP3 中, 改为了 UDP. 我们接下来讨论的主要版本都是 HTTP1.1 版本的.
依旧是和以前一样, 学习一个协议, 首先我们就要看他的报文格式, 但是这次的报文格式我们先不看一些图片, 而会采用实践的方式, 来直接进行抓包获取.
那为什么要用这种方式去获取呢? 主要原因是 HTTP 的报文格式还和前面的各种协议的报文格式不一样, 前面的报文格式就是只有一个的, 无论是请求还是响应使用的都是同一个报文格式. 但是 HTTP 不同, 它的请求是请求的格式, 响应是响应的格式, 两个玩意的格式不一样. 那为什么不一样呢? 实际上是因为, HTTP 协议是一种"一问一答"结构模型的协议, 因此请求和响应是需要有一些不同的. 不然我问啥你答啥这不就乱套了吗
此时可能有人问: 那TCP协议的确认应答, 不也是一问一答吗? 为啥人家只有一个报文格式?
实际上, TCP协议中的确认应答返回的ACK, 并不是真正意义上的 “答”, 它只是为了告诉发送端, 数据成功送达了, 其中并没有实际的业务数据. 而这里 HTTP 的一问一答, 则是 “问” 和 “答” 都是含有业务数据的, 因此这里是需要不同的报文格式的.
除了这种"一问一答"的结构模型, 其实还有 “一问多答” (下载文件) , “多问一答” (上传文件) , “多问多答” (串流 / 远程桌面) 这类结构模型
如何抓包
我们抓包需要通过一个软件, 叫做Fiddler
, 这里标一个直达官网的下载链接: Web Debugging Proxy and Troubleshooting Tools|Fiddler. 这里下载可以下载那个 Classic 版本, 另外一个 EveryWhere 的版本应该是要收费的. 安装的过程我们这里就不演示了
打开软件后, 会发现这个软件是一个左右结构的程序, 此时左边显示的是它抓到的包, 右边当我们左击左侧的包后它则会展示该包的详情信息. 此时左边有时会突然弹出一些包也是正常现象, 证明你的设备上有一些应用正在通过HTTP进行传输
此时我们还需要去打开一个抓包 HTTPS 的功能, 因为当今的互联网环境中 HTTP 的包会比较少. 虽然我们主要看的是 HTTP 的包, 但是这两个内容上没有很大的差别, 为了保证能够抓到充足的包提供我们学习, 还是推荐把该功能打开. 具体 HTTPS 是什么, 我们就在后面进行了解
首先点击上方的Tools
, 然后点击Options...
此时点到第二栏的HTTPS栏, 然后把所有的勾都勾上, 此时中间可能会弹出一个窗口问你要不要安装证书, 此时一定要点击这个Yes
, 并且后续也把这个证书安装上
然后再把剩下的勾全部打上就行了
此时我们再去随便访问一个网站, 就会发现可以抓取到HTTPS的包了
此时可能有人很好奇, 这个Fiddler是如何做到抓包的效果的呢?
实际上, Fiddler是一个代理软件, 当我们这边的客户端想向服务器发起请求的时候, 它就不会直接发, 而是将请求交给代理, 由代理来帮忙发送, 同时响应也由代理帮忙接收, 接收完之后再重新交给客户端.
举一个例子, 假如张三想吃饭了, 但是呢他不想自己出去餐馆吃, 于是叫了外卖小哥来去餐馆买一份外卖, 带回来给张三吃. 此时张三就是客户端, 外卖小哥就是代理, 餐馆就是服务器.
而Fiddler就可以在代理的途中, 把请求和响应记录下来, 那么就可以获取到请求和响应的内容了
Fiddler的使用
抓包
现在我们就开始看一下这些抓到的包(在看之前可以先全选Ctrl + A
, 然后使用Del
清空一下), 假设我现在访问了必应的首页
此时可能会发现一些五彩缤纷的东西弹了出来
其中这个蓝色的, 绿色的和紫色的, 这三个分别代表响应是了 HTML 文件(蓝色), JavaScript 文件(绿色)和 CSS 文件(紫色). 黑色的则是一些普通的数据, 具体指的意思是什么我们是不知道的, 除非我们能看到这个网站的代码, 它主要就是一些业务上的数据.
此时可能有人遇到了问题: 为啥我刷新了我的页面但是没有这样的五彩缤纷的数据报呢?
实际上, 这可能是因为命中了浏览器的缓存. 这是什么意思呢? 实际上我们每一次访问网站展示出的网页, 都是需要从服务器那边下载的, 然而有一些网页的内容可能就比较多, 通过网络加载可能就会比较慢. 因此浏览器一般会有一些缓存, 用来存储一些之前加载过的页面, 后续访问的时候就直接读取本地文件, 则会更快一些.
此时我们就可以使用Ctrl + F5
来强制刷新, 让浏览器不要读取本地缓存
查看包的信息
当我们点击一个左侧的包后(这里以一个HTML为例)
此时右上角红色的框框住的地方展示的就是请求报文, 而右下角这个蓝色框框住的地方展示的就是响应报文
接下来我们就来分别看这两个报文
首先, Fiddler对于这个报文的各个信息已经有了一定的分析, 并且做好了分类. 我们这主要还是看它的原始数据报, 此时我们就需要点击上方的那个Raw
选项
此时觉得字太小了不好看则可以点击右下角的View in Notepad
, 也就是在记事本中打开
打开后我们会发现, 这个文本时可以直接查看的, 也就是说 HTTP 的请求数据报是文本格式的, 也就是说这数据报里的内容全都是字符串, 而不是像前面的TCP/UDP/IP 数据报一样, 全都是二进制数据
下面的响应数据也是同理, 先点击 Raw 然后再进行查看
但是此时可能有人就会发现: 这底下的数据为什么是乱码啊? 这不应该是文本数据吗?
实际上最初的数据确实应该是文本的, 但是通常这种HTTP响应在传输的时候会进行压缩, 此时就变成了二进制文本. 那么为什么要压缩呢?
压缩也就是减少了数据报的大小, 那么为什么要减少数据报的大小呢? 实际上就是为了节约网络带宽成本. 一台服务器中, 最贵的资源就是网络带宽, 因为网络是从运营商那里租的, 不是和CPU这样的东西一样买了就是自己的, 用多了是要付更多的钱的, 因此大部分情况下, 会选择对这个数据报进行压缩从而减少网络带宽成本.
同时Fiddler也提供了一个操作来帮我们解压缩这段文本
点击这个后, 就会发现那段二进制被还原成了文本数据, 实际上看这个数据的开头也可以得知这是一个HTML
接下来, 我们就基于抓到的包, 来对报文的内容进行分析
报文格式
请求报文
请求报文的结构主要分为四部分, 我们依次来看
- 首行
首先第一个部分就是首行, 顾名思义就是第一行
我们先从首行看起, 会发现它分成了三个部分:
- GET: GET 是 HTTP 请求的方法, 用于说明这个请求是要干什么的. 具体还有什么方法我们后续介绍.
- https://cn.bing.com/: 这个东西被称作为
URL
, 称作唯一资源定位符, 用于描述一个资源在网络上的位置 - HTTP/1.1: 说明的就是HTTP的版本号. 指的一提的是, 虽然当今HTTP已经有了3.0版本, 但是主流使用的依旧是HTTP1.1
- 请求头(Header)
接下来就是看请求头(header), 这个和上面的首行不一样, 并不单单就指一行, 而是下面这一大串的以键值对结构组成的数据(包括没有截图进来的), 都是请求头
其中的键值对结构每个键值对独占一行, 键和值之间使用:
来区分, 这里的一些键值对属于是"标准规定"的, 也就是说是有一套标准的, 而不是靠写程序的人自定义的. 当然, 我们也可以自定义一些 Header 放到里面去
关于这些请求头说明的信息是什么, 我们下面进行介绍, 我们现在主要是先了解报文里有什么内容
- 空行
然后就是空行, 也很容易理解就是一行全是空的啥都没有, 这个东西主要是用于作为请求头的结束标志
- 正文
空行下边的就是正文(body), 如果发现空行下边啥都没有, 那么就证明这个请求中并没有正文
响应报文
响应报文和请求报文类似, 也是分成四个部分
- 首行
这边的首行同样也是分为了三个部分, 我们依次看这三个部分的意思
- HTTP/1.1: 我们也知道这是一个HTTP版本号, 那就没什么多说的
- 200: 这个表示的是这次请求的状态码, 用于表示这个请求的状态是什么
- OK: 则是对这个状态码的描述, 也就是解释了这个状态码的意思是啥
这里的状态码我们也在后续进行介绍, 继续往下看
- 响应头
这里就和请求一样了, 都是键值对结构
- 空行
同样同于标识响应头的结束标记
- 正文
在响应中, 正文的内容可能就比较长了, 也可能是多种格式, 例如html, css, javascript, 图片, 视频, 音频等等. 下面这里的这个就是一个 html
报文对比
其实可以看到, 两个报文的大体格式都是差不多的, 不过其内部的内容有一些具体差异
此时可能有人要问了: 为什么要有一个空行呢? 这个空行有什么用呢?
实际上这个我们在上面也解释过了, 这个空行主要是用于作为 Header 的结束标志的. 主要是因为 Header 并不是说严格规定的, 我们的代码中也可以给请求中加上 Header, 那么此时你这个 Header 的数量就不是固定的, 此时就需要一个标志来作为结束标识.
那么接下来, 我们就来依次了解一下报文中的一些细节内容, 例如 URL, 状态码, Header这样的东西.
URL
URL (Uniform Resource Locators), 全称统一资源定位符, 主要就是用来定位某个资源在网络中的位置的, 主要由以下几部分组成
我们依次看这些部分
- 协议方案名: 说明访问资源时使用的协议是什么
- 登录信息: 这个东西当今几乎已经不会使用了, 主要是容易暴露账号密码
- 服务器地址: 指向服务器的位置, 可以是 IP 地址, 也可以是域名
- 端口: 通过 IP 地址, 只能断定服务器的位置, 并不能找到是哪个应用程序, 因此需要端口来定位应用程序. 值得一提的是, URL 中的端口有时候是可以省略的, 其中 HTTP 协议中省略的话, 那么默认端口就是 80, 而 HTTPS 的默认端口则是 448
- 带层次的文件路径: 描述了需要的资源在服务器上的具体位置. 虽然看起来像是一个目录, 但是实际上拿到的数据也不一定就是以目录的形式存储在服务器中的, 也有可能是映射到不同代码上的, 甚至还可能是传过去的参数. 这里的目录格式怎么写, 主要是和服务器的代码密切相关. 也就是说, 这部分也是主要由写程序的人来指定的, 了解即可
- 查询字符串(query string): 一种键值对的结构, 以 ? 开头. 键值对和键值对之间使用 & 来分隔, 键和值之间使用 = 来分隔.
query string
中的内容和用途都是由些程序的人来指定的, 了解即可 - 片段标识符: 有的网页内容很长, 此时就会将他分成多个片段, 方便实现页面内部的跳转(一些技术文档就会这样干)
关于查询字符串, 还有一些额外的注意点, 假如查询字符串中的值部分需要包含一些特殊符号的话, 那么此时就需要进行URL encode
操作, 也就是进行一层 URL 编码. 因为我们可以看到, URL中的:
, ?
这样的符号, 是有特殊含义的, 因此如果包含了这样的符号, 那么就需要进行转义
比如, 我们的+
在转义过后, 就会变成%2B
, 其中2B
就是+
的ASCII
码值. 包括中文也是需要转义的, 会通过UTF-8
来转义, 同样是通过%配合UTF-8
码值组成的转义字符
在后续使用URL的时候, 要记得针对查询字符串做好URL encode
的动作, 如果没有处理好, 那么就可能导致浏览器解析失败, 导致请求无法正常运行
HTTP方法
上面我们简单介绍首行的时候提到过, 请求报文中有一个GET方法, 那么这里还有什么其他的方法吗?
实际上是有的, 比如我们如果在抓到的各种包里面一个一个找, 或许就可以找到一些不同的方法, 比如下面的这个就是一个POST请求
那此时可能有人要问了: 这GET请求和POST请求有什么区别呢?
实际上, 并没有本质的区别, 双方是可以互换场景使用的. 但是我们会有一定的使用习惯上的差异:
- GET 请求通常会把要传给服务器的数据加到 URL 的 query string 中, 而 POST 方法则是会把要传给服务器的数据放到 body 中. 这并不是硬性规定, 只是习惯用法, 但是推荐遵守. 因为假如通信的另一方遵循了这个原则, 这边没遵循, 此时通信可能就无法正常进行
- 语义上, GET 方法通常用来获取数据, POST 请求通常用于提交数据(登录 + 上传)
另外可能有一些其他关于 POST 和 GET 请求的说法, 这里也进行一些简单的说明
- GET 请求能传递的数据量有上限, POST 传递的数据量没有上限
这个说法实际上是一个远古问题, 早期版本的浏览器考虑到了硬件资源非常拉跨的这一点, 针对 GET 请求的 URL 进行了限制长度的操作. 但是实际上 RFC 文档并没有任何规定说 URL 必须要多长. 并且当今的浏览器已经没有了这样的限制, 因此这个说法不太正确
- GET 请求传输的数据不安全, POST 请求传输的数据更加安全
为什么会这样说, 其实是因为像用户名密码这样的信息, GET 请求会放到 URL 里面, 用户可以在浏览器上面直接看到. 而 POST 请求则是会放到 BODY 里面, 此时用户直接看就看不着了.
那这个是否安全的说法是否正确呢? 实际上你如果是从用户角度来看, 似乎确实有点道理, 但是如果你是要从真正的网络安全方面来看, 那这个说法肯定是不对的. 在网络安全中, 安全主要指的是数据不易被黑客利用, 利用包括且不限于获取, 破解等等行为. 但是 GET 和 POST 请求中, 即使存放的地方不一样, 只要你没有对数据进行一些加密措施, 那么抓取到的数据就是完整的保存在里面的, 那也没什么安全可言了.
比如我们这里直接通过 Fiddler 简单抓一下, 就能抓到 POST 请求的 body, 那么如果你没加密, 你这些数据不就一清二楚了吗
- GET 只能给服务器传输文本数据, POST 可以给服务器传输文本数据和二进制数据
实际上 GET 请求如果使用了 body, 也是可以传输二进制数据的, 只不过一般不用而已. 并且即使不用 body, 也可以把二进制数据进行转码成文本然后放到 URL 的 query string 中
此时可能就会有人问了: 二进制还可以转码成文本?
实际上这个操作并不罕见, 比如我们看一个涉及到上传图片的请求(例如更新头像这样的), 可能就会看到请求里面有一大段看不懂的东西, 但是这也不是乱码, 就是一些文本数据的组合. 这些东西就是图片的本体信息, 只不过进行了 base64 转码将图片本身的二进制数据转换成了 base64 码值
- GET 请求是幂等的, POST 请求是不幂等的
幂等指的是输入和输出是相对稳定的. 举一个例子, 我让计算器去算一个1 + 1
, 那么每次得到的结果都是2
, 那么这就可以称作是幂等的. 但是假如我此时去问一个小狗1 + 1
等于几, 此时它的反应就是未知的, 不确定的. 那么这个就是不幂等的.
回到 GET 和 POST 这个话题, 实际上 GET 和 POST 是否幂等这个问题并不是能直接说的, 这个主要是根据代码是如何书写来定的. 虽然 RFC 的标准文档中是推荐将 GET 请求实现成幂等的, 但是有没有不幂等的情况呢? 有, 举一个最简单的例子
比如搜索引擎推送的广告, 我们即使每次搜的东西都一样, 可能推送的广告条目每次都不一样
- GET 请求可以被浏览器缓存, POST 不可以被缓存
这相当于是幂等性质的连续, 如果是幂等的, 每一次结果都相同, 那自然就是可以缓存下来的, 但是如果不幂等, 那缓存了也没什么用
这里假如我们遇到了类似于:" GET请求和POST请求有什么区别呢?" 的这种问题, 我们主要回答首先需要明确他们没有本质区别, 然后再说明一下那两个使用习惯上的差异即可
实际上还有一些其他的方法类似于 PUT, HEAD, DELETE, OPTIONS, TRACE, CONNECT, LINK, UNLIKE 之类的, 实际上 HTTP 这么多的方法在当初诞生的时候, 实际上是为了区分不同的语义存在的. 例如 DELETE 就是去表示删除文件的, PUT 是表示传输文件的, HEAD 使用获取响应头的(即不包含 body).
但是在实际的使用过程中, 这样的初心就逐渐的被遗忘了, 如今HTTP的各种请求的使用已经不一定就是按照自己的初心来的了, 使用已经变得更加的随意. 如今的请求占比中 GET 占绝大多数, POST 占一部分, 而剩下的那些就是上面的这些请求, 因此这里就不过多介绍了.
认识Header
Header 我们上面也说过, 其中的部分键值对是有标准规定的, 也就是说这些标准规定的键值对我们是不能随意自定义的. 因此我们就有必要对这里的标准有一定的了解, 这里主要就介绍一些重要的键值对来介绍一下
- Host: 用来表示主机所在的位置和端口号, 虽然和 URL 中的那个信息大概率相同, 但是特殊情况下也会有不同(使用代理的时候)
- Content-Length: 用来描述 body 中数据的长度
- Content-Type: 用来描述 body 中数据的格式
上面的这两个属性, 只有在有 body 的时候才会存在, 那为什么需要这两个属性呢?
首先看第一个属性 Content-Length, 这其实还是之前谈到过的粘包问题的一个解决方案, 由于 HTTP 协议在传输层是基于 TCP 实现的, 也是使用字节流传输的, 因此为了能够区分哪里到哪里是一段数据, 就需要这个东西 Content-length 来帮助区分.
此时有人就会问了: 那 GET 请求大部分没有 body 的, 那就没有这个属性, 那它又要怎么区分呢? 还记得数据报中的第三个部分: “空行” 吗, 这个就是用来标识边界的. 也就是说这个空行, 一部分是为了标识空行的结束标志, 另一部分则是为了解决 TCP 中的粘包问题.
第二个属性 Content-Type, 主要就是用来说明传输的数据在 body 中是什么格式, 那这样接收方就会根据特定的文件类型来进行处理
- User-Agent(简称UA): 主要用来描述用户使用什么样的设备, 什么样的浏览器上网
为什么要这个属性呢? 实际上是因为在过去, 浏览器的功能是比较有限的, 但是随着时代的发展, 有一些浏览器就支持了一些新的功能, 此时网站的设计者也就可以跟随时代潮流更新自己的网站. 但是有一个问题, 假如使用旧版本的浏览器访问新版本网页的话, 就会出现兼容性问题. 因此这个属性就可以帮助网站的设计者去区分谁用的是什么样的浏览器, 然后针对它们使用的浏览器来进行特定的响应.
然而随着浏览器的发展, 如今 UA 的作用已经没有那么关键了, 现在 UA 主要用来区分是 PC 还是移动端, 但是也不是返回特定的网页, 大部分情况下只是为了统计, 因为当前前端已经有了一种技术可以使得网页可以兼容不同的设备
- Referer: 用来描述当前页面是从哪个页面跳转过来的
实际上这个东西也就是为了统计, 尤其是对于那些搜索引擎和广告商的合作是有很大意义的. 广告商会根据记录下来访问源, 从而进行分成, 但是这个也是主要看是按什么计费, 这里不多讨论.
此时可能有人就要问了: 那是不是可能有人把这个偷偷改成别人的了, 然后别人就能赚更多了?
实际上在过去还真的有这个操作, 那这个事情又是谁做的呢? 实际上就是运营商. 那既然有这样坑人的事, 那些搜索引擎公司肯定不乐意, 因此它们也从两个方面上进行了反制: 1. 法律 2. 技术
其中技术上的反制, 就是使用了 HTTPS 来代替 HTTP. HTTP 中最大的问题主要就是"明文传输"的问题, 而 HTTPS 则是基于一些协议对这个问题进行了一定的修复. 所谓的明文传输就是, 不对数据进行任何加密, 明文传输就容易被第三方获取并修改. 而 HTTPS 则是对 HTTP 的数据进行了加密, 此时第三方就无法轻易的获取并修改了. 因此这也是为什么如今网络中大部分网站都是以 HTTPS 为主而不是 HTTP
- Cookie: 一种浏览器往本地存储数据的一种机制
在访问网站的过程中, 难免会产生一些临时数据, 这些临时数据就会存储在本地, 这些数据可能在后续和服务器进行交互的时候也可以用到. 但是这些数据并不是直接以文件的形式存储, 因为要考虑安全性. 如果是网页可以随意存储文件到本地设备, 那假如访问了一些带有病毒的网页, 那么此时就可能会导致一些不可逆的后果. 因此浏览器引入了 Cookie 的机制, 虽然也是存储硬盘文件的方式, 但是需要经过浏览器, 并且只能存储键值对, 也就是只能存储简单的文本数据.
初识状态码
状态码实际上就是以一个数字来表示这个响应的状态的, 下面我们就简单介绍一些状态码, 详细的信息可以看这个: HTTP 状态码
状态码的大体分布分为5类:
- 1xx:正在处理, 需要继续发送数据
- 2xx:成功处理请求
- 3xx:重定向
- 4xx:请求错误
- 5xx:服务器错误
首先就是以 1 开头的部分, 这部分主要是告诉客户端这边别停, 还需要继续发送一些数据. 常见的有一个 101 状态码, 它用于去升级协议.
例如客户端现在有一些别的通信需求, 需要更换协议, 那么此时就会发送一个带有特殊 Header 的请求, 例如下面这样的
Connection: upgrade
Upgrade: 要升级的协议
那么此时如果服务器同意升级, 那么也会返回一个含有同样 Header 的响应, 同时此时响应的状态码就是 101.
以 2 开头的这一部分, 主要就是告诉客户端, 这次通信成功进行, 其中比较常见的就是 200, 代表请求成功.
以3开头的这一部分, 和它的名字一个意思, 就是给这个请求重新指路. 比如一些网站迁移了, 此时网站的地址发生了变化, 那么就可以给旧链接挂一个重定向响应, 告诉客户端你应该去这个地址.
其中也有一些不同的重定向状态, 例如 301 指的就是永久重定向, 而 302 指的就是临时重定向. 意思就是一个是永远都重定向, 另外一个则是暂时的重定向, 后面可能会恢复
以 4 开头的这一部分, 主要要告诉客户端的就是这个请求可能是有问题的, 例如著名的404 状态码, 就是告诉客户端, 请求中要找的资源不存在, 这个发生的情况主要可能就是输入了一个错误的 URL 导致的.
其中也有一些其他的状态码, 我们简单了解一下
- 400, 表示请求有问题, 可能是没有提供服务器需要的资源
- 401, 服务器要求需要身份验证, 一般可能因为用户未登录
- 403, 它表示的是客户端没有权限访问请求中的资源, 一般可能因为用户尝试越权
- 405, 表示不支持方法, 可能服务器的这个资源只允许使用 POST 方法访问, 而这里使用了其他方法访问.
以 5 开头的这一部分, 主要就是告诉客户端服务器出错了, 这边也有一个比较著名的状态码就是 500, 它指的就是内部服务器出了问题, 一般这种问题都是由服务器源代码有 Bug 导致的
常见的状态码主要就是200, 301, 404, 500这几个, 我们主要知道一下这几个代表的意思就行, 其他的遇到了再去查询
HTTPS协议
为什么需要 HTTPS
HTTPS 实际上上面也提到过, 主要就是解决了 HTTP 的"明文传输"的问题, 可以有效防止黑客进行数据的窃取和运营商劫持的情况发生. 实际上 HTTPS 内部结构和 HTTP 没有很大的不同, 因此我们上面即使抓的都是 HTTPS 的包但是实际上讲解的也是 HTTP 的知识. HTTPS 实际上就是在 HTTP 的基础之上, 引入了"加密"机制
网络实际上是一个危机四伏的地方, 可能我们现在没有什么感觉, 那我们不妨以一个例子来看
假设我们在一个商场里面, 并且连接了商场的公共路由器, 此时可能有人觉得这有啥, 我这不是常规操作?
此时确实没啥, 但是假如有一个黑客, 入侵了这个路由器, 并且对经过路由器的数据报进行抓包分析, 而你正巧在这个商场里面付钱, 你需要把密码的这种请求发送到银行或者支付宝的服务器那里, 此时你的密码就可能会被黑客截取, 此时黑客就可以用你的密码来进行一系列操作
但是为什么如今这种事情基本不会发生呢? 实际上是各个方面的技术人员们付出的巨大努力才使得我们能够这样操作, 例如对数据进行了加密, 银行或者支付宝有一定的算法用来计算操作的安全系数, 使得黑客即使真拿到密码也不能操作
因此如果像 HTTP 协议这样明文传输, 是非常危险的, 无异于在网络上把自己的家底全部袒露出来给别人看. 因此 HTTPS 就显得十分重要, 接下来我们就了解一下 HTTPS 的加密机制
加密基础知识
再了解 HTTPS 的加密机制前, 我们首先要知道一个数据要加密, 应该经历一些什么样的过程
加密, 可以理解为给数据加了一个锁, 如果想要解锁, 那么就需要一个钥匙. 同样的, 我想要锁住一个东西也需要钥匙. 加密也是一样, 我们需要一个"钥匙", 给数据加上锁, 同时也需要"钥匙"来给数据解锁, 此时这个"钥匙"就被称作密钥
(虽然这个密钥的"钥"长的和钥匙的"钥"一样, 但是实际上这个字读yue
, 但是大多数人还是习惯读yao
, 不过读音总是随着大众的, 因此喜欢怎么读就怎么读就行)
此时就会有明文 + 密钥 -> 密文
和密文 + 密钥 -> 明文
这样的转换, 但是和日常生活中大部分情况下开锁关锁只能用相同的钥匙不太一样, 加密和解密其实有两种方式
- 对称加密
简单的说就是, 加密和解密使用的是同一个密钥, 假设密钥为key
, 那么就有明文 + key -> 密文
和密文 + key -> 明文
- 非对称加密
和对称加密不同, 这里加密和解密使用的就不是同一个密钥了, 此时一般会有两个密钥, 分别称作"公钥"和"私钥", 其中这两个玩意的加密解密关系我们先直接看一个例子
假设有一对公钥和私钥, 那么就有明文 + 公钥 -> 密文
和密文 + 私钥 -> 明文
或者 明文 + 私钥 -> 密文
和密文 + 公钥 -> 明文
也就是说, 如果使用了公钥加密, 那么就只能用私钥解密. 如果使用私钥加密, 那么就只能用公钥解密. 这一对公钥私钥是成对出现的.
HTTPS的工作流程
引入对称加密
既然不能明文传输, 那么加密不就完了嘛, 那HTTPS首先就引入了对称加密, 使得数据在传输的过程中, 是加密状态的密文, 那么即使黑客截取到了密文, 也很难解密出里面的实际信息是什么.
此时可能有人问: 那这个密钥是每个客户端都一样的吗?
那肯定不是, 如果都是一样的那岂不是只要破解了一个, 那么其他的不就是和没加密一样了吗? 因此这里必须要求每一个客户端使用的密钥都不一样, 这样彼此就不知道互相的密钥.
但是这样还有一个问题, 服务器又要如何解密呢? 密钥应该怎么给服务器? 这里实际上也是通过网络传输来给服务器的, 但是这样就引出了一个新的问题: 如果黑客截取到了密钥, 那这加密不就没用了吗?
确实如此, 那我们能使用对称加密再加一次吗? 很明显也不能, 因为这样那这个密钥又被截取了呢? 这样就形成了无限套娃, 是没有结果的. 因此此时就需要考虑使用非对称加密
引入非对称加密
在正式通信前, 由服务器这边生成一对公钥和私钥, 同时把公钥传输给客户端. 此时客户端就通过这个公钥对等会通信使用的对称密钥进行加密. 此时私钥就只有服务器有, 也就是说, 只有服务器和客户端这边才能知道这个对称密钥是什么, 其他人都难以解密. 那么此时就可以正常进行对称加密通信了
下图是一个例子
此时即使黑客拿到了中间生成的公钥和使用公钥加密的对称密钥key
, 也不能解密出对称密钥key
, 因为使用公钥加密的数据只有私钥才能解密.
此时可能有人要问: 既然非对称加密这么牛, 为什么不全部使用非对称加密得了, 还要用非对称加密加密一下对称密钥, 这么麻烦.
实际上, 非对称加密相对于对称加密的运算成本是要更高的, 因此一般使用非对称加密的环节一般就是一些关键环节使用一下, 例如上面那个例子里面就单独对一个公钥进行非对称加密. 并且这种密钥的大小肯定比实际上的业务数据小很多, 因此加密的运算量也不会很多.
但是如果要使用非对称加密来加密大量的业务数据, 那么此时整体的传输效率相对于使用对称加密来加密业务数据就会大大降低. 对称加密和非对称加密共同使用的方式, 可以在保证安全性的同时又保证效率的影响最低.
此时可能有些人会提出一个疑问: 我假如入侵了路由器, 我可以拿到公钥, 那么此时我能不能使用这个公钥来做文章呢?
实际上还真可以, 拿到公钥后, 黑客就可以假扮一个服务器的角色, 给客户端发送另一个假公钥, 而自己藏着这个真公钥方便后续对服务器进行伪装. 同时等待客户端根据假公钥生成的对称密钥, 此时黑客就可以根据自己手中的假私钥来解密这个对称密钥, 从而拿到所有的数据了.
下图是一个例子
这个操作也被称作为"中间人攻击", 简单的说就是黑客会扮演一个中间人的角色, 面对客户端就将自己伪装成服务器, 面对服务器就把自己伪装成客户端, 让通信双方都以为自己在正常和对方通信.
这个通信中最大的漏洞就是, 客户端无法确认这个公钥到底是谁给的, 因此要解决中间人攻击的关键就是在于, 如何让客户端学会区分这个公钥是不是真的服务器发的. 因此此时就需要引入第三方, 证书颁发机构(CA, Certificate Authority).
引入证书
当网站的开发人员搭建服务器的时候, 此时会直接生成好一份公钥私钥, 并且不会多次生成, 只会有这一份, 然后就会把一些有关服务器的信息和这个公钥一起提交给证书颁发机构, 当证书颁发机构审核认定没有问题的时候, 就会给这个服务器颁发一个"证书". 这个证书并不是什么荣誉证书, 而是一个数据, 内部包含了一些服务器的信息以及后续用来校验的关键信息.
在后续通信的时候, 客户端就会先问服务器, 你的证书是什么? 然后客户端根据证书的信息来确定是否确实是服务器本身, 并且此时会拿到证书中写的公钥. 随后再通过这个公钥, 去返回对称加密的密钥.
此时可能就有人要问了: 那客户端怎么知道这个公钥改没改呢?
这就要说到证书中的一个机制, 数字签名. 这个数字签名是指公证机构生成证书的时候, 会使用一个私钥对其进行签名操作, 其对应的公钥则是存储在操作系统中. 因此即使黑客想要更改这个数字签名, 也是几乎不可能的. 同时由于这个数字签名的存在, 客户端就能分辨这个证书是否被篡改过.
我们也可以查看一下自己电脑上有的证书, 一般在浏览器的设置中可以查看
如果此时客户端识别到证书不对劲, 此时就可能会弹出一些警告来提示用户, 存在安全风险, 是否还要继续操作
此时可能还有人问: 那黑客能不能自己申请一个证书, 然后之前那样的"中间人攻击"呢?
此时主要就是看证书颁发机构的操作了, 人家也不是傻子, 在颁发证书前是会进行细致的校验, 因此这种伪装的情况就难以发生了.
HTTPS 的工作流程
根据我们上面的说法, HTTPS 的工作流程主要如下所示
- 服务器搭建的时候, 会生成一份公钥 1 和私钥 1, 并且把服务器信息和公钥1发给证书颁发机构, 请求获取证书
- 证书颁发机构审核完毕后, 那么就会使用证书颁发机构持有的私钥 2 对服务器提供的信息(包括公钥 1)进行数字签名, 随后将证书提供给服务器. 此时私钥 2 对应的公钥 2 内置在操作系统中.
- 客户端发送请求, 尝试和服务器建立连接, 询问证书
- 服务器收到请求, 返回证书
- 客户端通过操作系统内置的公钥 2, 看看证书有没有问题, 例如证书是否过期, 是否被篡改等
- 客户端验证证书没有问题, 拿到公钥 1, 生成密钥 3 (对称加密用), 使用公钥 1 对密钥 3 进行加密, 发送给服务器
- 服务器收到加密后的密钥 3, 使用私钥 1 解密, 获取密钥 3
- 后续服务器和客户端就可以使用密钥 3 进行通信了
下面通过一个图片来总览这个流程
这个过程实际上也是通过一个协议来支持的, 这个协议就叫做 SSL/TLS 协议, 上面描述的询问证书获取密钥的过程, 就可以说是 SSL/TLS 握手的核心流程, 具体上可能有所差异, 例如可能还需要一些准备工作或者是加密细节上的问题, 但是核心就是如上所述的.
浏览器从输入URL到展示出页面经历了什么
这个问题是一个经典的问题, 主要就是考察我们在网络这一块的知识. 简单的过程可以说, 给服务器发送一个请求, 服务器返回一个响应, 最后展示页面. 当然, 既然这里都特地把这个东西列出来了, 自然就不会说的这么简单
下面我们结合我们学过的 HTTP, HTTPS, DNS, IP, ARP, 以太网协议的相关知识, 来组织一个参考的答案. 具体流程如下
- 浏览器解析: 浏览器解析URL, 生成 HTTP 报文
- DNS解析: 先查看本地缓存是否有对应域名, 如果没有就开始搜寻域名服务器, 获取对应IP地址, 如果没有找到返回域名不存在
- 传输层打包: 浏览器会调用操作系统的 Socket API进行TCP/UDP的打包, 如果使用的是 TCP, 那么还需要进行三次握手的操作来建立连接
- HTTPS: 如果是HTTPS, 这里需要先询问服务器证书, 获取密钥(或者可以说进行 SSL/TLS 握手)
- 网络层打包: 打包IP数据, IP主要是为了能够规划路径
- 数据链路层打包: 打包MAC地址, MAC地址主要关注路径中的相邻节点
- 发送给路由器: 随后设备通过路由器的IP地址, 尝试在自身的 ARP 表中通过 IP 地址中找到路由器的 MAC 地址, 如果找到了就通过交换机发送过去, 如果没找到, 那么就先进行一次 ARP 广播获取到路由器的 MAC 地址. 获取到后就将数据进行发送
- 交换机转发: 交换机收到要发送到路由器的数据后, 会查询自己的 ARP 表, 看看哪一个网口对应着路由器, 如果找到了就进行转发, 如果没找到依旧是会进行一轮广播, 找到后进行转发
- 路由器转发: 路由器收到数据后, 会将源 IP 与 源端口号修改为路由器的公网 IP 和 公网端口号, 并且修改源 MAC 地址. 同时根据自身的路由表去搜索下一个路由器的 IP 地址, 同时也通过 ARP 寻址找到下一个设备的 MAC 地址. 找到后目的 MAC 地址修改为这个 MAC 地址, 随后进行发送
- 中间过程: 后续过程同理…中间的路由器不断进行路由分析, ARP 寻址, 一个一个的找路由器进行转发, 直到找到目标主机
- 目标主机接收到后, 发现 MAC 与 IP 一致, 如果是TCP那么就查看一下序号是否匹配. 随后, 通过端口找到应用程序, 应用程序进行处理, 随后封装响应, 返回响应, 返回过程和传过来的过程一致.
- 返回响应后: 浏览器收到响应, 解析界面, 展示
下面是一个简略版本的图示, 可以用于参考记忆