For the complete documentation index, see llms.txt. This page is also available as Markdown.

OpenZFS 加密功能登陆 FreeBSD

OpenZFS GELI

OpenZFS on FreeBSD 新增的特性中,包含一个精巧的实现:在数据集层面采用行业标准加密。这意味着此前由 GELI 主导的 FreeBSD 存储加密生态迎来了新的竞争者:OpenZFS “原生加密”。本文从技术和可用性两个角度比较它与 GELI 的异同,帮助你判断哪种加密方案更适合自己(剧透:多数情况下是新方案)。

GELI

GELI 一段时间以来是 FreeBSD 主要的本地全盘加密系统。GELI 使用 AES(通常是 AES-256,采用 XTS 或 CBC 模式,这两种模式特别适合磁盘数据加密),配合 SHA-256 哈希(与比特币所用的哈希相同)和消息认证。为此,GELI 借助 FreeBSD 的 crypto(9) 子系统提供硬件卸载,在支持 AES-NI 或其他加密加速的系统上(基本上是任何较新的平台)大幅提升性能。

然而,GELI 与 ZFS 毫无关系。GELI 加密的是整个设备,或者更细粒度地,设备上的分区。因此,首次使用任何以此方式保护的文件系统前,相关设备或分区必须“解锁”。解锁后,底层文件系统被识别并挂载,通常在整个运行期内保持。解锁过程由一个主密钥和至多两个用户密钥管理,后者涉及密钥文件和口令。这套复杂的密钥组合允许对 GELI 分区重新加密密钥(例如某个密钥泄露时)而不必重新加密数据。

FreeBSD 引导代码中集成了 GELI,因此只有一个小型 freebsd-boot 分区保持未加密,其中仅含足够代码用于提示输入 GELI 密钥口令、解密引导分区,并从底层 ZFS(或 UFS)文件系统读取引导加载程序或内核。这意味着整个文件系统(连同相关驱动器上每一字节数据)都被加密,只有那小块启动代码除外。

只要系统管理员正确实施,GELI 使用强加密、强模式、在数据密钥过度使用前轮换,并在实现中贯彻众多现代安全实践要素。因此 GELI 现在和将来都是一款在所有关键领域遵循最佳实践的安全加密实现。

OpenZFS 加密功能登陆 FreeBSD

常规块指针

+-------+-------+-------+-------+-------+-------+-------+-------+
|            vdev1              | GRID  |        ASIZE          |
+-------+-------+-------+-------+-------+-------+-------+-------+
|G|                       offset1                               |
+-------+-------+-------+-------+-------+-------+-------+-------+
|            vdev2              | GRID  |        ASIZE          |
+-------+-------+-------+-------+-------+-------+-------+-------+
|G|                      offset2                                |
+-------+-------+-------+-------+-------+-------+-------+-------+
|            vdev3              | GRID  |        ASIZE          |
+-------+-------+-------+-------+-------+-------+-------+-------+
|G|                      offset3                                |
+-------+-------+-------+-------+-------+-------+-------+-------+
|BDX|lvl| type  | cksum |E| comp|     PSIZE     |     LSIZE     |
+-------+-------+-------+-------+-------+-------+-------+-------+
|                        padding                                |
+-------+-------+-------+-------+-------+-------+-------+-------+
|                        padding                                |
+-------+-------+-------+-------+-------+-------+-------+-------+
|                        physical birth txg                     |
+-------+-------+-------+-------+-------+-------+-------+-------+
|                        logical birth txg                      |
+-------+-------+-------+-------+-------+-------+-------+-------+
|                        fill count                             |
+-------+-------+-------+-------+-------+-------+-------+-------+
|                        checksum[0]                            |
+-------+-------+-------+-------+-------+-------+-------+-------+
|                        checksum[1]                            |
+-------+-------+-------+-------+-------+-------+-------+-------+
|                        checksum[2]                            |
+-------+-------+-------+-------+-------+-------+-------+-------+
|                        checksum[3]                            |
+-------+-------+-------+-------+-------+-------+-------+-------+
/*
 * 图例:
 *
 * vdev         虚拟设备 ID
 * offset       虚拟设备中的偏移
 * LSIZE        逻辑大小
 * PSIZE        物理大小(压缩后)
 * ASIZE        已分配大小(包括 RAID-Z 校验和填充)
 * GRID         RAID-Z 布局信息(保留供将来使用)
 * cksum        校验和函数
 * comp         压缩函数
 * G            gang 块指示符
 * B            字节序(endianness)
 * D            去重
 * X            加密
 * E            blkptr_t 含嵌入式数据
 * lvl          间接层级
 * type         DMU 对象类型
 * phys birth   dva[0] 写入时的 txg;若与逻辑诞生 txg 相同则为零
 *              注意,通常所有 dva 都会在该 txg 中写入,
 *              但若被设备移除操作搬移则可能不同。
 * log. birth   块逻辑上诞生的交易组
 * fill count    bp 之下非零块的数量
 * checksum[4]   bp 描述的数据的 256 位校验和
 */

OpenZFS 原生加密

到 2019 年年中,ZFS-on-Linux(ZoL)0.8 引入原生加密支持,即在受支持的 Linux 平台上对 ZFS 数据集本身加密。多亏 ZoL 和 FreeBSD 双方多位贡献者的努力,这项重要能力即将在 2020 年夏天前后随 FreeBSD 13 抵达 FreeBSD。

文件系统加密方案层面,我们不像 GELI 那样加密整个设备。这本身就很吸引人——常见的需求是只对敏感数据子集加密,而这些数据本来就可能自然隔离到独立数据集中!原生加密完全在数据集内运作,表现得就像又一个 ZFS 数据集属性。令人惊叹的是,ZFS 数据结构的基础布局无需扩展就能容纳加密!原生加密通过巧妙地重新利用 ZFS 块指针结构中的空闲空间实现。

加密块指针

熟悉 ZFS 数据集属性的人想必遇到过参数 copies。它默认为 1,也可选 2(少见)或 3(几乎从不)。恰巧,数据结构中指向第三份副本(仅在几乎从不使用的 copies=3 情况下相关)的空间,恰好大小合适,可实现基于 AES 的加密策略。所以,以失去少用的三副本存储能力(外加校验!)为代价,我们换来了文件系统加密。与其他数据集属性一样,该属性由子数据集继承。事实上,加密属性与其他熟悉的 ZFS 数据集属性唯一显著的区别是,使用加密必须(理所当然地)在创建时设置,且不能取消。因此,数据集内的 ZFS 数据和元数据在挂载前以加密形式静止保存,但数据集的存在以及该数据集的 ZFS 属性(如 logicalused 以及其他可能揭示所含数据规模和范围的属性)必然始终对操作系统可见。

ZFS 原生加密最颠覆性的特性是:令人惊讶的是,数据集可在未挂载(因此仍处于加密状态)时进行 scrub 或 resilver。这一出人意料的能力通过将原本的 256 位哈希字段拆分为仍绰绰有余的 128 位哈希和 128 位消息认证码(MAC)实现。这意味着 ZFS 管理员与 GELI 情形不同,无需访问客户端解密密钥即可执行 ZFS 维护任务——这的确是个诱人的特性。

此外,敏感数据暂时无需访问时,数据集可以照例卸载,随后变为加密静止状态,无密钥者无法访问;而 GELI 通常在引导时解密整个文件系统并在整个运行期内保持解密,难以做到这一点。再者,GELI 整个文件系统由一组密钥保护,对不同用户的访问控制粒度很粗——用户要么能访问整个系统,要么什么都访问不了。原生 ZFS 数据集加密则可轻松为不同数据集配备不同密钥,并按合适的访问组合分发给指定用户。

引入 ZFS 原生加密的同时,ZFS send 也具备了发送“原始”数据块的能力,即数据块不被以任何方式解释,原样发送到接收方。对于原生加密数据集,这意味着无需加载密钥,数据块在网络上传输期间可保持加密,即便该传输(无意或有意地)本身未加密。这让敏感数据可以备份到远程 ZFS 系统,而远程系统或其管理员(以及前文所说的本地管理员)都永远无法访问明文。相比基于 GELI 的方案,这是显著的安全态势改进。

(注意:FreeBSD 引导代码尚不支持从加密数据集引导,但未来很可能成为可能!)

体验 OpenZFS 2.0

撰写本文时,OpenZFS 2.0 距离发布并合入 FreeBSD 源代码树大概还有几个月。等不及的话,可在 FreeBSD 12.1(及更高版本)上用 sysutils/openzfs 软件包安装预发布版。若不用 -RELEASE,最好从 Ports 编译 sysutils/openzfssysutils/openzfs-kmod(因为模块必须与将运行的内核精确匹配)。安装后,把 /boot/loader.conf 中的 zfs_load="YES" 替换为 openzfs_load="YES"。Port 或软件包会安装到标准 Ports 前缀 /usr/local,因此可能需要调整 PATH 环境变量,确保 shell 先找到 /usr/local/sbin/zfs 而非系统提供的 /sbin/zfs。一旦池中包含任何加密数据集,池本身(包括其未加密数据集)就再也无法在不支持加密特性标志的系统上导入。然而正如新 ZFS 特性常见的那样,一旦使用加密特性的最后一个数据集被销毁,加密特性标志会从“active”回退为“enabled”,池又能被旧版 ZFS 导入。提醒:用 OpenZFS 2.0 创建的池会启用新特性,可能不易向下兼容,因此此类实验应谨慎进行。zpool 手册页描述了如何在创建池时选择性地禁用单个特性标志。

把数据集迁移到 OpenZFS 原生加密

要从现有未加密数据集迁移数据(记住:GELI 加密的设备在文件系统层包含未加密数据集,因此也适用于 GELI 池!),先创建一个启用 OpenZFS 原生加密的新数据集:

然后,对原数据集创建快照,用 ZFS 复制把内容复制到新的加密数据集。完成后,可销毁原数据集,把加密数据集改回原名称,并更新挂载点。别忘了考虑由此产生的未分配空间的潜在安全影响——这些空间可能仍含未加密版本的数据;你可能想覆盖此空间。zpool initialize 命令是合理的实现方式。

若从 GELI 迁移,回忆一下 GELI 位于文件系统之前的层,因此不与文件系统交互。因此,移除 GELI 的唯一方法是让每个 GELI 加密的设备(或分区)重新以未加密设备的形式出现。所幸 ZFS 本身可以助我们一臂之力。例如,冗余度足够的池可以有策略地交替使用 geli killzpool replace 命令序列,逐一移除每个设备的 GELI 层。对于 ZFS 冗余度良好的池,这相对容易,可以安全地在线完成。对于冗余度不足或无冗余的池,此过程将需要(或更安全地配合使用)一块容量合适的临时辅助盘。

结论

GELI 很棒,陪伴我们多年。它加密整个设备(多数情况下包括其上的操作系统),例如你想保护被盗(或被没收)的笔记本或返修磁盘上的每一字节时,它无疑是上佳之选。然而,作为安全方案它的粒度很粗,而且让“已加密”数据在系统整个运行期内保持可访问状态存在相当大的风险。例如,黑客若获得 GELI 模式下挂载设备的端点访问权,原则上可全盘访问数据。

OpenZFS 原生加密则给管理员更大控制权——加密粒度更细,可灵活组合用户访问。它还允许未使用的加密数据集单独卸载,处于受保护的“静止”状态,而系统其余部分仍可访问。对于存放敏感数据、运行时间可能长达数月甚至数年的文件服务器而言,这比 GELI 更合理。若黑客获得不受限的端点访问权(物理或网络),他在数据集未挂载时看到的内容,与无特权用户看到的一样。OpenZFS 原生加密目前唯一的缺点是必然泄露其所加密数据性质的部分信息:数据集是否存在、数据集名称、任何快照的名称、数据量、可能的创建时间、可压缩程度等等。通常而言,这类信息无关紧要——某台服务器存放例如 4000 名患者的健康记录或监控视频,这本身并不敏感,但这些记录和视频的实际内容才敏感。同样难以反驳的是,任何要求更少系统管理员(相对于内容所有者)能访问数据的加密方案都更可取。

很难想象除上述被盗笔记本场景外,还有什么场景下 GELI 像 OpenZFS 原生加密那样合理、好用、可扩展。GELI 作为设备级加密方案当然会继续作为可信可靠的解决方案陪伴我们,但粒度精细到单个数据集的基于 ZFS 的加密方案极具吸引力,很可能成为今后 ZFS 用户期望的标配。


ALLAN JUDE 是 Klara Inc. 的工程副总裁,Klara Inc. 是一家全球性的 FreeBSD 专业服务与支持公司。他还主持每周一期的重要 BSD 播客 BSDNow.tv,并于 2016 和 2018 年当选 FreeBSD 核心团队。他与 Michael W Lucas 合著了《FreeBSD Mastery: ZFS》和《FreeBSD Mastery: Advanced ZFS》。

KYLE “DRKK” KNEISL 拥有数学博士学位,在华盛顿特区担任科学家。他自 2013 年起是 FreeBSD 和 FreeNAS 社区的活跃成员与倡导者。

最后更新于