用Rust手把手编写一个Proxy(代理), TLS加密通讯
用Rust手把手编写一个Proxy(代理), TLS加密通讯
项目 ++wmproxy++
gite: https://gitee.com/tickbh/wmproxy
github: https://github.com/tickbh/wmproxy
为什么选择TLS
了解TLS
安全传输层协议(TLS)用于在两个通信应用程序之间提供保密性和数据完整性。
该协议由两层组成: TLS 记录协议(TLS Record)和 TLS 握手协议(TLS Handshake)。
TLS版本的历程
| 版本 | 发表年份 | RFC文件 | 弃用年份 | RFC链接 |
|---|---|---|---|---|
| TLS1.0 | 1999年 | RFC2246 | 2021年弃用 | https://datatracker.ietf.org/doc/rfc2246/ |
| TLS1.1 | 2006年 | RFC4346 | 2021年弃用 | https://datatracker.ietf.org/doc/rfc4346/ |
| TLS1.2 | 2008年 | RFC5246 | 正在使用 | https://datatracker.ietf.org/doc/rfc5246/ |
| TLS1.3 | 2018年 | RFC8446 | 正在使用 | https://datatracker.ietf.org/doc/rfc8446/ |
TLS协议的优势是与高层的应用层协议(如HTTP、FTP、Telnet等)无耦合。应用层协议能透明地运行在TLS协议之上,由TLS协议进行创建加密通道需要的协商和认证。应用层协议传送的数据在通过TLS协议时都会被加密,从而保证通信的私密性。
我们此时正应用他与应用层完全不耦合,又经历20年的发展历程非常的完善和安全,完全可以信任。
Client->>Server: TLS协议版本、随机数、支持的加密套件和对应公钥A
Server-)Server: 生成随机数B,根据信息生成密钥
Server-->>Client: 选用加密套件,服务端随机数,服务端证书
Server-->>Client: 使用的P、G、公钥B与签名
Server-->>Client: 握手报文的信息(服务端加密)
Client-)Client: 验证证书,使用a、B计算出K,得到密钥
Client->>Server: 握手报文的信息(客户端加密)
Client->>Server: 应用数据(客户端加密)
Server-->>Client: 应用数据(服务端加密)
了解RSA算法
1. 算法原理
算法本身基于一个简单的数论知识:给出两个素数,很容易将它们相乘,然而给出它们的乘积,想得到这两个素数就显得尤为困难。如果能够解决大整数(比如几百位的整数)分解的快速方法,那么 RSA 算法将轻易被破解。
2.公钥私钥的生成
- 准备两个非常大的素数p和q(转化成二进制后1024位或者4096或者更大位数,位数越多越难破解);
- 计算出两个大素数的乘积n=pq;
- 同样的方法计算m=(p-1)(q-1),这里的m为n的欧拉函数
- 找到一个数e(1 < e < m),满足(e,m)的最大公约数为1,即互素
- 找到数字d,需满足ed mod m = 1,即余数为1
- 此时生成完毕,公钥为(n,e),私钥为(n, d)
3. RSA加密
对明文x,用公钥(n, e)对x加密,将x转换成数字,通过公式得出密文y
y = x^e mod n
4. RSA解密
对明文y,用私钥(n, d)对y解密
x = y^d mod n
5. 小数测试
取p=5,q=11,得到n=p*q=55
m=(p-1)(q-1) = 40
取e=3,根据ed mod m = 1,可取d=27
此时公钥(n, e)=(55, 3)
此时私钥(n, d)=(55, 27)
提供明文a = 14,用公钥加密则密文c = a ^ e mod n = 14 ^ 3 mod 55 = 49
解密密文b = 49,用私钥解密则明文d = b ^ d mod n = 49 ^ 27 mod 55 = 14
6. 性能分析
因为RSA用到了指数级的计算,位数又是至少1024位起的,所以计算量非常的庞大,所以RSA的算法效率并不高,所以TLS除一开始密文交换的时候用到RSA,后续均用得到的密文做对称加密以减少计算量,TLS1.3所用如下TLS_AES_128_GCM_SHA256,TLS_AES_256_GCM_SHA384,TLS_CHACHA20_POLY1305_SHA256,TLS_AES_128_CCM_SHA256,TLS_AES_128_CCM_8_SHA256等对称加密算法。
7. Nginx证书文件pem及key
key文件,即包含
-----BEGIN RSA PRIVATE KEY-----的文件,这里面包含的信息有n, e, d, p, q等完整的RSA信息,也是保证安全最重要的信息,格式类似如下
RSAPrivateKey ::= SEQUENCE {
version Version,
modulus INTEGER, -- n
publicExponent INTEGER, -- e
privateExponent INTEGER, -- d
prime1 INTEGER, -- p
prime2 INTEGER, -- q
exponent1 INTEGER, -- d mod (p-1)
exponent2 INTEGER, -- d mod (q-1)
coefficient INTEGER, -- (inverse of q) mod p
otherPrimeInfos OtherPrimeInfos OPTIONAL
}
pem文件,包含了公钥信息(n, d)及证书链信息,可以知道谁签发的。
加密节点实现
角色说明,在wmproxy中存在两种角色,
- 末端的处理服务器
- 中间方只进行流量转发
关于TLS的参数有以下参数
pub struct Proxy {
/// 连接服务端是否启用tls
ts: bool,
/// 接收客户端是否启用tls
tc: bool,
/// tls证书所用的域名
domain: Option<String>,
/// 公开的证书公钥文件
cert: Option<String>,
/// 隐私的证书私钥文件
key: Option<String>,
}
因为加密存在可能的性能损耗,若在私有网络里不存在传输安全理论上可以不用开启加密传输。如果存在多个节点,前面节点已启用过加密,理论上后面节点也无需多次加密。
直接用https传输可能暴露什么?
因为客户端发起Client Hello的时候必须带上访问的domain,也就是网络的嗅探方虽然无法知道你访问的具体内容,但是可以知道你访问的网站列表。如:

启动二级代理
- 在本地启动代理
wmproxy -b 127.0.0.1 -p 8090 -S 127.0.0.1:8091 --ts
因为纯转发,所以在当前节点设置账号密码没有意义-S表示连接到的二级代理地址,有该参数则表示是中转代理,否则是末端代理。--ts表示连接父级代理的时候需要用加密的方式链接
- 在远程启动代理
wmproxy --user proxy --pass proxy -b 0.0.0.0 -p 8091 --tc
--tc表示接收子级代理的时候需要用加密的方式链接,可以--cert指定证书的公钥,--key指定证书的私钥,--domain指定证书的域名,如果不指定,则默认用自带的证书参数
至此通过代理访问的,我们已经没有办法得到真正的请求地址,只能得到代理发起的请求
源码说明
关于TLS依赖,选择的是rustls,tokio-rustls。
那么关于客户端的连接,那就有两种情况,一种是TcpStream,另一种是TlsStream<TcpStream>,我们的处理函数不确定传入的是哪种类型,所以此前的入参TcpStream全部改成泛型T,类似
async fn deal_stream<T>(&mut self, inbound: T) -> ProxyResult<()>
where T: AsyncRead + AsyncWrite + Unpin {
}
这样子只要可以异步读和写都可以成为入参的流。
如果存在tc参数,那么会将客户端转成TlsStream以便继续处理
if let Some(a) = accept.clone() {
let inbound = a.accept(inbound).await;
if let Ok(inbound) = inbound {
// 获取的流跟正常内容一样读写, 在内部实现了自动加解密
let _ = self.deal_stream(inbound).await;
} else {
println!("accept error = {:?}", inbound.err());
}
} else {
let _ = self.deal_stream(inbound).await;
};
客户端连接
let connector = TlsConnector::from(tls_client.unwrap());
let stream = TcpStream::connect(&server).await?;
// 这里的域名只为认证设置
let domain = rustls::ServerName::try_from(&*domain.unwrap_or("soft.wm-proxy.com".to_string()))
.map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "invalid dnsname"))?;
if let Ok(mut outbound) = connector.connect(domain, stream).await {
// connect 之后的流跟正常内容一样读写, 在内部实现了自动加解密
let _ = tokio::io::copy_bidirectional(&mut inbound, &mut outbound).await?;
}
这里利用的是TLS与上层解藕,只要他参与握手完之后,完全按我们的通讯来定。
后续改进
现在每个请求都和代理服务端进行一次请求握手,当开启断开非常多的时候会比较耗性能,可以考虑共用一条socket然后内部做协议解析,会减少握手时间,只是在流量非常大的时候会出现某条请求耗光了所有的带宽。
用Rust手把手编写一个Proxy(代理), TLS加密通讯的更多相关文章
- EFK教程(4) - ElasticSearch集群TLS加密通讯
基于TLS实现ElasticSearch集群加密通讯 作者:"发颠的小狼",欢迎转载 目录 ▪ 用途 ▪ ES节点信息 ▪ Step1. 关闭服务 ▪ Step2. 创建CA证书 ...
- EFK-4::ElasticSearch集群TLS加密通讯
转载自:https://mp.weixin.qq.com/s?__biz=MzUyNzk0NTI4MQ==&mid=2247483822&idx=1&sn=6813b22eb5 ...
- 新的知识点来了-ES6 Proxy代理 和 去银行存款有什么关系?
ES给开发者提供了一个新特性:Proxy,就是代理的意思.也就是我们这一节要介绍的知识点. 以前,ATM还没有那么流行的时候(暴露年纪),我们去银行存款或者取款的时候,需要在柜台前排队,等柜台工作人员 ...
- Java实战_手把手编写记事本
Java运用SWT插件编写桌面记事本应用程序 可实现windows系统桌面记事本基本功能.傻瓜式教学,一步一步手把手操作.小白也可自己编写出完整的应用程序. 须要工具:Eclipse(带SWT插件) ...
- 自己写一个java.lang.reflect.Proxy代理的实现
前言 Java设计模式9:代理模式一文中,讲到了动态代理,动态代理里面用到了一个类就是java.lang.reflect.Proxy,这个类是根据代理内容为传入的接口生成代理用的.本文就自己写一个Pr ...
- 手把手教你编写一个具有基本功能的shell(已开源)
刚接触Linux时,对shell总有种神秘感:在对shell的工作原理有所了解之后,便尝试着动手写一个shell.下面是一个从最简单的情况开始,一步步完成一个模拟的shell(我命名之为wshell) ...
- 手把手教你编写一个简单的PHP模块形态的后门
看到Freebuf 小编发表的用这个隐藏于PHP模块中的rootkit,就能持久接管服务器文章,很感兴趣,苦无作者没留下PoC,自己研究一番,有了此文 0×00. 引言 PHP是一个非常流行的web ...
- Hexo+NexT(六):手把手教你编写一个Hexo过滤器插件
Hexo+NexT介绍到这里,我认为已经可以很好地完成任务了.它所提供的一些基础功能及配置,都已经进行了讲解.你已经可以随心所欲地配置一个自己的博客环境,然后享受码字的乐趣. 把博客托管到Github ...
- caddy & grpc(3) 为 caddy 添加一个 反向代理插件
caddy-grpc 为 caddy 添加一个 反向代理插件 项目地址:https://github.com/yhyddr/caddy-grpc 前言 上一次我们学习了如何在 Caddy 中扩展自己想 ...
- C# 使用 Proxy 代理请求资源
C# 使用 Proxy 请求资源,基于 HttpWebRequest 类 前言 这是上周在开发 C# 中使用 Proxy 代理时开发的一些思考和实践.主要需求是这样的,用户可以配置每次请求是否需要代理 ...
随机推荐
- 如何批量修改 GitHub 代码提交作者
批量修改 GitHub 代码提交作者需要进行以下步骤: 首先,你需要 clone 远程仓库到本地,使用以下命令: git clone <repository-url> ``` 将 `< ...
- Burp+Xray的联动使用
Burp+Xray的联动使用 步骤如下, 1)首先,我们启动Xray的url监听功能,我们设置监听地址为127.0.0.1,端口为7777.监听的报告输出到xray文件夹根目录下的proxy_test ...
- MyBatis体系笔记
MyBatis 什么是MyBatis MyBatis是优秀的持久层框架 MyBatis使用XML将SQL与程序解耦,便于维护 MyBatis学习简单,执行高效,是JDBC的延伸 1.MyBatis开发 ...
- 前端vue地图定位并测算当前定位离目标位置距离可用于签到打卡
前端vue地图定位并测算当前定位离目标位置距离可用于签到打卡, 下载完整代码请访问uni-app插件市场地址: https://ext.dcloud.net.cn/plugin?id=12974 效果 ...
- 4. JDK相关设置
恐惧是本能,行动是信仰(在此感谢尚硅谷宋红康老师的教程) 1. 项目的 JDK 设置 File-->Project Structure...-->Platform Settings --& ...
- BeEF记录
前情提要 最近项目上常规手段遇阻,计划进行水坑钓鱼,一番搜索找到近期SolarMarker组织的手法,但是没有找到相关样本,于是就自己实现了一个类似的前端功能(水坑手法项目会持续记录学习,但目前不会放 ...
- JS逆向实战20——某头条jsvm逆向
声明 本文章中所有内容仅供学习交流,抓包内容.敏感网址.数据接口均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关,若有侵权,请联系我立即删除! 网站 目标网站:aHR0c ...
- Typecho博客部署一言接口
开始部署 下载代码上传至你的网站目录,把解压出来的文件夹改名为hitokoto 然后访问https://域名及文件路径/hitokoto查看效果 示例:https://sunpma.com/other ...
- MAUI Blazor Android 输入框软键盘遮挡问题
前言 最近才发现MAUI Blazor Android存在输入框软键盘遮挡这个问题,搜索了一番,原来这是安卓webview一个由来已久的问题,还好有大佬提出了解决方案 AndroidBug5497Wo ...
- 智能制造之路—从0开始打造一套轻量级MOM平台之基础平台搭建(Linux部署)
一.前言 前面我们选定了Admin.net来搭建我们的MOM快速开发平台,本章主要描述.NET6平台的Linux部署,以及记录搭建过程中坑. 本次搭建我们选择某云的轻量应用服务器,系统选择CentOS ...