文章目录
- HTTP
- HTTP 是什么?
- 理解“应用层协议”
- 理解 HTTP 协议的⼯作过程
- HTTP 协议格式
- 抓包⼯具的使用
- 抓包⼯具的原理
- 抓包结果
- 协议格式总结
- HTTP 请求(Request)
- 认识 URL
- URL 的基本格式
- 关于URL encode
- 认识“⽅法”(method)
- GET ⽅法
- POST 方法
- 【面试题】GET 和 POST 的区别
- 补充说明
- 其他⽅法
- 认识请求“报头”(header)
- HOST
- Content-Length
- Content-Type
- User-Agent(简称 UA)
- Referer
- Cookie
- 认识请求“正⽂”(body)
- HTTP 响应详解
- 认识“状态码”(status code)
- 200 OK
- 404 Not Found
- 403 Forbidden
- 405 Method Not Allowed
- 500 Internal Server Error
- 504 Gateway Timeout
- 302 Move temporarily
- 301 Moved Permanently
- 认识响应“报头”(header)
- Content-Type
- 认识响应“正文”(body)
- 通过 form 表单构造 HTTP 请求
- form 发送 GET 请求
- form 发送 POST 请求
- 通过 ajax 构造 HTTP 请求
- 发送 GET 请求
- 发送 POST 请求
- 通过 Java socket 构造 HTTP 请求
- HTTPS
- HTTPS 是什么?
- 臭名昭著的“运营商劫持”
- 为什么运营商要进行劫持?
- “加密”是什么?
- HTTPS 的工作过程
- 对称加密
- 非对称加密
- 中间人攻击**(Man-in-the-Middle Attack, MITM)**
- 问题背景:
- 攻击过程:
- 关键问题:
- 数字证书
- 数字签名
- 通过证书解决中间⼈攻击
- 中间⼈有没有可能篡改证书?
- 中间⼈有没有可能整个掉包证书?
- 常见问题
- 为什么摘要内容在⽹络传输的时候⼀定要加密形成签名?
- 为什么签名不直接加密,⽽是要先 hash 形成摘要?
- 完整流程
- 总结
- HTTPS ⼯作过程中涉及到的三组密钥:
- 为什么需要三组密钥?
HTTP
HTTP 是什么?
HTTP(全称为“超⽂本传输协议”)是⼀种应用非常⼴泛的应用层协议。
HTTP 诞⽣与1991年,⽬前已经发展为最主流使用的⼀种应用层协议。最新的 HTTP 3 版本也正在完善中,⽬前Google/Facebook等公司的产品已经⽀持了。
HTTP 往往是基于传输层的 TCP 协议实现的,(HTTP1.0、HTTP1.1、HTTP2.0、均为TCP,HTTP3 基于 UDP 实现)。
⽬前我们主要使用的还是 HTTP1.1 和 HTTP2.0。当前讨论的 HTTP 以 1.1 版本为主。
我们平时打开⼀个⽹站,就是通过 HTTP 协议来传输数据的。
当我们在浏览器中输⼊⼀个 搜狗搜索的 “⽹址”(URL)时,浏览器就给搜狗的服务器发送⼀个 HTTP 请求,搜狗的服务器返回⼀个 HTTP 响应。
这个响应结果被浏览器解析之后,就展⽰成我们看到的⻚⾯内容。(这个过程中浏览器可能会给服务器发送多个 HTTP 请求,服务器会对应返回多个响应,这些响应⾥就包含了⻚⾯ HTML、CSS、JavaScript、图⽚、字体等信息)。
所谓“超⽂本”的含义,就是传输的内容不仅仅是⽂本(⽐如 html、css 就是⽂本),还可以是⼀些其他的资源,⽐如图⽚、视频、⾳频等⼆进制的数据。
理解“应用层协议”
我们知道,数据能从客⼾端进程经过路径选择跨⽹络传送到服务器端进程[IP+Port]。
可是,仅仅把数据从A点传送到B点就完了吗?
这就好⽐,在淘宝上买了⼀部⼿机,卖家[客⼾端]把⼿机通过顺丰[传送+路径选择]送到买家[服务器]⼿⾥就完了吗?
当然不是,买家还要使用这款产品,还要在使用之后,给卖家打分评论。
所以,我们把数据从A端传送到B端,TCP/IP解决的是顺丰的功能,⽽两端还要对数据进⾏加⼯处理或者使用,所以我们还需要⼀层协议,不关⼼通信细节,关⼼应用细节!这层协议叫做应用层协议。
⽽应用是有不同的场景的,所以应用层协议是有不同种类的,其中经典协议之⼀的HTTP就是其中的佼佼者。
再回到刚刚买⼿机的例⼦,顺丰相当于 TCP/IP 的功能,那么买回来的⼿机都附带了说明书【产品介绍,使用介绍,注意事项等】,⽽该说明书指导用⼾该如何使用⼿机【虽然我们都不看,但是⽗⺟辈有部分是有看说明书的习惯)】,此时的说明书可以理解为用⼾层协议。
理解 HTTP 协议的⼯作过程
当我们在浏览器中输⼊⼀个“⽹址”,此时浏览器就会给对应的服务器发送⼀个 HTTP 请求,对⽅服务器收到这个请求之后,经过计算处理,就会返回⼀个 HTTP 响应。
事实上,当我们访问⼀个⽹站的时候,可能涉及不⽌⼀次的HTTP 请求/响应`的交互过程。可以通过 chrome 的开发者⼯具观察到这个详细的过程。
通过F12`打开 chrome 的开发者⼯具,切换到 Network 标签⻚,然后刷新⻚⾯即可看到如下图效果,每⼀条记录都是⼀次 HTTP 请求/响应。
注意:当前 搜狗主⻚ 是通过 https 来进⾏通信的。https 是在 http 基础之上做了⼀个加密解密的⼯作。
HTTP 协议格式
HTTP 是⼀个⽂本格式的协议,可以通过 Chrome 开发者⼯具或者 Fiddler 抓包,分析HTTP 请求/响应`的细节。
抓包⼯具的使用
可以使用Fiddler`。
- 左侧窗⼝显⽰了所有的 HTTP请求/响应,可以选中某个请求查看详情
- 右侧上⽅显⽰了 HTTP 请求的报⽂内容,(切换到 Raw 标签⻚可以看到详细的数据格式)
- 右侧下⽅显⽰了 HTTP 响应的报⽂内容,(切换到 Raw 标签⻚可以看到详细的数据格式)
- 请求和响应的详细数据,可以通过右下⻆的View in Notepad通过记事本打开
可以使用ctrl + a全选左侧的抓包结果,delete键清除所有被选中的结果。
抓包⼯具的原理
Fiddler 相当于⼀个“代理”。
浏览器访问sogou.com时,就会把 HTTP 请求先发给 Fiddler,Fiddler 再把请求转发给 sogou 的服务器。当 sogou 服务器返回数据时,Fiddler 拿到返回数据,再把数据交给浏览器。因此 Fiddler 对于浏览器和 sogou 服务器之间交互的数据细节都是非常清楚的。
代理就可以简单理解为⼀个跑腿⼩弟,你想买罐冰阔落,⼜不想⾃⼰下楼去超市,那么就可以把钱给你的跑腿⼩弟,跑腿⼩弟来到超市把钱给超市⽼板,再把冰阔落拿回来交到你⼿上。
这个过程中,这个跑腿⼩弟对于“你”和“超市⽼板”之间的交易细节是非常清楚的。
抓包结果
以下是⼀个 HTTP请求/响应 的抓包结果。
HTTP请求:
- ⾸⾏:[⽅法] + [url] + [版本]`
- Header:请求的属性(每一行用一组键值对表示,键值对用冒号分隔);每组属性之间使用\n分隔;遇到空⾏表⽰Header部分结束
- Body:空⾏后⾯的内容都是Body,Body允许为空字符串。如果Body存在,则在Header中会有⼀个Content-Length属性来标识Body的⻓度。
HTTP响应:
- ⾸⾏:[版本号] + [状态码] + [状态码解释]`
- Header:请求的属性(每一行用一组键值对表示,键值对用冒号分隔);每组属性之间使用\n分隔;遇到空⾏表⽰Header部分结束
- Body:空⾏后⾯的内容都是Body,Body允许为空字符串。如果Body存在,则在Header中会有⼀个Content-Length属性来标识Body的⻓度;如果服务器返回了⼀个html⻚⾯,那么html⻚⾯内容就是在body中。
协议格式总结
思考问题:为什么 HTTP 报⽂中要存在“空⾏”?
因为 HTTP 协议并没有规定报头部分的键值对有多少个。
空⾏就相当于是“报头的结束标记”,或者是“报头和正⽂之间的分隔符”。
HTTP 在传输层依赖 TCP 协议,TCP 是⾯向字节流的。如果没有这个空⾏,就会出现“粘包问题”。
粘包问题:指在基于流的协议(如 TCP)中,接收方收到的数据不是按照发送方发送的消息边界来划分的,而是多个消息粘在一起,或者一个消息被分割成多个部分接收。
HTTP 请求(Request)
认识 URL
URL 的基本格式
平时我们俗称的“⽹址”其实就是说的 URL(Uniform Resource Locator 统⼀资源定位符)。
互联⽹上的每个⽂件都有⼀个唯⼀的URL,它包含信息指出⽂件的位置以及浏览器应该怎么处理它。
URL 的详细规则由 因特⽹标准RFC1738 进⾏了约定(https://datatracker.ietf.org/doc/html/rfc1738](https://datatracker.ietf.org/doc/html/rfc1738))。
⼀个具体的 URL:https://v.bitedu.vip/personInf/student?userId=10000&classId=100`
可以看到,在这个 URL 中有些信息被省略了:
https
:协议⽅案名。常⻅的有 http 和 https,也有其他的类型(例如访问 mysql 时用的jdbc:mysql
)。user:pass
:登陆信息。现在的⽹站进⾏⾝份认证⼀般不再通过 URL 进⾏了,⼀般都会省略。v.bitedu.vip
:服务器地址。此处是⼀个“域名”,域名会通过 DNS 系统解析成⼀个具体的 IP 地址(通过 ping 命令可以看到,v.bitedu.vip
的真实 IP 地址为118.24.113.28
)。端⼝号
:上⾯的 URL 中端⼝号被省略了。当端⼝号省略的时候,浏览器会根据协议类型⾃动决定使用哪个端⼝。例如 http 协议默认使用 80 端⼝,https 协议默认使用 443 端⼝/personInf/student
:带层次的⽂件路径。userId=10000&classId=100
:查询字符串(query string)。本质是⼀个键值对结构,键值对之间使用 & 分隔,键和值之间使用=分隔。⽚段标识
:此 URL 中省略了⽚段标识。⽚段标识主要用于⻚⾯内跳转,通过不同的⽚段标识跳转到⽂档的不同章节,当加载页面时会自动定位到相应区域。
URL 中的可省略部分:
- 协议名:可以省略,省略后默认为
http://
。 - ip地址/域名:在 HTML 中可以省略(⽐如 img、link、script、a标签的 src 或者 href 属性)。省略后表⽰服务器的ip/域名与当前 HTML 所属的ip/域名`⼀致。
- 端⼝号:可以省略。省略后如果是 http 协议,端⼝号⾃动设为 80;如果是 https 协议,端⼝号⾃动设为 443。
- 带层次的⽂件路径:可以省略。省略后相当于/。有些服务器会在发现/路径的时候⾃动访问/index.html`。
- 查询字符串:可以省略
- ⽚段标识:可以省略
关于URL encode
像/ ? :等这样的字符,已经被url当做特殊意义理解了,因此这些字符不能随意出现。
⽐如,某个参数中需要带有这些特殊字符时,就必须先对特殊字符进⾏转义。
⼀个中⽂字符由 UTF-8 或者 GBK 这样的编码⽅式构成,虽然在 URL 中没有特殊含义,但是仍然需要进⾏转义。否则浏览器可能把 UTF-8/GBK 编码中的某个字节当做 URL 中的特殊符号。
转义的规则:将需要转码的字符转为16进制,然后从右到左,取4位(不⾜4位直接处理),每2位做⼀位,前⾯加上%,编码成%XY格式。
在线 URL 编码解码
例如:“C++”被转义成了“C%2B%2B”
认识“⽅法”(method)
GET ⽅法
GET 是最常用的 HTTP ⽅法,大部分场景使用的都是 GET 方法。
常用于获取服务器上的某个资源。例如在浏览器中直接输⼊ URL,此时浏览器就会发送出⼀个 GET 请求。另外,HTML 中的 link、img、script等标签,也会触发 GET 请求。
使用 Fiddler 观察 GET 请求:
打开 Fiddler,访问 搜狗主⻚,观察抓包结果。
从上⾯的结果可以看出:
,说明是通过浏览器地址栏发送的 GET 请求。
- 和 sogou 域名相关的请求
- 通过 html 中的link、script、img 标签产⽣的
2. <font style="color:#000000;">通过 ajax 的⽅式产⽣的</font>
GET 请求的特点:
- ⾸⾏的第⼀部分为 GET
- URL 的 query string 可以为空,也可以不为空
- header 部分有若⼲个键值对结构
- body 部分为空
关于 GET 请求的 URL ⻓度问题
⽹上有些资料上描述:get 请求⻓度最多1024kb,这样的说法是错误的。
HTTP 协议由 RFC 2616 标准定义,标准原⽂中明确说明:“Hypertext Transfer Protocol – HTTP/1.1 does not specify any requirement for URL length`”。没有对 URL 的⻓度有任何的限制。
实际 URL 的⻓度取决于浏览器的实现和 HTTP 服务器端的实现。在浏览器端,不同的浏览器最⼤⻓度是不同的,但是现代浏览器⽀持的⻓度⼀般都很⻓;在服务器端,⼀般这个⻓度是可以配置的。
POST 方法
POST ⽅法也是⼀种常⻅的⽅法。常用于提交用⼾输⼊的数据到服务器(例如登陆时要输入的用户名和密码到服务器)。
通过 HTML 中的 form 标签可以构造 POST 请求,或者使用 JavaScript 的 ajax 也可以构造 POST 请求。
POST 请求的特点:
- ⾸⾏的第⼀部分为 POST
- URL 的 query string ⼀般为空(也可以不为空)
- header 部分有若⼲个键值对结构
- body 部分⼀般不为空。
- body 内的数据格式通过 header 中的Content-Type`指定
- body 的⻓度由 header 中的Content-Length`指定
【面试题】GET 和 POST 的区别
- 语义不同:GET ⼀般用于获取数据;POST ⼀般用于提交数据。
- GET 的 body ⼀般为空,需要传递的数据通过query string传递;POST 的query string`⼀般为空,需要传递的数据通过 body 传递。
- GET 请求⼀般是幂等的,POST 请求⼀般是不幂等的。(如果对同一个 URL 进行多次请求,得到的结果⼀样,就视为该请求是幂等的)。
- GET 可以被缓存,POST 不能被缓存。(这⼀点也是承接幂等性)
补充说明
- 关于语义:GET 完全可以用于提交数据,而 POST 也完全可以用于获取数据。
- 关于幂等性:
- 标准建议 GET 实现为幂等的,但在实际开发中 GET 也不必完全遵守这个规则。
- 主流⽹站都有“猜你喜欢”功能,会根据用⼾的历史⾏为实时更新现有的结果。
- 关于安全性:
- 有些资料上说“POST ⽐ GET 更安全”,这样的说法是不科学的。
- 是否安全取决于前端在传输密码等敏感信息时是否进⾏加密,和 GET 或 POST ⽆关。
- 关于传输数据量:
- 有的资料上说“GET 传输的数据量⼩,而 POST 传输数据量⼤”,这个也是不科学的。
- 标准没有规定 GET 的 URL 的⻓度,也没有规定 POST 的 body 的⻓度。传输数据量多少,完全取决于不同浏览器和不同服务器之间的实现区别。
- 关于传输数据类型:
- 有的资料上说“GET 只能传输⽂本数据,而 POST 可以传输⼆进制数据”,这个也是不科学的。
- GET 的query string虽然⽆法直接传输⼆进制数据,但是可以针对⼆进制数据进⾏url encode`。
其他⽅法
- PUT:和 POST 相似,只是具有幂等特性,⼀般用于更新数据
- DELETE:删除服务器指定资源
- OPTIONS:返回服务器所⽀持的请求⽅法
- HEAD:类似于GET,只不过响应体不返回,只返回响应头
- TRACE:回显服务器端收到的请求,测试的时候会用到这个
- CONNECT:预留,暂⽆使用
这些⽅法的 HTTP 请求可以使用 ajax 来构造,也可以通过⼀些第三⽅⼯具来构造。
任何⼀个能进⾏⽹络编程的语⾔都可以构造 HTTP 请求,本质上就是通过 TCP socket 写⼊⼀个符合 HTTP 协议规则的字符串。
认识请求“报头”(header)
header 的整体的格式也是“键值对”结构,每个键值对占⼀⾏,键和值之间使用冒号分割。
HOST
表⽰服务器主机的地址和端⼝。
Content-Length
表⽰ body 中的数据⻓度,单位是字节。body 中的数据有多长,在空行之后就读取多长(以字节为单位)。
Content-Type
表⽰请求的 body 中的数据格式
常见选项:
- application/x-www-form-urlencoded`:form 表单提交的数据格式,此时 body 的格式形如:
title=test&content=hello
- multipart/form-data:form 表单提交的数据格式,在 form 标签中加上enctyped=“multipart/form-data`” ,通常用于提交图⽚/⽂件。
- application/json`:数据为 json 格式。body 格式形如:
{"username":"123456789","password":"xxxx","code":"jw7l","uuid":"d110a05ccde64b16
User-Agent(简称 UA)
表⽰浏览器/操作系统的属性。形如:Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Safari/537.36`。
其中:
- Windows NT 10.0; Win64; x64`:表⽰操作系统信息
- AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Safari/537.36`:表示浏览器信息
User-Agent 之所以是这个样⼦的故事:https://zhuanlan.zhihu.com/p/398807396
Referer
表⽰这个⻚⾯是从哪个⻚⾯跳转过来的。形如https://v.bitedu.vip/login
如果直接在浏览器中输⼊URL,或者直接通过收藏夹访问⻚⾯,是没有 Referer 的。
Cookie
Cookie 中存储了⼀个字符串,这个数据可能是客⼾端(⽹⻚)⾃⾏通过** JS **写⼊的,也可能来⾃于服务器(服务器在 HTTP 响应的 header 中通过 Set-Cookie 字段给浏览器返回数据)。
往往可以通过这个字段实现**“⾝份标识”**的功能。
每个不同的域名下都可以有不同的 Cookie,不同⽹站之间的 Cookie 并不冲突。
两种设置 cookie 的方法:
- 通过 header 中的 set-cookie : value
- 通过 JS 设置
可以通过抓包观察⻚⾯登陆的过程(以码云为例):
为了⽅便观察,先清除掉之前登陆的cookie
登录请求:
POST https://gitee.com/login HTTP/1.1
Host: gitee.com
Connection: keep-alive
Content-Length: 469
Cache-Control: max-age=0
sec-ch-ua: "Google Chrome";v="131", "Chromium";v="131", "Not_A Brand";v="24"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Origin: https://gitee.com
Content-Type: application/x-www-form-urlencoded
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: https://gitee.com/login?redirect_to_url=%2F
Accept-Encoding: gzip, deflate, br, zstd
Accept-Language: zh-CN,zh;q=0.9
Cookie: sl-session=YjUMb+dNl2c2oT31KSVrAg==; BEC=1f1759df3ccd099821dcf0da6feb0357; user_locale=zh-CN; oschina_new_user=false; csrf_token=WhkVjDobjyElZTyZRNNlQs5Ub5lQWzJNv9aBTwAZAl0PWaFKQJE3f9vkOrV3NvXY%2FtlNvmxmGGiStyHni%2BTPXQ%3D%3D; Hm_lvt_24f17767262929947cc3631f99bfd274=1737882731; HMACCOUNT=226DE9A22207DD16; user_return_to_0=%2F; tz=Asia%2FShanghai; Hm_lpvt_24f17767262929947cc3631f99bfd274=1737882747; gitee-session-n=UWFPcmRMc0VaRkRvbnFyYldLamNoWTZpNFZmRUkwajdYNG9VTVJxUzVzbmxhZVVQeVljK3BlaEMyZDR4ZlQ2RE1PeExQZjBXNSthc3hKb0lZMGtpSklTVzBHKy9RUDhyd1gxQ1gxL3J0Nll2QlJZRG5FT1lZVmlEV0NpNXRZN285K2VHSnlXZ2o2RGJ1Qmo0cW1IZFR1b2lyT21rZW5hSm84U0ZOL3JGZnFXck0ra3RINk5qZ2RyMHM3NElGdkFlVVlRLzkrSnlFUlluSkRRRkdUSGRGR2ZoM3pYVUVkbE5ReGtWQ0E2bzR0L2cwaWdGQUlXOG0vSE1xdFY4c00wdDJYcksxTDJiSktuUnFCQllFOHdQSlNLU0tyNzVrMG5ydU5zSVM5bXJmblp2MFpJdzZEYjlWTjFBcHJZamhzeitkRFdBdmJHaElYMVB0Sjc4Sit6RlJ4VUtzeW1VVjRoWUM4SWVvTDdBcXNqTGFzSmxQOGJoVFQreWdGY3RVWVp3MWpWVi9mUFNaQ0xkdnFCWGk2eEtnZnJIVnNaNDlzTllWNGhoeXFqUGFGOTRpT0sySXdQUENJa1dQZm10cGFPSy0tSDhIQU5vQkFjTFRnWTJRUW1hNEtXUT09--87b91eeda961ed990b7c9a44221f01e7a660094dencrypt_key=password&utf8=%E2%9C%93&authenticity_token=sDyyd%2FimLTHdD26Ni%2BxGY8MI2a1XIBqCVwfpsZFJQBTlfAaxgiyVbyOOaKG4Cdb584X7imsdMKd6ZkkZGrSNFA%3D%3D&redirect_to_url=%2F&user%5Blogin%5D=13319213575&encrypt_data%5Buser%5Bpassword%5D%5D=l5f2zGOBSv9q8cn7H5AAXf2kNzEBt6cYPfRSQcrGn67BVZP1MeAG%2FPv5Sf2oQO2uYdFL3jsBLxPBjNQExihugahvSotnvEqkq%2FOgSKV8aG%2FeDSLjrNrpDZeltsQXrdcjSOj9S%2BupgaBUnHhbmN2IauByoBDzBQ7qE4lOzO%2FIScY%3D&user%5Bremember_me%5D=0&user%5Bremember_me%5D=1
登录响应:
HTTP/1.1 302 Found
Server: ADAS/1.0.201
Date: Sun, 26 Jan 2025 09:13:04 GMT
Content-Type: text/html; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
X-UA-Compatible: chrome=1
Expires: Sun, 1 Jan 2000 01:00:00 GMT
Pragma: must-revalidate, no-cache, private
Location: https://gitee.com/
Cache-Control: no-cache
Set-Cookie: remember_user_token=BAhbCFsGaQMT%2FJxJIiIkMmEkMTAkS0xaZElLRGFQQVI1bXlsMko3emRVdQY6BkVUSSIXMTczNzg4Mjc4NC4wMDg1NTA0BjsARg%3D%3D--1c69603eb8568732c118d1732f639a8631bfa2ba; domain=.gitee.com; path=/; expires=Sun, 09 Feb 2025 09:13:04 -0000; HttpOnly
Set-Cookie: oschina_new_user=false; path=/; expires=Thu, 26 Jan 2045 09:13:04 -0000
Set-Cookie: gitee_user=true; path=/
Set-Cookie: gitee-session-n=Tjc2L0g4dHlXOFg1RCt4R2M5ODZMcG5iVFovNTFPWWwvZnpyaXRmVm15a2t5bFIwZmF5bzZGdVZsQW9kRVBMcXNuRS9jbFlzNW11SE1aNzBDTXV4N2t5OUFOcDBoNU1kTnVzQnhMSjZ4OEJlR1gxd1B1Rnc0UmZyc284bFRHK0ZNZVQvZzdsWWxYcjM0cEZyK0pYelJUa3JRbU14Y2Jjd3NRTmtLc25zZ1ROU0NxbEpzZGNGMzlUbzBHL1Y4aDNXemRTejdiYS9CRlpMWmVKT3pWQURmMUM5RXVRUC9UWmJzWmhXL1hBMjljZm5TV0VUYzg1aGRnVEVnZnNwV3daOVJ3NjMrclNoR1Rnc0VRcXBBQ05HcWZFRnV4ZU5IWDJZWDdOYUxmaGt0K3N2eEF3ZStZMk9OR1VmTzlUM05PQUVMRzFJWHY0bDJwZXZlb28zSmZGcHF1djV6RHkwK28rRkVxZnh4aXAwa3QrdUU0T0JCOWtFY3VaSitORmtMYzJzdm5GRTBXOEttc0w5UnFiRWJkSHRRd0xhczlXVzJpSHhYRVJiZ3dxLy9rcHhUV3BqZFlJVzE0elNsMFVhblQ5SXZDVkM2aFgrb0VWUy8xYnZMQ1ZSejJPYkk5WHBYOGJsaDhwOCt3NnF3eGVDcHZGbndHL1h6QW9Td29iZGdjYmZ4ZHZoN25tcDVvdWJXWkV3NDFHbXQwWkhkbkczTnZhOEdPdCtLdVV6SFZrPS0ta2c0UFBlUEpuNTJiSW1BME5qOWp1QT09--1c7039d7956c70ec66fcd8ee2b3cfea8abf5923a; domain=.gitee.com; path=/; HttpOnly
X-Request-Id: 3598daec54adba02e490c22e05061300
X-Runtime: 0.176103
Vary: Origin
X-Frame-Options: SAMEORIGIN
Content-Security-Policy: frame-ancestors 'self' https://*.gitee.com54
<html><body>You are being <a href="https://gitee.com/">redirected</>.</body></html>
0
可以看到,响应中包含了 4 个 Set-Cookie 属性,其中我们重点关注第四个,⾥⾯包含了⼀个gitee-session-n
这样的属性,属性值是⼀串很⻓的加密之后的信息。这个信息就是用⼾当前登陆的⾝份标识,也称为“令牌(token)”。
登陆成功之后,后续访问码云的其他⻚⾯,请求中就都会带着刚才获取到的 Cookie 信息。请求中的 Cookie 字段也包含了⼀个gitee-session-n
属性,⾥⾯的值和刚才服务器返回的值相同。后续只要访问 gitee 这个⽹站,就会⼀直带着这个令牌,直到令牌过期/下次重新登陆。
登陆过程如下:
这个过程和去医院看病很相似:
- 到了医院先挂号。挂号时候需要提供⾝份证,同时得到了⼀张“就诊卡”,这个就诊卡就相当于患者的“令牌”;
- 后续去各个科室进⾏检查、诊断、开药等操作,都不必再出⽰⾝份证了,只要凭就诊卡即可识别出当前患者的⾝份;
- 看完病了之后,不想要就诊卡了,就可以注销这个卡。此时患者的⾝份和就诊卡的关联就销毁了(类似于⽹站的注销操作、删除 cookie 操作);
- 再次看病时,可以办⼀张新的就诊卡,此时就得到了⼀个新的“令牌”。
认识请求“正⽂”(body)
正⽂中的内容格式和 header 中的Content-Type
密切相关,可以通过抓包来观察这⼏种情况。
application/x-www-form-urlencoded
multipart/form-data
application/json
HTTP 响应详解
认识“状态码”(status code)
状态码表⽰访问⼀个⻚⾯的结果(是访问成功,还是访问失败,或者是其他的⼀些情况……)。
200 OK
这是⼀个最常⻅的状态码,表⽰访问成功。抓包抓到的⼤部分结果都是 200。
**注意:**在抓包观察响应数据的时候,可能会看到压缩之后的数据,形如:
⽹络传输中“带宽”是⼀个稀缺资源,为了传输效率更⾼往往会对数据进⾏压缩。点击 Fiddler 中的即可进⾏解压缩,看到原始的内容。
404 Not Found
表示没有找到资源。
浏览器输⼊⼀个 URL,⽬的就是为了访问对⽅服务器上的⼀个资源,如果这个 URL 标识的资源不存在,那么就会出现 404。
403 Forbidden
表⽰访问被拒绝。
有的⻚⾯通常需要用⼾具有⼀定的权限才能访问(登陆后才能访问),如果用⼾没有登陆直接访问,就会出现 403。
405 Method Not Allowed
HTTP 中所⽀持的⽅法,有 GET、POST、PUT、DELETE 等,但是对⽅的服务器不⼀定都⽀持所有的⽅法(或者不允许用⼾使用⼀些其他的⽅法),这时会出现 405。
500 Internal Server Error
服务器出现内部错误。⼀般是服务器的代码执⾏过程中遇到了⼀些特殊情况(服务器异常崩溃)会产⽣这个状态码。
504 Gateway Timeout
当服务器负载⽐较⼤的时候,服务器处理单条请求的时候消耗的时间就会很⻓,就可能会导致出现超时的情况。
302 Move temporarily
临时重定向。重定向类似于“呼叫转移”。
在登陆⻚⾯中经常会⻅到 302,用于实现登陆成功后⾃动跳转到主⻚。响应报⽂的 header 部分会包含⼀个 Location 字段,表⽰要跳转到哪个⻚⾯。
301 Moved Permanently
永久重定向。当浏览器收到这种响应时,后续的请求都会被⾃动改成新的地址。
301 也是通过 Location 字段来表⽰要重定向到的新地址。
认识响应“报头”(header)
响应报头的基本格式和请求报头的格式基本⼀致。
类似于 Content-Type、Content-Length等属性的含义也和请求中的含义⼀致。
Content-Type
响应中的 Content-Type 常⻅取值有以下⼏种
text/html
:body 数据格式是 HTMLtext/css
:body 数据格式是 CSSapplication/javascript
:body 数据格式是 JavaScriptapplication/json
:body 数据格式是 JSON
认识响应“正文”(body)
正⽂的具体格式取决于 Content-Type。
通过 form 表单构造 HTTP 请求
form 表单是 HTML 中的⼀个常⽤标签,可以⽤于给服务器发送 GET 或者 POST 请求。
form 发送 GET 请求
form 的重要参数:
action
:构造的 HTTP 请求的 URL 是什么method
:构造的 HTTP 请求的⽅法是 GET 还是 POST(form 只⽀持 GET 和 POST)
input 的重要参数:
type
:表⽰输⼊框的类型。text 表⽰⽂本,password 表⽰密码,submit 表⽰提交按钮name
:表⽰构造出的 HTTP 请求的 query string 的 key。query string 的 value 就是输⼊框的⽤⼾输⼊的内容value
:input 标签的值。对于 type 为 submit 类型来说,value 就对应了按钮上显⽰的⽂本
<form action="http://abcdef.com/myPath" method="GET"><input type="text" name="userId"><input type="text" name="classId"><input type="submit" value="提交">
</form>
⻚⾯展⽰的效果:
在输⼊框随便填写数据:
点击“提交”,此时就会构造出 HTTP 请求并发送出去。
form 发送 POST 请求
修改上⾯的代码,把 form 的 method 修改为 POST
<form action="http://abcdef.com/myPath" method="POST"><input type="text" name="userId"><input type="text" name="classId"><input type="submit" value="提交">
</form>
⻚⾯效果不变。
主要的区别:
- method 从 GET 变成了 POST
- 数据从 query string 移动到了 body 中
通过 ajax 构造 HTTP 请求
从前端⻆度,除了浏览器地址栏能构造 GET 请求,form 表单能构造 GET 和 POST 之外,还可以通过 ajax 的⽅式来构造 HTTP 请求,并且功能更强⼤。
ajax 全称 Asynchronous Javascript And XML,是 2005 年提出的⼀种 JavaScript 给服务器发送 HTTP 请求的⽅式,特点是可以不需要 刷新⻚⾯/⻚⾯跳转 就能进⾏数据传输。
在 JavaScript 中可以通过 ajax 的⽅式构造 HTTP 请求。
发送 GET 请求
// 引⼊ jquery
<script src="https://code.jquery.com/jquery-3.6.3.min.js"></script>$.ajax({url: 'https://example.com/api/data', // 请求的目标地址method: 'GET', // HTTP 请求的方法// 向服务器传递参数// data 参数会自动将键值对转换为查询字符串并附加到 URL 上,// 例如:https://example.com/api/data?name=John&age=25。data: { name: 'John',age: 25},// 如果需要自定义请求头(如添加认证令牌)headers: { 'Authorization': 'Bearer your_token_here'},// 当请求成功时执行。success: function (data) {console.log('成功获取数据:', data);},// 当请求失败时执行error: function (xhr, status, error) {console.error('请求失败:', error);console.error('状态码:', xhr.status);console.error('响应内容:', xhr.responseText);}
});
浏览器和服务器交互过程(引⼊ ajax 后):
发送 POST 请求
对于 POST 请求,需要设置 body 的内容:
- 先使⽤ setRequestHeader 设置 Content-Type
- 再通过 send 的参数设置 body 内容
发送 application/x-www-form-urlencoded 数据:
// 引⼊ jquery
<script src="https://code.jquery.com/jquery-3.6.3.min.js"></script>$.ajax({url: 'https://example.com/api/data', // 请求地址method: 'POST', // 请求方法contentType: 'application/x-www-form-urlencoded', // 设置请求头,指定发送的数据为 URL 编码格式data: { // 要发送的数据(jQuery 会自动将其转换为 URL 编码格式)name: 'John',age: 25},success: function (data) {console.log('响应数据:', data);},error: function (xhr, status, error) {console.error('请求失败:', error);}
});
发送 application/json 数据:
// 引⼊ jquery
<script src="https://code.jquery.com/jquery-3.6.3.min.js"></script>$.ajax({url: 'https://example.com/api/data', // 请求地址method: 'POST', // 请求方法contentType: 'application/json', // 设置请求头,指定发送的数据为 JSON 格式// 要发送的数据,使用 JSON.stringify 将 JavaScript 对象转换为 JSON 字符串,确保数据格式正确data: JSON.stringify({ name: "John", age: 25 }),success: function (data) {console.log('响应数据:', data);},error: function (xhr, status, error) {console.error('请求失败:', error);}
});
通过 Java socket 构造 HTTP 请求
所谓的“发送 HTTP 请求”,本质上就是按照 HTTP 的格式往 TCP Socket 中写⼊⼀个字符串。
所谓的“接受 HTTP 响应”,本质上就是从 TCP Socket 中读取⼀个字符串,再按照 HTTP 的格式来解析。
我们基于 Socket 的知识,完全可以构造出⼀个简单的 HTTP 客⼾端程序,⽤来发送各种类型的 HTTP 请求。
package com.zxy;import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;public class HttpClient {private Socket socket;private String ip;private int port;public HttpClient(String ip, int port) throws IOException {this.ip = ip;this.port = port;socket = new Socket(ip, port);}public String get(String url) throws IOException {StringBuilder request = new StringBuilder();// 构造首行request.append("GET " + url + " HTTP/1.1\n");// 构造 headerrequest.append("Host: " + ip + ":" + port + "\n");// 构造空行request.append("\n");// GET 请求不需要 body, 就构造完毕了.OutputStream outputStream = socket.getOutputStream();// outputStream 是一个字节流. 以字节为单位进行写入. 因此需要把 StringBuilder 转换成 byte[]outputStream.write(request.toString().getBytes());// 读取响应InputStream inputStream = socket.getInputStream();// 搞一个 1M 大小的缓冲区, 用来存放响应数据byte[] buffer = new byte[1024 * 1024];// n 表示实际读到的字节数int n = inputStream.read(buffer);return new String(buffer, 0, n, "utf-8");}// post 的实现和刚才的 get 基本一致, 只是多了一步构造 bodypublic String post(String url, String body) throws IOException {StringBuilder request = new StringBuilder();// 构造首行request.append("POST " + url + " HTTP/1.1\n");// 构造 headerrequest.append("Host: " + ip + ":" + port + "\n");request.append("Content-Type: text/plain\n");request.append("Content-Length: " + body.getBytes().length + "\n");// 构造空行request.append("\n");// 构造 bodyrequest.append(body);// 发送请求OutputStream outputStream = socket.getOutputStream();outputStream.write(request.toString().getBytes());// 读取响应InputStream inputStream = socket.getInputStream();byte[] buffer = new byte[1024 * 1024];int n = inputStream.read(buffer);return new String(buffer, 0, n, "utf-8");}public static void main(String[] args) throws IOException {// 此时可以获取到 搜狗主⻚ 的 htmlHttpClient httpClient = new HttpClient("www.sogou.com", 80);String getResp = httpClient.get("/index.html");System.out.println(getResp);// String postResp = httpClient.post("/AjaxMockServer/info", "这是正文");// System.out.println(postResp);}
}
HTTPS
HTTPS 是什么?
HTTPS(HyperText Transfer Protocol Secure)是一种安全的超文本传输协议,用于在客户端和服务器之间进行加密通信,确保数据的安全性和隐私性。
HTTPS 是一个网络层协议,是在 HTTP 协议的基础上加了一个加密层。
臭名昭著的“运营商劫持”
下载⼀个 天天动听
未被劫持的效果,点击下载按钮,就会弹出天天动听的下载链接:
已被劫持的效果,点击下载按钮,就会弹出 QQ 浏览器的下载链接:
由于我们通过⽹络传输的任何的数据包都会经过运营商的⽹络设备(路由器、交换机等),那么运营商的⽹络设备就可以解析出你传输的数据内容,并进⾏篡改。
点击“下载按钮”,其实就是在给服务器发送了⼀个 HTTP 请求,获取到的 HTTP 响应其实就包含了该 APP 的下载链接。
运营商劫持之后,就发现这个请求是要下载天天动听,那么就⾃动的把交给用⼾的响应给篡改成“QQ浏览器”的下载地址了。
为什么运营商要进行劫持?
利益动人心。
其实不⽌运营商可以劫持,其他的⿊客也可以用类似的⼿段进⾏劫持,来窃取用⼾隐私信息,或者篡改内容。
试想⼀下,如果⿊客在用⼾登陆⽀付宝的时候获取到用⼾账⼾余额,甚⾄获取到用⼾的⽀付密码…
所以,在互联⽹上,明⽂传输是⽐较危险的事情!!!
HTTPS 就是在 HTTP 的基础上进⾏了加密,进⼀步的来保证用⼾的信息安全。
“加密”是什么?
- 加密就是把 明⽂(要传输的信息)进⾏⼀系列变换⽣成 密⽂;
- 解密就是把 密⽂ 再进⾏⼀系列变换,还原成 明⽂。
在这个加密和解密的过程中,往往需要⼀个或者多个中间的数据,辅助进⾏这个过程,这样的数据称为密钥。
83 版<<⽕烧圆明园>>中,有⼈要谋反⼲掉慈禧太后,恭亲王奕䜣给慈禧递的折⼦,折⼦内容只是扯⼀扯家常,套上⼀张挖了洞的纸就能看到真实要表达的意思。
明⽂:“当⼼肃顺、端华、戴恒”(这⼏个⼈都是当时的权⾂,后来被慈禧⼀锅端)
密⽂:奏折全⽂
密钥:挖了洞的纸
加密解密到如今已经发展成⼀个独⽴的学科:密码学,⽽密码学的奠基⼈,也正是计算机科学的祖师爷之⼀:艾伦·⻨席森·图灵。
HTTPS 的工作过程
既然要保证数据安全,就需要进⾏“加密”。⽹络传输中不再直接传输明⽂了,⽽是加密之后的“密⽂”。
加密的⽅式有很多,但是整体可以分成两⼤类:对称加密和非对称加密。
对称加密
对称加密其实就是通过同⼀个“密钥”,既能把明⽂加密成密⽂,也能把密⽂解密成明⽂。
⼀个简单的对称加密:按位异或。
假设明⽂ a = 1234,密钥 key = 8888,则加密 a ^ key 得到的密⽂ b 为 9834。
然后针对密⽂ 9834 再次进⾏运算 b ^ key,得到的就是原来的明⽂ 1234。
对于字符串的对称加密也是同理,每⼀个字符都可以表⽰成⼀个数字。
当然,按位异或只是最简单的对称加密,HTTPS 中并不是使用按位异或。
引⼊对称加密之后,即使数据被截获,由于⿊客不知道密钥是什么,因此⽆法进⾏解密,也就不知道请求的真实内容了。但事情没这么简单。
服务器同⼀时刻其实是给很多客⼾端提供服务的,这么多的客⼾端,每个⼈用的秘钥都必须是不同的(如果是相同那密钥就太容易扩散了,⿊客就也能拿到了)。因此,服务器就需要维护每个客⼾端和每个密钥之间的关联关系,这是个很⿇烦的事情。
⽐较理想的做法,就是能在客⼾端和服务器建⽴连接的时候,双⽅协商确定这次的密钥是什么。
但是如果直接把密钥明⽂传输,那么⿊客也就能获得密钥了,此后的加密操作就形同虚设了。
因此,密钥的传输也必须加密传输!
但是要想对密钥进⾏对称加密,就仍然需要先协商确定⼀个“密钥的密钥”,这就成了“先有鸡还是先有蛋”的问题了,此时密钥的传输再用对称加密就⾏不通了。所以,需要引入非对称加密。
非对称加密
非对称加密要用到两个密钥,⼀个叫做“公钥”,⼀个叫做“私钥”。公钥和私钥是配对的,最⼤的缺点就是运算速度非常慢,⽐对称加密要慢很多。
公钥与私钥:
- 公钥:公开发布的密钥,任何人都可以获取并用于加密数据。保存在客户端。
- 私钥:由用户单独保管的密钥,用于解密数据或生成数字签名。保存在用户端。
加密与解密:
- 使用公钥加密的数据,只能用对应的私钥解密。
- 使用私钥加密的数据,可以用对应的公钥解密。
- 公钥加密的密文不能用公钥解密。
非对称加密的数学原理⽐较复杂,涉及到⼀些数论相关的知识,这⾥举⼀个简单的⽣活上的例⼦:
A 要给 B ⼀些重要的⽂件,但是 B 可能不在,于是 A 和 B 提前做出约定:
- B 说:我桌⼦上有个盒⼦,然后我给你⼀把锁,你把⽂件放盒⼦⾥用锁锁上,然后我回头拿着钥匙来开锁取⽂件。
在这个场景中,这把锁就相当于公钥,钥匙就是私钥。公钥给谁都⾏(不怕泄露),但是私钥只有 B ⾃⼰持有,持有私钥的⼈才能解密。
- 客户端在本地生成一个随机的对称密钥,并通过服务器的公钥对其进行加密。加密后的密钥通过网络发送给服务器。
- 由于中间的⽹络设备没有私钥,即使截获了数据,也⽆法还原出内部的原⽂,也就⽆法获取到对称密钥;
- 服务器接收到加密的对称密钥后,使用自己的私钥解密,还原出客户端生成的对称密钥。随后使用该对称密钥加密返回给客户端的数据。客户端用相同的对称密钥解密服务器的响应数据。
- 后续客⼾端和服务器的通信都只用对称加密即可。由于该密钥只有客⼾端和服务器两个主机知道,其他主机/设备不知道密钥,即使截获数据也没有意义。
由于对称加密的效率⽐非对称加密⾼很多,因此**只是在开始阶段协商密钥的时候使用非对称加密,后续的传输仍然使用对称加密。**
那么接下来问题⼜来了:
- 客⼾端如何获取到公钥?
- 客⼾端如何确定这个公钥不是⿊客伪造的?
中间人攻击**(Man-in-the-Middle Attack, MITM)**
⿊客可以使用中间⼈攻击,获取到对称密钥。
问题背景:
- 非对称加密的基本原理:
- 服务器拥有公钥 S 和私钥 S′ 用于安全通信。
- 客户端需要通过服务器的公钥 S 来协商一个对称密钥 X,以便后续高效地进行对称加密通信。
- 攻击者的手段:
- 中间人(MITM)持有自己的公钥 M 和私钥 M′ 。
- 中间人劫持了客户端与服务器之间的通信,篡改了服务器的公钥 S ,用自己的公钥 M 替代。
攻击过程:
阶段 1:公钥替换
- 客户端请求服务器的公钥 S 。
- 服务器响应并发送公钥 S 。
- 中间人劫持该报文,提取公钥 S 并保存,然后将公钥 S 替换为自己的公钥 M ,并将伪造的报文发送给客户端。
阶段 2:对称密钥协商
- 客户端收到被篡改的公钥 M ,误以为这是服务器的公钥。
- 客户端生成对称密钥 X ,用公钥 M 加密后发送给服务器。
- 中间人再次劫持该报文,用自己的私钥 M′ 解密,成功获取对称密钥 X。
- 中间人用之前保存的服务器公钥 S 重新加密对称密钥 X,并将伪造的报文发送给服务器。
阶段 3:双向通信
- 服务器收到报文后,用自己的私钥 S′ 解密,得到对称密钥 X。
- 此时,客户端、中间人和服务器都认为双方正在使用对称密钥 X 进行通信。
- 然而,中间人已经完全掌控了整个通信过程:
- 客户端发往服务器的数据会被中间人解密、窃听甚至篡改后再转发给服务器。
- 服务器发往客户端的数据也会被中间人拦截并篡改。
关键问题:
- 为什么会出现这种漏洞?
- 核心问题是 公钥的信任问题。客户端无法验证接收到的公钥是否确实来自合法的服务器,从而被中间人利用。
- 如何防止中间人攻击?
- 引入 数字证书 和 证书颁发机构(CA):
- 服务器的公钥 S 被绑定到一个由可信 CA 签名的数字证书中。
- 客户端在接收公钥时,会验证数字证书的真实性(检查签名是否由可信 CA 提供)。
- 如果公钥被篡改为 M,则数字证书验证失败,通信中断。
- 引入 数字证书 和 证书颁发机构(CA):
- 实际应用中的解决方案:
- HTTPS 协议通过 TLS/SSL 实现安全通信,其中包含证书验证机制。
- 客户端(如浏览器)内置了一组受信任的 CA 列表,用于验证服务器证书的合法性。
数字证书
服务端在使⽤ HTTPS 前,需要向 CA 机构申领⼀份数字证书,数字证书⾥含有证书申请者信息、公钥信息等。服务器把证书传输给浏览器,浏览器从证书⾥获取公钥就⾏了,证书就如⾝份证,证明服务端公钥的权威性。
证书可以理解成是⼀个结构化的字符串,⾥⾯包含了以下信息:
- 证书发布机构
- 证书有效期
- 公钥
- 证书所有者
- 签名
- …
需要注意的是:申请证书的时候,需要在特定平台⽣成查,会同时⽣成⼀对⼉密钥对⼉,即公钥和私钥。这对密钥对⼉就是⽤来在⽹络通信中进⾏明⽂加密以及数字签名的。
数字签名
签名的形成是基于⾮对称加密算法的。注意,⽬前暂时和 https 没有关系,不要和 https 中的公钥私钥搞混了。
**当服务端申请 CA 证书的时候,CA 机构会对该服务端进⾏审核,并专⻔为该⽹站形成数字签名。**过程如下:
- CA 机构拥有一对⾮对称密钥:私钥 A 和公钥 A’。
- 私钥 A 用于生成数字签名。
- 公钥 A’ 则被广泛分发给客户端,用于验证签名。
- CA 机构对服务端申请的证书明⽂数据进⾏ hash,形成数据摘要。
- 然后 CA 使用私钥 A 对数据摘要进行加密,生成数字签名 S。
服务端申请的证书明⽂和数字签名 S 共同组成了数字证书,这样⼀份数字证书就可以颁发给服务端了。
- 数字签名的主要目的是:
- 确保证书内容未被篡改(数据完整性)。
- 验证证书是由可信的 CA 颁发的(身份认证)。
- 数据摘要:通过哈希函数对原始数据进行处理后生成的一段固定长度的字符串。它具有
- 唯一性:同样的原始字符串,通过相同的哈希函数(如 md5运算) 后,得到的值都是一样的。
- 不可逆性:数据摘要是一个单向过程,无法从摘要反推出原始数据。
通过证书解决中间⼈攻击
在客⼾端和服务器刚⼀建⽴连接的时候,服务器给客⼾端返回⼀个证书,这个证书包含了刚才的公钥,也包含了⽹站的⾝份信息。
当客⼾端获取到这个证书之后,会对证书进⾏校验(防⽌证书是伪造的):
- 判定证书的有效期是否过期:
- 客户端首先检查证书中的有效期字段(Not Before 和 Not After),确认当前时间是否在有效期内。
- 如果证书已过期或尚未生效,则直接判定为无效证书,终止通信。
- 判定证书的发布机构是否受信任:
- 操作系统中已内置的受信任的证书发布机构。
- 客户端检查证书中指定的 CA(证书颁发机构)是否属于操作系统或浏览器内置的可信 CA 列表。
- 如果 CA 不在可信列表中,则认为该证书不可信,可能是伪造的。
- 验证证书是否被篡改:
- 获取 CA 的公钥:
- 客户端从系统中提取与证书中指定 CA 对应的公钥
- 解密签名得到 hash1
- 使用 CA 的公钥解密证书中的签名部分,得到一个 hash 值(称为数据摘要),设为 hash1。
- 计算证书内容的 hash2
- 客户端对证书的内容(不包含签名部分)重新进行哈希运算,得到另一个哈希值,记为 hash2。
- 对比 hash1 和 hash2
- 如果相等,则说明证书没有被篡改过。
- 如果不相等,则说明证书已被篡改,证书不可信。
- 获取 CA 的公钥:
查看浏览器的受信任证书发布机构:
Chrome 浏览器,点击右上⻆的:
选择“设置”,搜索“证书管理”,即可看到以下界⾯:
中间⼈有没有可能篡改证书?
中间人有可能篡改证书的明文内容,但由于缺乏 CA 的私钥,他们无法生成匹配的签名。客户端在验证签名时会发现篡改行为,并拒绝该证书,从而保护通信安全。
假设中间人试图篡改证书的明文内容(如修改公钥或持有者信息),以下是可能发生的情况:
- 中间人没有 CA 的私钥,因此无法重新计算签名。
- 如果中间人强行篡改证书内容而不更新签名,客户端在验证签名时会发现:
- 解密后的签名与证书内容的哈希值不匹配。
- 客户端判定该证书已被篡改,不可信。
- 当客户端检测到证书签名无效时,会采取以下措施:
- 终止通信:客户端不会继续与服务器建立连接,防止潜在的信息泄露。
- 提示用户:浏览器或应用程序可能会弹出警告,提示用户证书不可信。
中间⼈有没有可能整个掉包证书?
- 由于中间⼈没有 CA 私钥,所以⽆法制作假的证书。
- 所以中间⼈只能向 CA 申请真证书,然后⽤⾃⼰申请的证书进⾏掉包。
- 这个确实能做到证书的整体掉包,但是,证书明⽂中包含了域名等服务端认证信息,如果整体掉包,客⼾端依旧能够识别出来。
中间⼈没有 CA 私钥,所以无法通过合法方式伪造或修改数字证书。
为什么中间人无法制作假证书?
- 数字证书的核心是 CA 的签名,它确保了证书的真实性和完整性。
- **签名过程需要使用 CA 的私钥对证书明文的数据摘要进行加密。**由于中间人无法获取 CA 的私钥,他们无法生成合法的签名。
- 如果中间人尝试伪造签名,客户端在验证时会发现签名无效(解密后与证书内容的哈希值不匹配),从而拒绝该证书。
常见问题
为什么摘要内容在⽹络传输的时候⼀定要加密形成签名?
常⻅的摘要算法有:MD5 和 SHA 系列
以 MD5 为例,我们不需要研究具体的计算签名的过程,只需要了解 MD5 的特点:
- 定⻓:⽆论多⻓的字符串,计算出来的 MD5 值都是固定⻓度(16字节版本或者32字节版本)
- 分散:源字符串只要改变⼀点点,最终得到的 MD5 值都会差别很⼤
- 不可逆:通过源字符串⽣成 MD5 很容易,但是通过 MD5 还原成原串理论上是不可能的。
正因为 MD5 有这样的特性,我们可以认为如果两个字符串的 MD5 值相同,则认为这两个字符串相同。
理解判定证书篡改的过程(这个过程就好⽐判定这个⾝份证是不是伪造的⾝份证):
假设服务器有一个简单的字符串 "hello"
,并使用 MD5 算法计算其哈希值为 BC4B2A76B9719D91
。
- 服务器端操作
- 服务器计算字符串
"hello"
的 MD5 值:BC4B2A76B9719D91
。 - 将字符串
"hello"
和其对应的哈希值BC4B2A76B9719D91
一起发送给客户端。
- 服务器计算字符串
- 客户端验证
- 客户端接收到字符串
"hello"
和哈希值BC4B2A76B9719D91
。 - 客户端重新计算字符串
"hello"
的 MD5 值。 - 如果计算得到的哈希值与接收到的哈希值一致,则说明数据未被篡改。
- 如果不一致,则说明数据在传输过程中被篡改。
- 客户端接收到字符串
- 篡改检测示例
- 如果字符串被篡改为
"hella"
,其 MD5 值变为BDBD6F9CF51F2FD8
。 - 客户端重新计算
"hella"
的 MD5 值后发现与接收到的哈希值BC4B2A76B9719D91
不匹配,从而检测到数据被篡改。
- 如果字符串被篡改为
但是还有个问题:如果⿊客把`"hella"`篡改了,同时也把哈希值重新计算下,客⼾端就分辨不出来了。
所以被传输的哈希值不能传输明⽂,需要传输密⽂。
所以,对证书明⽂(这⾥就是"hella"
)hash 形成散列摘要,然后 CA 使⽤⾃⼰的私钥加密形成签名,将"hella"
和加密的签名合起来形成 CA 证书,颁发给服务端。当客⼾端请求的时候,就发送给客⼾端。
如果被中间⼈截获了,因为没有 CA 私钥,就⽆法更改或者整体掉包,就能安全的证明,证书的合法性。
最后,客⼾端通过操作系统⾥已经存的了的证书发布机构的公钥进⾏解密,还原出原始的哈希值,,再进⾏校验。
为什么签名不直接加密,⽽是要先 hash 形成摘要?
在数字签名中,先对数据进行 hash 运算生成摘要,再对摘要进行加密的主要原因是:
- 缩小签名密文的长度:哈希值的长度固定且远小于原始数据,减少了签名的存储和传输成本。
- 加快数字签名的验证签名的运算速度:对固定长度的摘要进行加密比对整个数据加密更高效。
完整流程
左侧都是客⼾端做的事情,右侧都是服务器做的事情:
总结
HTTPS ⼯作过程中涉及到的三组密钥:
- 第⼀组(⾮对称加密:CA 签名相关):
- **目的:**确保证书的真实性和完整性。
- 秘钥对:
- **私钥:**由 CA 持有,用于对证书的摘要进行加密生成签名。
- **公钥:**由客户端持有(操作系统包含了可信的 CA 认证机构名单、同时持有对应的公钥),用于验证证书签名。
- 过程:
- 服务器向 CA 申请证书时,CA 使用其私钥对证书内容的哈希值进行加密,生成签名;
- 客户端接收到证书后,使用 CA 的公钥解密签名,得到原始摘要;
- 客户端重新计算证书内容的哈希值并与解密后的摘要对比,如果一致,则证明证书未被篡改。
- 第⼆组(⾮对称加密:密钥协商相关):
- **目的:**安全地协商出对称加密的密钥。
- 密钥对:
- **私钥:**由服务器生成并保存,用于解密客户端发送的对称密钥。
- **公钥:**服务器通过证书把公钥传递给客户端,用于加密对称密钥。
- 过程:
- 服务器生成一对非对称密钥(公钥和私钥),并将公钥嵌入到证书中;
- 客户端接收到证书后,提取公钥,并用该公钥加密随机生成的对称密钥;
- 客户端将加密后的对称密钥发送给服务器;
- 服务器使用自己的私钥解密,获取到对称密钥。
- 第三组(对称加密:数据传输相关):
- **目的:**高效地加密解密后续传输的数据。
- 密钥:
- 对称密钥:由客户端生成并通过第二组秘钥的安全通道传递给服务器。
- 过程:
- 客户端和服务器协商出相同的对称密钥后,所有后续通信都使用该对称密钥进行加密。
- 对称密钥算法效率高,适合大规模传输数据。
为什么需要三组密钥?
- 安全性与效率的平衡:
- 非对称加密(第一组和第二组)确保了密钥交换的安全性,但计算复杂度较高,不适合大量数据加密。
- 对称加密(第三组)效率高,适合实际的数据传输。
- 职责分离:
- 第一组密钥专注于证书的真实性验证。
- 第二组密钥负责安全地协商出对称密钥。
- 第三组密钥专注于高效的数据加密解密。
其实⼀切的关键都是围绕这个对称加密的密钥。
因为它效率高,但不安全,其他的机制都是辅助这个密钥⼯作,保证其安全的。