抗量子电子邮件:我们如何使用加密的 SQLite 邮箱来保障您的电子邮件安全

前言

Important

我们的电子邮件服务是 100%开源,并通过安全加密的 SQLite 邮箱注重隐私。

在我们推出 IMAP 支持 之前,我们使用 MongoDB 来满足持久数据存储需求。

这项技术非常了不起,我们至今仍在使用它 - 但为了使用 MongoDB 进行静态加密,您需要使用提供 MongoDB Enterprise 的提供商,例如 Digital Ocean 或 Mongo Atlas - 或者支付企业许可证费用(随后必须与销售团队合作以应对延迟)。

转发电子邮件 团队需要一款开发者友好、可扩展、可靠且加密的 IMAP 邮箱存储解决方案。作为开源开发者,使用需要支付许可费用才能获得静态加密功能的技术违反了 我们的原则 的要求——因此,我们进行了实验、研究,并从零开始开发了一款全新的解决方案来满足这些需求。

我们不使用共享数据库来存储您的邮箱,而是单独存储您的邮箱,并使用您的密码(只有您拥有)进行加密。我们的电子邮件服务非常安全,如果您忘记密码,您的邮箱就会丢失(需要使用离线备份进行恢复或重新开始)。

请继续阅读,我们将深入探讨 电子邮件服务提供商的比较我们的服务如何运作我们的技术堆栈 等。

电子邮件服务提供商比较

我们是唯一一家 100% 开源且注重隐私的电子邮件服务提供商,可存储单独加密的 SQLite 邮箱,提供无限的域、别名和用户,并支持出站 SMTP、IMAP 和 POP3:

**与其他电子邮件提供商不同,使用 Forward Email,您无需按域名或别名支付存储空间费用。**存储空间将在您的整个帐户中共享——因此,如果您拥有多个自定义域名,并且每个域名都拥有多个别名,那么我们将是您的理想解决方案。请注意,您仍然可以根据需要按域名或别名强制执行存储空间限制。

阅读电子邮件服务比较

如何运作

  1. 使用您的电子邮件客户端(例如 Apple Mail、Thunderbird、Gmail 或 Outlook)– 使用您的用户名和密码连接到我们安全的 IMAP 服务器:
  • 您的用户名是您域名的完整别名,例如 hello@example.com
  • 您的密码是随机生成的,并且仅在您点击我的帐户 别名生成密码时显示 30 秒。
    1. 连接后,您的电子邮件客户端会将 IMAP 协议命令 发送到我们的 IMAP 服务器,以保持您的邮箱同步。这包括撰写和存储电子邮件草稿,以及您可能执行的其他操作(例如,将电子邮件标记为“重要”或将电子邮件标记为“垃圾邮件”)。

    2. 邮件交换服务器(通常称为“MX”服务器)接收新的入站邮件并将其存储到您的邮箱。当这种情况发生时,您的电子邮件客户端将收到通知并同步您的邮箱。我们的邮件交换服务器可以将您的邮件转发给一个或多个收件人(包括 网络钩子),将您的邮件存储在我们提供的加密 IMAP 存储空间中,或两者兼而有之

    1. 在后台,我们的安全电子邮件存储设计采用两种方式来保护您的邮箱加密,并且只有您可以访问:
    • 当从发件人收到您的新邮件时,我们的邮件交换服务器会将邮件写入您的个人、临时且加密的邮箱。

    • 当您使用电子邮件客户端连接到我们的 IMAP 服务器时,您的密码将被加密并存储在内存中,用于读写您的邮箱。只有使用此密码才能读写您的邮箱。请注意,由于您是唯一拥有此密码的人,因此在您访问邮箱时,只有您才能读写您的邮箱。下次您的电子邮件客户端尝试轮询邮件或同步时,您的新邮件将从此临时邮箱传输到您的实际邮箱文件中,并使用您提供的密码进行存储。请注意,此临时邮箱随后将被清除并删除,因此只有您受密码保护的邮箱才能保留这些邮件。

    • 如果您已连接到 IMAP(例如使用 Apple Mail 或 Thunderbird 等电子邮件客户端),则我们无需写入临时磁盘存储。我们会提取并使用您内存中加密的 IMAP 密码。实时情况下,当邮件尝试发送给您时,我们会向所有 IMAP 服务器发送 WebSocket 请求,询问它们是否有您的活动会话(这是提取部分),然后会将该加密的内存密码传递给服务器——因此,我们无需写入临时邮箱,而是可以使用您的加密密码写入您实际的加密邮箱。

    1. 加密邮箱的备份 每日生成。您也可以随时请求新的备份,或从我的帐户域名 别名下载最新备份。如果您决定切换到其他电子邮件服务,您可以随时轻松迁移、下载、导出和清除您的邮箱和备份。

    技术

    数据库

    我们探索了其他可能的数据库存储层,但没有一个能像 SQLite 那样满足我们的要求:

    数据库 静态加密 Sandboxed 邮箱 执照 Used Everywhere
    SQLite :星号: ✅ 是,使用 SQLite3MultipleCiphers :白色勾号: ✅ 公共领域 :白色勾号:
    MongoDB :x:"Available in MongoDB Enterprise only" ❌ 关系数据库 ❌ AGPL 和 SSPL-1.0 :x:
    rqlite :x:Network only ❌ 关系数据库 :白色复选标记:MIT :x:
    dqlite :x:Untested and not yet supported? :x:Untested and not yet supported? :白色复选标记:LGPL-3.0-only :x:
    PostgreSQL :白色勾选标记:Yes ❌ 关系数据库 PostgreSQL(类似于 BSDMIT :x:
    MariaDB :白色勾选标记:For InnoDB only ❌ 关系数据库 :white_check_mark:GPLv2BUSL-1.1 :x:
    CockroachDB :x:Enterprise-only feature ❌ 关系数据库 BUSL-1.1 及其他 :x:

    上表中有一个 比较几种 SQLite 数据库存储选项的博客文章

    安全

    我们始终使用 静态加密 (AES-256)、传输中加密 (TLS)、通过 HTTPS 进行 DNS (“DoH”) 和 柑橘 (🍊) 加密邮箱,以及 sqleet (ChaCha20-Poly1305) 加密邮箱。此外,我们还使用基于令牌的双因素身份验证(而非易受 中间人攻击 攻击的短信验证码)、禁用 root 访问权限的轮换 SSH 密钥、通过受限 IP 地址独占访问服务器等等。

    如果出现 邪恶女仆袭击 或第三方供应商的恶意员工,您的邮箱仍然只能使用您生成的密码打开。请放心,除了符合 SOC Type 2 投诉标准的服务器提供商 Cloudflare、DataPacket、Digital Ocean 和 Vultr 之外,我们不依赖任何第三方供应商。

    我们的目标是尽可能少地使用 单点故障

    个邮箱

    tldr; 我们的 IMAP 服务器对您的每个邮箱使用单独加密的 SQLite 数据库。

    SQLite 非常流行 嵌入式数据库 – 它当前在您的手机和计算机上运行 – 并被几乎所有主要技术所采用

    例如,在我们的加密服务器上,linux@example.cominfo@example.comhello@example.com 等邮箱对应一个 SQLite 数据库文件,每个文件对应一个 .sqlite 数据库文件。我们也不会使用邮箱地址来命名数据库文件,而是使用 BSON ObjectID 和生成的唯一 UUID,这些 UUID 不会透露邮箱的所属者或邮箱地址(例如 353a03f21e534321f5d6e267.sqlite)。

    每个数据库都使用您的密码(只有您知道)通过 sqleet (ChaCha20-Poly1305) 进行加密。这意味着您的邮箱是单独加密的、独立的、可移植的。

    我们已经使用以下 PRAGMA 对 SQLite 进行了微调:

    PRAGMA 目的
    cipher=chacha20 参考 Projects 下的 better-sqlite3-multiple-ciphers 以了解更多信息。
    key="****************" 这是您已解密的仅存储在内存中的密码,它将通过您的电子邮件客户端的 IMAP 连接传递到我们的服务器。每次读写会话都会创建并关闭新的数据库实例(以确保沙盒和隔离)。
    journal_model=WAL 预写日志(“WAL”)which boosts performance and allows concurrent read access
    busy_timeout=5000 防止写锁错误while other writes are taking place
    synchronous=NORMAL 增加交易 without data corruption risk 的持久性。
    foreign_keys=ON 强制执行外键引用(例如从一个表到另一个表的关系)。By default this is not turned on in SQLite,但为了验证和数据完整性,应该启用它。
    encoding='UTF-8' Default encoding 用于确保开发人员的理智。

    所有其他默认值均来自 官方 PRAGMA 文档 所指定的 SQLite。

    并发

    tldr; 我们使用 WebSocket 对您的加密 SQLite 邮箱进行并发读取和写入。

    读取

    您手机上的电子邮件客户端可能会将 imap.forwardemail.net 解析为我们的一个 Digital Ocean IP 地址,而您的桌面客户端可能会将 提供者 解析为一个单独的 IP。

    无论您的电子邮件客户端连接到哪个 IMAP 服务器,我们都希望该连接能够实时、100% 准确地从您的数据库读取数据。这可以通过 WebSocket 实现。

    写入

    写入数据库有点不同 - 因为 SQLite 是一个嵌入式数据库,并且您的邮箱默认位于单个文件中。

    我们已经探索了下面的 litestreamrqlitedqlite 等选项 - 但是这些都不能满足我们的要求。

    为了在启用预写日志(“WAL”)的情况下完成写入操作,我们需要确保只有一台服务器(“主服务器”)负责执行此操作。WAL 可以显著加快并发速度,并允许一个写入器和多个读取器。

    主服务器运行在已挂载了加密邮箱卷的数据服务器上。从分发的角度来看,您可以将 imap.forwardemail.net 后面的所有 IMAP 服务器视为辅助服务器(“辅助服务器”)。

    我们通过WebSockets实现双向通信:

    • 主服务器使用 WSWebSocketServer 服务器实例。
    • 辅助服务器使用 WSWebSocket 客户端实例,该客户端实例由 websocket 承诺重新连接 websocket 包装。这两个包装器确保 WebSocket 能够重新连接,并能够针对特定的数据库写入操作发送和接收数据。

    备份

    tldr; 您的加密邮箱每天都会备份。您也可以随时从我的账户域名 别名中立即请求新的备份或下载最新备份。

    对于备份,我们只需在 IMAP 命令处理期间每天运行 SQLite VACUUM INTO 命令,该命令会利用您通过内存 IMAP 连接获取的加密密码。如果未检测到现有备份,或者文件的 SHA-256 哈希值与最新备份相比发生变化,则会存储备份。

    请注意,我们使用 VACUUM INTO 命令,而不是内置的 backup 命令,因为如果在 backup 命令操作期间修改了页面,则必须重新开始。VACUUM INTO 命令将创建快照。有关更多信息,请参阅关于 GitHub黑客新闻 的注释。

    此外,我们使用 VACUUM INTO 而不是 backup,因为 backup 命令会使数据库在短时间内保持未加密状态,直到调用 rekey(有关详情,请参阅此 GitHub 评论)。

    辅助服务器将通过 WebSocket 连接指示主服务器执行备份 - 然后主服务器将收到执行备份的命令,随后将:

    1. 连接到您的加密邮箱。
    2. 获取写锁。
    3. 通过 wal_checkpoint(PASSIVE) 运行 WAL 检查点。
    4. 运行 VACUUM INTO SQLite 命令。
    5. 确保复制的文件可以使用加密密码打开(安全保护/防伪)。
    6. 将其上传到 Cloudflare R2 进行存储(或如果您指定,则上传到您自己的提供商)。

    请记住,您的邮箱是加密的 - 虽然我们对 WebSocket 通信有 IP 限制和其他身份验证措施 - 但如果出现不良行为者,您可以放心,除非 WebSocket 有效负载具有您的 IMAP 密码,否则它无法打开您的数据库。

    目前每个邮箱仅存储一个备份,但将来我们可能会提供时间点恢复(“PITR”)。

    我们的 IMAP 服务器支持具有复杂查询、正则表达式等的 SEARCH 命令。

    快速的搜索性能得益于 FTS5sqlite-regex

    我们通过 Date.prototype.toISOStringDate 值作为 ISO 8601 字符串存储在 SQLite 邮箱中(使用 UTC 时区以确保相等性比较正常运行)。

    还存储了搜索查询中所有属性的索引。

    项目

    下表概述了我们在源代码和开发过程中使用的项目(按字母顺序排列):

    项目 目的
    Ansible DevOps 自动化平台可轻松维护、扩展和管理我们的整个服务器群。
    Bree Node.js 和 JavaScript 的作业调度程序,具有 cron、dates、ms、later 和人性化支持。
    Cabin 开发人员友好的 JavaScript 和 Node.js 日志库,兼顾安全性和隐私性。
    Lad Node.js 框架通过 MVC 等为我们的整个架构和工程设计提供支持。
    MongoDB NoSQL 数据库解决方案,我们用于存储邮箱之外的所有其他数据(例如您的帐户、设置、域和别名配置)。
    Mongoose MongoDB 对象文档模型(“ODM”),我们在整个堆栈中都使用这个模型。我们编写了特殊的辅助函数,以便我们能够轻松地继续将 Mongoose 与 SQLite 结合使用。
    Node.js Node.js 是开源、跨平台的 JavaScript 运行时环境,可运行我们所有的服务器进程。
    Nodemailer 用于发送电子邮件、创建连接等功能的 Node.js 软件包。我们是该项目的官方赞助商。
    Redis 用于缓存、发布/订阅渠道和 HTTPS 请求上的 DNS 的内存数据库。
    SQLite3MultipleCiphers SQLite 的加密扩展允许对整个数据库文件进行加密(包括预写日志(“WAL”)、日志、回滚等)。
    SQLiteStudio 可视化 SQLite 编辑器(您也可以使用它)来测试、下载和查看开发邮箱。
    SQLite 嵌入式数据库层,用于可扩展、独立、快速且有弹性的 IMAP 存储。
    Spam Scanner Node.js 反垃圾邮件、电子邮件过滤和网络钓鱼预防工具(我们对 Spam Assassinrspamd 的替代品)。
    Tangerine 使用 Node.js 通过 HTTPS 请求进行 DNS 处理并使用 Redis 进行缓存 - 从而确保全局一致性等等。
    Thunderbird 我们的开发团队使用它(并且也推荐它)作为转发电子邮件的首选电子邮件客户端
    UTM 我们的开发团队使用它为 iOS 和 macOS 创建虚拟机,以便使用我们的 IMAP 和 SMTP 服务器(并行)测试不同的电子邮件客户端。
    Ubuntu 基于现代开源 Linux 的服务器操作系统,为我们所有的基础设施提供支持。
    WildDuck IMAP 服务器库 – 请参阅其关于 attachment de-duplicationIMAP protocol support 的注释。
    better-sqlite3-multiple-ciphers Node.js 的快速、简单的 API 库,用于以编程方式与 SQLite3 交互。
    email-templates 开发人员友好的电子邮件框架,用于创建、预览和发送自定义电子邮件(例如帐户通知等)。
    json-sql-enhanced 使用 Mongo 风格语法的 SQL 查询构建器。这节省了我们开发团队的时间,因为我们可以在整个堆栈中继续使用 Mongo 风格编写,并且采用与数据库无关的方法。它还可以通过使用查询参数来避免 SQL 注入攻击
    knex-schema-inspector SQL 实用程序用于提取现有数据库架构的信息。这使我们能够轻松验证所有索引、表、列、约束等是否有效,以及 1:1 是否符合其应有的状态。我们甚至编写了自动化助手,以便在数据库架构发生更改时添加新的列和索引(并附带极其详细的错误警报)。
    knex SQL 查询生成器,我们仅通过 knex-schema-inspector 进行数据库迁移和模式验证。
    mandarin 使用 Google Cloud Translation API 自动进行 i18n 短语翻译,并支持 Markdown。
    mx-connect Node.js 包用于解析和建立与 MX 服务器的连接并处理错误。
    pm2 带有内置负载均衡器(用于提高性能的fine-tuned)的 Node.js 生产流程管理器。
    smtp-server SMTP 服务器库 – 我们将其用于我们的邮件交换(“MX”)和出站 SMTP 服务器。
    ImapTest 一款实用的工具,用于根据基准测试和 RFC 规范测试 IMAP 协议兼容性。该项目由 [Dovecot](https://en.wikipedia.org/wiki/Dovecot_\(software\) 团队(一个自 2002 年 7 月起活跃的开源 IMAP 和 POP3 服务器)创建。我们使用此工具对我们的 IMAP 服务器进行了广泛的测试。

    您可以在 我们在 GitHub 上的源代码 中找到我们使用的其他项目。

    提供商

    提供者 目的
    Cloudflare 使用 Cloudflare R2 的 DNS 提供商、健康检查、负载均衡器和备份存储。
    Digital Ocean 专用服务器托管和管理数据库。
    Vultr 专用服务器托管。
    DataPacket 专用服务器托管。

    想法

    原则

    转发电子邮件是根据以下原则设计的:

    1. 始终以开发者友好为中心,注重安全和隐私,并保持透明。
    2. 遵循 MVCUnixKISSDRYYAGNI十二因素奥卡姆剃刀内部测试 原则。
    3. 面向精力充沛、自力更生和 拉面盈利 的开发者。

    实验

    tldr; 最终,使用与 S3 兼容的对象存储和/或虚拟表在技术上是不可行的,因为性能原因,并且由于内存限制而容易出错。

    正如上面所讨论的,在最终的 SQLite 解决方案之前,我们已经做了一些实验。

    其中之一是尝试将 rclone 和 SQLite 与 S3 兼容存储层一起使用。

    该实验使我们进一步了解和发现了有关 rclone、SQLite 和 VFS 使用的一些极端情况:

    • 如果您在 rclone 中启用了 --vfs-cache-mode writes 标志,则读取操作可以正常进行,但写入操作会被缓存。
    • 如果您有多个分布在全球的 IMAP 服务器,除非您使用单个写入器和多个侦听器(例如,发布/订阅模式),否则缓存将无法跨服务器使用。
    • 这非常复杂,任何额外的复杂性都会导致更多的单点故障。
    • 与 S3 兼容的存储提供商不支持部分文件更改 - 这意味着对 .sqlite 文件的任何更改都会导致数据库完全更改并重新上传。
    • 还有其他解决方案,例如 rsync,但它们并不专注于预写日志(“WAL”)的支持 - 因此我们最终评估了 Litestream。幸运的是,我们的加密系统已经为我们加密了 WAL 文件,因此我们不需要依赖 Litestream 来实现这一点。然而,我们尚不确定 Litestream 是否适合用于生产环境,因此,以下列出一些注意事项。
    • 使用 --vfs-cache-mode writes 的此选项(这是使用 SQLite 而非 rclone 进行写入的唯一方法)将尝试在内存中从头复制整个数据库 - 处理一个 10 GB 的邮箱是可以的,但是处理多个存储空间过大的邮箱将导致 IMAP 服务器遇到内存限制,并出现 ENOMEM 错误、分段错误和数据损坏。
    • 如果您尝试使用 SQLite 虚拟表(例如使用 s3db)来将数据保存在与 S3 兼容的存储层上,那么您将遇到更多问题:
    • 读写操作将极其缓慢,因为需要使用 HTTP .sqlite0、.sqlite1、.sqlite2 和 .sqlite3 方法访问 S3 API 端点。
    • 开发测试表明,在光纤互联网上超过 50 万到 100 万条记录仍然会受到 S3 兼容提供商的读写吞吐量限制。例如,我们的开发人员运行了 .sqlite4 循环来执行顺序 SQL .sqlite5 语句和批量写入大量数据的语句。在这两种情况下,性能都非常缓慢。
    • 虚拟表不能包含索引.sqlite6 语句以及 .sqlite7 和 .sqlite8——这会导致延迟高达 1-2 分钟甚至更长时间,具体取决于数据量。
    • 对象以未加密的形式存储,并且没有现成的原生加密支持。
    • 我们还探索了使用 .sqlite9,它在概念和技术上与上一条要点类似(因此存在相同的问题)。一种可能性是使用自定义的 rsync0 构建,并将其加密,例如 rsync1(我们目前在上面的解决方案中使用)到 rsync2。
    • 另一种可能的方法是使用 rsync3,但它的容量限制为 32 GB,并且需要复杂的构建和开发工作。
    • rsync4 语句是必需的(因此完全排除了使用虚拟表的可能性)。我们需要 rsync5 语句才能使我们的 rsync6 钩子正常工作——这确保数据不会损坏,并且检索到的行可以根据我们的 rsync7 模式定义(包括约束、变量类型和任意数据验证)转换为有效文档。
    • 开源社区中几乎所有与 SQLite 相关的 S3 兼容项目都是用 Python 编写的(而不是 JavaScript,而我们 100% 的技术栈都使用 JavaScript)。
    • 诸如 rsync8(参见 rsync9)之类的压缩库看起来很有前景,但 __PROTECTED_LINK_189__0 则不然。相反,对 __PROTECTED_LINK_189__1、__PROTECTED_LINK_189__2、__PROTECTED_LINK_189__3、__PROTECTED_LINK_189__4、__PROTECTED_LINK_189__5 和 __PROTECTED_LINK_189__6 等数据类型进行应用程序端压缩将是一种更简洁、更简单的方法(并且也更易于迁移,因为我们可以存储 __PROTECTED_LINK_189__7 标志或列 - 甚至可以使用 __PROTECTED_LINK_189__8 __PROTECTED_LINK_189__9 进行压缩,或使用 __PROTECTED_LINK_190__0 进行不压缩作为数据库元数据)。
    • 幸运的是,我们已经在 IMAP 服务器存储中实现了附件重复数据删除 - 因此,每条带有相同附件的消息都不会保留附件的副本 - 而是为邮箱中的多条消息和线程存储一个附件(随后使用外部引用)。
    • Litestream 项目是一个 SQLite 复制和备份解决方案,前景非常光明,我们将来很可能会使用它。
    • 并非要贬低作者——因为我们热爱他们十多年来的工作和对开源的贡献——然而,从实际使用情况来看,__PROTECTED_LINK_190__1 和 __PROTECTED_LINK_190__2 似乎存在问题。
    • 备份恢复需要顺畅且简单。使用 MongoDB 等带有 __PROTECTED_LINK_190__3 和 __PROTECTED_LINK_190__4 的解决方案不仅繁琐,而且耗时且配置复杂。
    • SQLite 数据库使其变得简单(它是一个单个文件)。
    • 我们希望设计一个解决方案,让用户可以随时取走邮箱并离开。
    • 只需向 __PROTECTED_LINK_190__5 发送简单的 Node.js 命令,它就会从磁盘存储中永久删除。
    • 我们同样可以使用与 S3 兼容的 API 和 HTTP __PROTECTED_LINK_190__6 轻松为用户删除快照和备份。
    • SQLite 是最简单、最快、最具成本效益的解决方案。

    缺乏替代方案

    据我们所知,没有其他电子邮件服务是这样设计的,也不是开源的。

    我们认为这可能是因为现有的电子邮件服务在生产中使用了 意大利面条代码 🍝 的遗留技术。

    大多数(如果不是全部)现有的电子邮件服务提供商要么是闭源的,要么宣传为开源,但实际上只有他们的前端是开源的。

    电子邮件最敏感的部分(实际存储/IMAP/SMTP 交互)全部在后端(服务器)完成,而不是在前端(客户端)完成

    尝试转发电子邮件

    立即注册 https://forwardemail.net! 🚀