用 Rust 实现一个替代 WebSocket 的协议

很久之前我就对websocket颇有微词,它的确满足了很多情境下的需求,但是仍然有不少问题。对我来说,最大的一个问题是websocket的数据是明文传输的,这使得websocket的数据很容易遭到劫持和攻击。同时,WebSocket继承自HTTP协议,这既是它的优点同样也是弊端。

于是我便想着,有没有一种协议,既能保证既能继承websocket的特性,还能保证数据安全的进行加密传输?我的确没能找到一个合适的协议,于是我就有一个大胆的想法,我为什么不能自己实现一个?

原本我是用Python实现的,毕竟我对PythonC/C++更熟悉一些,我的确用Python实现了一个版本,但是它的运行效率实在不能令我满意,实在是一坨答辩差强人意。于是我再三思考,又考虑了朋友的意见,我最后选择用Rust来实现它。

首先声明我现在已经是一个虔诚的 Rust 信徒。 不论如何,Rust 的确提供了比预期中更好的安全性、稳定性和高效性,在所有权、生命周期以及无损切片的作用下,大多时候你可以避免不必要的内存分配。在我进行基准测试的过程中,我发现Python异步并发的效率和Rust单线程运行的效率差不多

为了保证数据安全性,我需要让连接在握手的时候完成密钥交换。但是作为一个新的协议,我必须还要考虑握手效率,一个笨重的协议不是我所想要的(事实上在最开始的时候它依旧这样,但所幸我解决了),同时我还需要保证这一步的密钥是安全的。

最开始我参考了TLS协议,我使用RSA算法来对密钥进行加密,还使用了Scrypt,我发现我的握手效率及其不稳定,有时候能低到200ms左右,有时候甚至能高达数千毫秒,我后来才意识到这是RSA密钥的问题,因为我没有使用密钥池而是直接在请求被接入的时候再生成密钥。这个开销是巨大的。

除此之外,RustRSA密钥库被审计发现存在侧信道攻击(详情见 RustSec 官方资料)的重大漏洞,但是感觉维护者们已经摆烂了好吧千万别顺着网线打我,但是的确直到现在(距离我使用它已经经过了7个月),但是这个问题仍旧未能被解决。

RSA库的安全问题

于是我放弃了使用 rsa crate 转而使用 elliptic-curvep256 这两个给 crate,采用ECDHE算法来实现加密。最开始我使用 P256 曲线,但是后来我意识到p256 crate 的安全性同样是未得到审计的,这一点在p256的官方文档中被声明了。

p256 的安全免责声明

后来我再次放弃了使用p256,转而使用ring作为我的密码学原语库。实际上,ring中有相当一部分使用了C语言,但是由于RustC的高兼容性,这点暂时也无伤大雅。这里ECDHE利用双曲线X2215曲线反解的复杂性来确保密钥的安全性,它会在两端分别生成一对ECC密钥,一个公钥和一个私钥。随后,两端会分别向对方传输自己的公钥,完成密钥交换。最后,我们可以利用两端自己的私钥和对方的公钥计算得到一个共享密钥

实际上,这个时候我们的共享密钥仍然不是最终的密钥。我们使用高效安全的HKDF密钥派生算法来产出一个16位的密钥,这个密钥就是我们最终用于AES加密的密钥。

我们所使用的ECDH本质上是利用ECC密钥完成的,而ECDSA远比RSA更加快速,不论是从密钥对生成上还是加密效率上。尽管我们可以使用密钥池来规避RSA的时间复杂度,但是这并不能降低开销。(实际上生成ECC密钥所消耗的算法复杂度也比RSA更低)

同时,为了防止在协议层被黑客利用协议漏洞实现网络攻击,我要求客户端首先发送请求和公钥,客户端必须要向服务端展现自己需要建立连接的诚意,否则这常常会被黑客利用和攻击。除此之外,考虑到 MTU 也就是最大传输单元的问题,我将数据包大小限定在1024,这可以有效避免大文件在公网网络传输中的可能遇到的问题。

在修改后的基准测试中,我就已经在本地回环实现了稳定的10ms以内的握手延迟,而这实际上只是更改了加密算法后的效率。后来我还优化了几处可能出现并发数据竞争以及一个最开始为了节约时间而使用了正则表达式实现一处功能的问题。在后来经过一段时间的基准测试之后,我已经将Oblivion协议的本地回环握手延迟降低到0.5ms也就是500μs,这意味着在回环情况下,我只需要不到1ms的时间就可以完成握手并建立一个全双工的通讯信道。

值得一提的是,我们其实也完全可以使用 TLS 来完成握手过程,但是 TLS 协议要求验证服务端证书的可信性,这一步对我们来说是不必要的,于是我为此自行拟定了一个握手协议。

在完成握手之后,我们在两端之间建立长连接,一个双端实现了端对端加密的通讯信道就已经建立了。由于它是面向全双工和安全传输的,我为它命名为Oblivion

由于我使用了 TCP 协议而不是 UDP 协议,这意味着我不需要将时间花费在重传上,因为TCP是可靠的,它已经在系统层面为我们实现了这一点。而TCP是面向流的协议,这意味着你可以像操作系统中文件的输入输出流一样(或者说终端的标准输入输出stdioiostream更容易理解?)使用TcpStream,而I/O总线的输入和输出是独立的。那么我们便可以像WebSocket一样进行全双工的数据传输了。

而在握手的时候我们已经在双端得到了一个安全的密钥,可以放松的用来做AES GCM加密,那么在双端信道中写入读取均使用这个密钥作为加密解密的密钥,我们的数据在网络中就完全是密文传输了。

我借助 Rust 语言高并发和并发安全的特性,将这些过程封装成了一个 Rust Crate,并发布在了 crates.io 上。你可以像使用一个后端库一样来使用它,它就像一个使用了Oblivion协议替代传统HTTP协议的后端框架。你可以使用cargo add oblivion来将我的库加入你的项目依赖中使用。

你可以轻松创建一个绑定在39端口的Oblivion服务端:

use anyhow::Result;
use oblivion::models::render::BaseResponse;
use oblivion::models::router::Router;
use oblivion::models::server::Server;
use oblivion::models::session::Session;
use oblivion::path_route;
use oblivion::types::server;
use oblivion_codegen::async_route;

#[async_route]
fn welcome(sess: Session) -> server::Result {
    Ok(BaseResponse::TextResponse(
        format!(
            "欢迎进入信息安全区, 来自[{}]的朋友!",
            sess.request.get_ip()
        ),
        200,
    ))
}

#[tokio::main]
async fn main() -> Result<()> {
    let mut router = Router::new();

    router.route(RoutePath::new("/handler", RouteType::Path), handler);

    path_route!(router, "/welcome" => welcome);

    let server = Server::new("0.0.0.0", 7076, router);
    server.run().await?;
}

也可以轻松在客户端访问它:

use anyhow::Result;
use oblivion::models::client::Client;

#[tokio::main]
async fn main() -> Result<()> {
    let client = Client::connect(&format!("127.0.0.1:7076{}", args[2])).await?;

    client.recv().await?.text()?;
    client.close().await?;

    Ok(())
}

相关的源码均已经在 GitHub 开源:https://github.com/noctisynth/oblivion-rust,欢迎使用、提 issue 和 PR。同样可以查阅Oblivion的文档。

最后,如果我的文章或者项目对你有所帮助,作者舔着脸真诚地希望你能给我的项目点一个 Star,这是对我最大的鼓励!

点一个 Star

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/745206.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【操作系统】操作系统发展简史

目录 1.前言 2.第一代&#xff08;1945~1955&#xff09;&#xff1a;真空管和穿孔卡片 3.第二代&#xff08;1955~1965&#xff09;&#xff1a;晶体管和批处理系统 4.第三代&#xff08;1965~1980&#xff09;&#xff1a;集成电路和多道程序设计 5.第四代&#xff08;1…

关于VMware遇到的一些问题

问题一&#xff1a;打不开磁盘…或它所依赖的某个快照磁盘&#xff0c;开启模块DiskEarly的操作失败&#xff0c;未能启动虚拟机 解决方法&#xff1a; 首先将centos 7关机&#xff0c;然后把快照1删掉 然后打开虚拟机所在目录&#xff0c;把提示的000001.vmdk全部删除&…

本地读取classNames txt文件

通过本地读取classNames,来减少程序修改代码,提高了程序的拓展性和自定义化。 步骤: 1、输入本地路径,分割字符串。 2、将className按顺序放入vector容器中。 3、将vector赋值给classNmaes;获取classNames.size(),赋值给CLASSES;这样,类别个数和类别都已经赋值完成。…

大厂面试官问我:Redis内存淘汰,LRU维护整个队列吗?【后端八股文四:Redis内存淘汰策略八股文合集】

往期内容&#xff1a; 大厂面试官问我&#xff1a;Redis处理点赞&#xff0c;如果瞬时涌入大量用户点赞&#xff08;千万级&#xff09;&#xff0c;应当如何进行处理&#xff1f;【后端八股文一&#xff1a;Redis点赞八股文合集】-CSDN博客 大厂面试官问我&#xff1a;布隆过滤…

vue3 Cesium 离线地图

1、vite-plugin-cesium 是一个专门为 Vite 构建工具定制的插件&#xff0c;用于在 Vite 项目中轻松使用 Cesium 库。它简化了在 Vite 项目中集成 Cesium 的过程。 npm i cesium vite-plugin-cesium vite -D 2、配置vite.config.js import cesium from vite-plugin-cesiumexp…

监测与管理:钢筋计在工程项目中的应用

在现代工程建设中&#xff0c;特别是大型长期工程项目&#xff0c;对结构安全性的监测与管理至关重要。钢筋计作为一种重要的监测工具&#xff0c;在工程项目中发挥着不可替代的作用。本文将探讨钢筋计在长期工程项目中的应用&#xff0c;包括安装方法、数据监测与分析以及实际…

“基于下垂的多电源分布式控制系统设计”,高分资源,匠心制作,查重5%,下载可用。强烈推荐!!!

“基于下垂的多电源分布式控制系统设计”&#xff0c;高分资源&#xff0c;匠心制作&#xff0c;查重5%&#xff0c;下载可用。强烈推荐&#xff01;&#xff01;&#xff01; 摘要 社会的进步与发展&#xff0c;人们对于能源的需求和依赖越来越大。与此同时&#xff0c;国家…

通达信擒牛亮剑出击抄底主升浪指标公式源码

通达信擒牛亮剑出击抄底主升浪指标公式源码&#xff1a; ABC1:(CLOSE-REF(CLOSE,1))/REF(CLOSE,1)*100; ABC2:IF(CLOSE>OPEN,CLOSE,OPEN); ABC3:IF(CLOSE>OPEN,OPEN,CLOSE); ABC4:LLV(ABC2,4); ABC5:HHV(ABC3,4); ABC6:ABC2>ABC4 AND ABC3<ABC4 AND ABC2>ABC5 …

【unity实战】制作unity数据保存和加载系统——大型游戏存储的最优解

最终效果 文章目录 最终效果前言存储位置信息存储更多数据存储场景信息持久化存储数据 前言 前面写过小型游戏存储功能&#xff1a; 【unity实战】制作unity数据保存和加载系统——小型游戏存储的最优解&#xff08;包含数据安全处理方案的加密解密&#xff09; 这次做一个针…

spire.Pdf 将pdf转成image

一、nuget安装 <ItemGroup><PackageReference Include"Spire.PDF" Version"10.6.7" /></ItemGroup> 二、直接上代码 using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using System; using System.IO;namespace …

spring 中自动代理生成器的实现

为什么需要自动代理生成器 在 spring 中&#xff0c;提供了 org.springframework.aop.framework.ProxyFactoryBean 来实现对目标 bean 的增强&#xff0c;但此工具类存在以下缺点&#xff1a; 目标 bean 被增强后&#xff0c;获取实例对象时&#xff0c;使用的是配置的代理 b…

PostgreSQL使用教程

安装 PostgreSQL 您可以从 PostgreSQL 官方网站下载适合您操作系统的安装程序&#xff0c;并按照安装向导进行安装。 启动数据库服务器 安装完成后&#xff0c;根据您的操作系统&#xff0c;通过相应的方式启动数据库服务器。 连接到数据库 可以使用命令行工具&#xff08;如 p…

EE trade:利弗莫尔三步建仓法

在股市投资领域&#xff0c;利弗莫尔这个名字代表着无数的智慧和经历。他的三步建仓法成为了投资者们趋之若鹜的学习对象。本文将详细解析利弗莫尔的著名买入法&#xff0c;通过分步进攻方式&#xff0c;有效掌控市场并实现盈利。 一、利弗莫尔的三步建仓法详解 利弗莫尔三步…

SaaS 出海:Databend Cloud 的定位与实践

提到 “SaaS 出海”这个词大家肯定并不陌生&#xff0c;SaaS 企业将业务拓展到海外市场已经成为许多 SaaS 公司的重要战略方向。随着企业对于灵活性、可扩展性以及成本效益需求的不断增长&#xff0c; SaaS 模式提供了理想的解决方案。对于寻求出海机会的 SaaS 企业来说&#x…

秋招Java后端开发冲刺——关系型数据库篇(Mysql)

本文介绍关系型数据库及其代表Mysql数据库&#xff0c;并介常见面试题目。 一、数据库概述 1. 数据库&#xff08;Database, DB&#xff09;&#xff1a;是长期储存在计算机内的、有组织的、可共享的数据集合。 2. 数据库管理系统&#xff08;Database Management System, D…

高性能并行计算华为云实验五:PageRank算法实验

目录 一、实验目的 二、实验说明 三、实验过程 3.1 创建PageRank源码 3.2 makefile的创建和编译 3.3 主机配置文件建立与运行监测 四、实验结果与分析 4.1 采用默认的节点数量及迭代次数进行测试 4.2 分析并行化下节点数量与耗时的变化规律 4.3 分析迭代次数与耗时的变…

数据结构——跳表Skip List

本文对跳表的定义、实现、应用等进行简单总结。 一、 介绍 1.定义 跳表&#xff08;Skip List&#xff09;&#xff1a;是一种概率性数据结构&#xff0c;由William Pugh在1990年提出&#xff0c;主要用于在有序的元素集合上进行快速的搜索、插入和删除操作。跳表的效率与平衡…

百威英博旗下知名啤酒品牌Jupiler,创意助力比利时国足角逐欧洲杯冠军!

怎么说呢&#xff1f;今天非常开心。 因为今天分享的这个品牌创意案例很特别&#xff0c;和夏天、足球有关&#xff0c;和梦想、啤酒有关&#xff0c;还和QR Tiger 、二维彩虹有关。而把这一切连接在一起的&#xff0c;是一个小小的二维码。 这个夏天&#xff0c;百威英博旗下…

选专业,分析就业前景和市场需求

大学专业纷繁复杂&#xff0c;每个专业的就业前景和市场需求也天差地别&#xff0c;一般而言&#xff0c;就业前景优和市场需求的专业的学生更容易就业&#xff0c;更容易实现个人价值&#xff1f; 一、充分利用性格优势 在专业选择当中&#xff0c;如果我们自己对某个专业拥有…

背包模型——AcWing 423. 采药

背包模型 定义 背包模型是一种常见的算法问题模型&#xff0c;它主要涉及将一些物品放入一个容量有限的背包中&#xff0c;以达到某种最优目标&#xff0c;如最大化价值或最小化重量等。 运用情况 常用于资源分配、项目选择、货物装载等实际问题中。例如&#xff0c;在选择…