HTTP 加密代理
涛叔HTTP 代理在企业中有着应泛的应用。但 HTTP 代理信令是明文传输的,不但会泄漏个人隐私,而且会导致网络审查和阻断。本文介绍一种使用 TLS 技术对 HTTP 实行加密保护的方法。
对于明文 HTTP 请求,代理通信的流程如下:
Agent Proxy Site
| | |
| <1> tcp dial to proxy:8080 | |
|<--------------------------->| |
| <2> GET / HTTP/1.1 | |
| Host: baidu.com | <3> tcp dial to baidu.com:80 |
|---------------------------->|<---------------------------->|
| | <4> GET / HTTP/1.1 |
| | Host: baidu.com |
| |----------------------------->|
| | <5> HTTP/1.1 200 OK |
| <4> HTTP/1.1 200 OK |<-----------------------------|
|<----------------------------| |
| | |
- <1> 客户端同代理服务器建立 TCP 连接
- <2> 客户端将 HTTP 请求发送给代理服务器
- <3> 代理服务器解析 HTTP 请求,获取目标服务器地址并建立 TCP 连接
- <4> 代理服务器将客户端 HTTP 请求发送给目标服务器
- <5> 目标服器将响应内容发送给代理服务器
- <6> 代理服务器将目标服务器的响应内容发送给客户端
如是你要访问 HTTPS 站点,则需遵循另外一套流程:
Agent Proxy Site
| | |
| <1> tcp dial to proxy:8080 | |
|<--------------------------->| |
| <2> CONNECT baidu.com:443 | |
|---------------------------->| <3> tcp dial to baidu.com:443|
| <4> HTTP/1.1 200 OK |<---------------------------->|
|<----------------------------| |
| | |
| https data | https data |
|<--------------------------->|<---------------------------->|
| | |
- <1> 客户端同代理服务器建立 TCP 连接
- <2> 客户端向发送给代理服务器发送 CONNECT 请求
- <3> 代理服务器解析 HTTP 请求,获取目标服务器地址并建立 TCP 连接
- <4> 代理服务器向客户端发送 Connection Established 响应(图中标为 OK)
接下来代理服务器会不断将客户端发来数据转发给目标服务器,并把目标服务器的发来的数据转发给客户端。
那为什么 HTTP 请求和 HTTPS 请求的代理通信流程不一样呢?因为对于明文的 HTTP 请求,代理服务器可以通过解析请求内容获取目标服务器的域名和端口,进而发起 TCP 连接。可对于 HTTPS 请求,客户端在发送 HTTP 请求之前要发送 TLS 握手数据。Proxy 收到 TLS 握手数据后是无法获取目标服务器。所以,协议要求客户端要先发送一个 CONNECT 请求来告诉代理服务器目标服务器的域名和端口。
大家注意,对于 HTTP 请求,Agent 和 Proxy 之间的通信中全明文的;对于 HTTPS 请求, Agent 的 CONNECT 请求也是通过明文发送给 Proxy 的。因为是明文协议,所以存在安全风险。
我们知道,HTTPS 就是使用 TLS 对明文 HTTP 协议进行的加密的。那我们可不可以使用 TLS 对 Agent 和 Proxy 之间的通信进行加密呢?理论上是可行的,但现实中却不可行,因为改造现有系统来支持 TLS 版 HTTP 代理的工作量太大了1。那就没有其他办法了吗?有,那就是引入二级代理,其代理通信流程如下:
Agent Local Proxy Remote Proxy Site
| | | |
| <1> tcp dial to proxy:8080 | | |
|<--------------------------->| | |
| <2> GET / HTTP/1.1 | | |
| Host: baidu.com | | |
|---------------------------->| <3> tls handshake | |
| |<--------------------------->| |
| ~=================================~ |
| | <4> CONNECT baidu.com:80 | |
| |---------------------------->| |
| | | <5> tcp dial to baidu.com:80 |
| | <6> HTTP/1.1 200 OK |<---------------------------->|
| |<----------------------------| |
| | <7> GET / HTTP/1.1 | |
| | Host: baidu.com | <8> GET / HTTP/1.1 |
| |---------------------------->| Host: baidu.com |
| | |----------------------------->|
| | | <9> HTTP/1.1 200 OK |
| | <10> HTTP/1.1 200 OK |<-----------------------------|
| |<----------------------------| |
| ~=================================~ |
| <11> HTTP/1.1 200 OK | | |
|<----------------------------| | |
| | | |
对于 HTTPS 请求,通信流程则稍微简单一些:
Agent Local Proxy Remote Proxy Site
| | | |
| <1> tcp dial to proxy:8080 | | |
|<--------------------------->| | |
| <2> CONNECT baidu.com:443 | | |
|---------------------------->| <3> tls handshake | |
| |<--------------------------->| |
| ~=================================~ |
| | <4> CONNECT baidu.com:443 | |
| |---------------------------->| |
| | | <5> tcp dial to baidu.com:443|
| | <6> HTTP/1.1 200 OK |<---------------------------->|
| <7> HTTP/1.1 200 OK |<----------------------------| |
|<----------------------------| | |
| https data | https data | https data |
|<--------------------------->|<--------------------------->|<---------------------------->|
| ~=================================~ |
| | | |
这里的核心是引入 Local Proxy 和 Remote Proxy 两台代理服务器。对 Agent 来说,Local Proxy 就是一台普通的代理服务器。Local Proxy 收到代理请求后不会直接连接目标服务器,而是先跟 Remote Proxy 建立 TLS 连接,并使用 CONNECT 请求将目标服务器的信息发送给 Remote Proxy,Remote Proxy 收到 CONNECT 请求后执行代理连辑。 Local Proxy 既是 Agent 的 Proxy,又是 Remote Proxy 的 Agent,具有双重人格。 Local Proxy 和 Remote Proxy 之间的通信使用 TLS 加密,保证不被恶意用户窃取。
理好思路,编码实现就很容易了。我自己使用 Go 语言的实现已经发布到了 GitHub 上,供大家参考。https://github.com/taoso/tlstunnel
最后,TLS 加密 HTTP 代理的缺点。
第一,TLS 通信需要 SSL 证书。SSL 证书一般只签发给域名,所以还需要注册一个域名。好在我们可以去 https://www.freenom.com 注册一年期免费域名,也可以使用 https://letsencrypt.org/ 签发三月期证书,所以这个问题不大。
第二,就是 TLS 握手效率的问题。TLS v1.2 握手流程确实很繁琐,至少要四次通信才能完成握手,延迟的确很大。但是,TLS v1.3 对握手流程做了大幅优化,只需两次通信即可,大大缩短了握手延迟。所以,只要能使用 TLS v1.3,这个问题也不大。
现在像 Chrome/Firefox 等浏览器均支持通过 HTTPS 发起 HTTP 代理请求,也就是所谓的 Secure Web Proxy,具体可参考 https://www.chromium.org/developers/design-documents/secure-web-proxy/↩︎