生产实践 |「译」1password 的 Rust 实践


英文原文

Rust已经风靡编程语言界。自2015年发布1.0版本以来,它一直是最受喜爱的编程语言之一,拥有一批忠实的开发者和贡献者。

为何 Rust 在软件开发者中会如此受宠?为了解答这个疑问,我们踏上了一段关于 Rust 软件开发的新旅程。我们将采访一些在重要项目中使用 Rust 的技术人员。这些重要项目涉及但不限于手机应用、服务程序、初创公司的最小可行化产品。

在本系列的第一期中,我们采访了 1Password 的工程副总裁 Michael Fey。他们为什么选择 Rust 做开发?Rust 给安全软件带来了哪些好处?如果你想使用 Rust 开发类似的软件,应该关注哪些库?如果你想知道这些问题的答案,请继续阅读。

你能给我们介绍下关于公司和你的一些情况吗?

1Password 是一款已经被数百万人和70,000家企业采用的优秀的密码管理软件,用于保护他们的敏感数据。它支持主流浏览器、桌面和移动设备. 它能帮助你记住所有你没有必要去记住的密码。

我是 1Password 客户端开发的工程副总裁。如果您曾在 Mac、Windows PC、iPhone、iPad、Android 手机、平板电脑或浏览器中使用过1Password,那么您就使用了我们团队开发的软件。从2004年开始,我们就专注于打造这款软件。这是一款体验绝佳的安全产品,为此我们感到非常自豪。

你能谈谈 1Password 的技术栈吗?你们的代码中有多大一部分是用 Rust 编写的?

我们在 1Password 中使用Rust已经有好几年了。我们的 Windows 团队是这项工作的领头羊。Windows版的1Password 7 中大约 70% 的代码是用 Rust 编写的。我们还在2019年底把 1Password Brian (一种浏览器填充逻辑的引擎) 从 Go 移植到 Rust,然后把 Rust 编译为 WebAssembly,最后再部署到浏览器插件中。这样我们就可以利用到 WebAssembly 的速度和性能。

它们得益于产品采用了Rust,在过去几年我们取得了巨大成功。现在我们正在对几乎整个产品线进行重写,Rust 在其中扮演主要角色。我们正在使用 Rust 创建一个headless 1Password 应用: 把所有的业务逻辑、加密解密、数据库访问、服务器通信等统统包裹到一个薄薄的 UI 层中,然后作为原生应用部署到系统中。

1Password 采用 Rust 的原因是什么,是看中它的高性能或类型/内存安全吗?

最初吸引我们使用 Rust 的主要原因之一是内存安全; Rust 可以增强我们对保护客户数据安全的信心,这无疑让我们兴奋不已。不过,除了内存安全之外,我们对Rust生态系统的喜爱还有很多。没有传统的运行时是一个显著的性能优势;例如,我们不再担心垃圾收集器的性能开销。Rust提供了一种 "程序正确性 "的形式和许多针对运行时未定义行为的保证。强类型系统在编译时会强制保证这些规则。仔细地将应用逻辑与Rust的强类型规则对齐,使API难以被误用。同时,因为不需要对约束和不变量进行运行时检查,所以可以写出简洁的代码。在程序执行之前,编译器就可以保证: 不存在无效的运行时代码路径, 不会因此产生程序异常。因为运行时状态验证更少,所以写出的代码会更干净、更高效、更内聚、质量也更高。与其他语言相比,Rust 很少需要运行时调试。如果能编译通过,你就可以相当确定它不会表现出未定义行为。它可能不是你想要的,但它会是 "正确的"

Rust 的另一个非常强大却常被忽视的特性是程序化宏系统[1]。它使我们能够编写一种工具:可以自动将 Rust 中定义的类型与我们的客户端语言 (Swift、Kotlin和 TypeScript) 共享。这种工具的输出会自动处理序列化/反序列化过程。这意味着客户端开发人员在与 Rust 库交互时,可以继续使用他们选择的语言进行编程,同时又可以消除使用 FFI 进行 JSON 解析的烦恼。除了上述这些益处,我们还能获得每一种目标语言在编译期类型检查的好处。我们已经把这个工具集成到持续集成服务器中,这意味着对Rust模型的改变会导致客户端应用程序的编译失败,而这些失败情况会在代码评审中被发现。

这个工具已经成为我们开发过程中不可或缺的组成部分,让我们的进度比以前快得多。一旦我们的类型在Rust中被定义,我们就能立即在客户端语言中生成等价类型。这使我们的开发人员能够专注于解决问题。而不必去捋模版代码,再使用 FFI 进行通信

Rust对开发像1Password这样以安全为中心的应用程序的支持(库和其他)有多好?

对于实现安全软件的大部分基础组件来说,那是绰绰有余的。有两个大型的、突出的密码学平台( ringRust Crypto 组),它们提供了丰富的功能。正如我在前面提到的,用 Rust 编写程序会让你对内存的使用充满信心,也让你更难意外引入与内存相关的漏洞。还有一个很好的系统,用来跟踪Rust crates中不时出现的漏洞:RustSec 数据库。它是由其他 Rust 开发者提供的社区资源,并且经常更新。此外,Rust 和 Cargo 还包含了 batteries-included 测试框架。这意味着你总是有一种容易的方式来编写单元测试套件,以保证关键代码(比如加密函数)的正确性。

如果存在 Rust 原生安全库,那当然是最理想的 (而且它们会及时出现) 。如果没有也不必担心,我们还有其他选项:使用C语言或原生平台库中的一些东西。在我们的Rust代码中,我们将这一点发挥得淋漓尽致,比如调用生物识别解锁的原生实现(Touch ID、Face ID、Windows Hello)和特定平台的设置实现(比如苹果平台上的NSUserDefaults)。

其中有什么特别的Rust库是你想介绍一下的吗?

当然有。1Password 使用了 Tokio、Hyper/Reqwest、Ring 和Neon。得益于这些 Rust 库,我们才能完成这个雄心勃勃的项目。你也应该看看我们在 crates.io 上的 密码规则解析器 。它主要基于苹果支持的规范。他们的工具和文档可以在 这里 找到。

在用 Rust 开发 1Password 的过程中,遇到的最大挑战是什么?

我们团队中的许多人都是Rust的新手,他们经历了典型的学习曲线,这与它的内存管理和所有权模型有关。我们还发现编译时间很长;我们的CPU和风扇肯定会受到锻炼。😄

你对结果满意吗?

绝对满意

你有什么关键的心得想跟我们的观众分享吗?

如果你是Rust的新手,请从小处着手,并在此基础上进行改进。我们在刚开始的时候进行了大量的实验,试图找到基于Rust的最佳解决方案。当你的实验成功后,回顾一下你过去使用其他语言的工作方式,看看你的代码能否从Rust的理念中获益。

如果你是1Password的新用户,今天就可以通过这个链接注册,家庭和个人账户第一年可以节省50%的费用。如果你正在做一个开源项目,你可以免费获得一个1Password Teams账户。请前往我们的 GitHub 仓库了解更多信息。

附录

[1] 指的是typeshare. 它的功能是把一些用rust 写的结构体生成为其他语言的结构体,比如下面的rust 的一个struct


#![allow(unused)]
fn main() {
#[derive(Serialize, Deserialize, Debug)]
struct Teacher {
    name: String,
    age: u64,
    id: u64,
}
}

转化为typescript的变成如下:

export interface Teacher {
    name: string;
    age: number;
    id: number;
}

它同时支持typescript,swift,java. 它把Rust写的struct生成了团队其他中定义各语言结构体的规范。所以该规范也只是1password团队内部定义domain层的规范。不一定适合其他团队。

[2] 另一款开源的密码管理器bitwarden. 也有rust 实现后台bitwarden_rs. 有兴趣可以进一步阅读。


译者简介:

柴杰,中国科学技术大学集成电路工程专业,在读硕士研究生。兴趣与专长为虚拟内存系统、分布式系统。

审校:

  • 严炳(ryan),算法,大数据开发从业者,喜欢和有开源精神的人一起共事。