Restic设计原理

Restic(
https://github.com/restic/restic)一款开源文件备份工具,存储文件前将文件切割成对象进行存储,可以支持多种存储后端,包括本地目录、SFTP、HTTP Server和多种云平台的对象存储 。Restic支持快照等备份常用功能,同时兼顾数据存储的安全性 。
本文主要讲解Restic设计原理,内容来源于Restic官方文档的Design篇(
https://restic.readthedocs.io/en/latest/100_references.html#design) 。
术语这一节主要介绍文档中使用的专业术语 。
存储库(Repository):备份过程中生成的所有数据都以结构化形式发送并存储在存储库中,例如:存储在文件系统中,文件系统中可以创建多级目录 。作为存储库,必须具备多种操作能力,例如列出内容 。
Blob: Blob是将数据与识别信息(如数据的 SHA-256 哈希值及其长度)组合在一起 。
包(Pack): 一个Pack将多个Blobs进行组合,例如在一个文件中
快照(Snapshot): 一个快照是文件和目录在某个备份时间点的状态 。状态的含义是内容以及元数据(metadata)信息,例如:文件或目录及其内容的名称、修改时间 。
存储ID(Storage ID):存储ID是存储库中存放内容的SHA-256 。只有得到此ID才能从存储库中加载文件 。
存储库格式所有数据存放在restic存储库中 。存储库可以存储不同类型的数据,可以根据ID获取 。”Storage ID”是文件内容的SHA-256哈希值 。存储库内所有文件仅仅写入一次,后续不再修改 。这可以让多个客户端并发访问,甚至是写入到存储库中 。仅仅只有删除操作将数据从存储库中移除 。
在撰写本文时,仅仅实现的存储库后端是基于文件系统的目录和文件 。可以在同一系统或者内置的SFTP客户端(或者其他的后端方式)访问存储库 。两种访问方法的目录布局相同 。存储库类型会在后续详细描述 。
存储库由多个目录和一个名为 config 的文件组成 。对于存储库中的其他所有文件,文件的名称是存储 ID 的小写十六进制表示,即文件内容的 SHA-256 哈希 。通过简单地运行程序 sha256sum 并将其输出与文件名进行比较,轻松验证文件是否有意外修改,例如磁盘读取错误 。如果文件名的前缀在同一目录中是唯一的,则可以使用前缀代替完整的文件名 。
除了存储在 keys 目录中的文件外,所有文件都在计数器模式 (CTR) 下使用 AES-256 进行加密 。加密数据的完整性由 Poly1305-AES 消息验证码(有时也称为“签名”)保护 。
在每个加密文件的前 16 个字节中,存储了初始化向量 (IV) 。其后是加密数据并由 16 字节 mac 结尾 。格式为:IV || 密文 || MAC 。完整的加密开销为 32 个字节 。对于每个文件,都会选择一个新的随机 IV 。
文件 config 以这种方式加密,并包含如下 JSON 文档:
{"version": 1,"id": "5956a3f67a6230d4a92cefb29529f10196c7d92582ec305fd71ff6d331d6271b","chunker_polynomial": "25b468838dcb75"}解密后,restic 首先检查版本字段是否包含它可以理解的版本号,否则会中止 。此时,版本预计为 1 。字段 id 包含一个由 32 个随机字节组成的唯一 ID,以十六进制编码 。无论是通过 SFTP 还是在本地访问它,这都会唯一标识存储库 。chunker_polynomial 字段包含一个参数,用于将大文件分割成更小的块(见下文) 。
restic 存储库的基本布局如下所示:
/tmp/restic-repo├── config├──data│├── 21││└── 2159dd48f8a24f33c307b750592773f8b71ff8d11452132a7b2e2a6a01611be1│├── 32││└── 32ea976bc30771cebad8285cd99120ac8786f9ffd42141d452458089985043a5│├── 59││└── 59fe4bcde59bd6222eba87795e35a90d82cd2f138a27b6835032b7b58173a426│├── 73││└── 73d04e6125cf3c28a299cc2f3cca3b78ceac396e4fcf9575e34536b26782413c│[...]├── index│├── c38f5fb68307c6a3e3aa945d556e325dc38f5fb68307c6a3e3aa945d556e325d│└── ca171b1b7394d90d330b265d90f506f9984043b342525f019788f97e745c71fd├── keys│└── b02de829beeb3c01a63e6b25cbd421a98fef144f03b9a02e46eff9e2ca3f0bd7├── locks├── snapshots│└── 22a5af1bdc6e616f8a29579458c49627e01b32210d09adb288d1ecda7c5711ec└── tmp存储库可以使用restic init命令初始化,例如:
$ restic -r /tmp/restic-repo init包(Pack)格式除 Key 和 Pack 文件外,存储库中的所有文件仅包含原始数据,存储为IV || 密文 || MAC 。包文件可能包含一个或多个 Blob 数据 。
一个包的结构如下:
EncryptedBlob1 || ... || EncryptedBlobN || EncryptedHeader || Header_LengthPack 文件的末尾是header,它描述了内容 。header经过加密和身份验证 。Header_Length 是加密头的长度,使用四字节整数little-endian编码 。将header放置在文件末尾,目的是为了在备份过程中,读取blob后立即将他们写入连续的流中 。这降低了代码的复杂度,并且避免了在包完成后,在已知内容和header长度后重复写入文件的问题 。


推荐阅读