Restic设计原理( 二 )


所有 blob(EncryptedBlob1、`EncryptedBlobN 等)都经过独立鉴权和加密 。存储库可以在无需解密Blob情况下,进行重组 。此外,还可以进行高效的索引,因为只需要读取header,找出Pack中包含哪些Blob 。因为header是经过鉴权的,因此可以检查header的真实性,而无需读取完整的Pack 。
解密后,Pack 的头部由以下元素组成:
Type_Blob1 ||Length(EncryptedBlob1) ||Hash(Plaintext_Blob1) ||[...]Type_BlobN ||Length(EncryptedBlobN) ||Hash(Plaintext_Blobn) ||这足以计算Pack中所有 Blob 的偏移量 。长度是 Blob 的长度,它是 little-endian 格式的四字节整数 。type 字段是一个单字节字段,根据下表标记 blob 的内容:
Type
Meaning
0
data
1
tree
所有其他类型均无效,未来可能会添加更多类型 。
为了重建索引或解析没有索引的Pack,首先必须读取最后四个字节才能找到header的长度 。之后,可以读取和解析header,这会产生所有已包含的 blob 的明文哈希、类型、偏移量和长度 。
索引(indexing)索引文件包含有关数据和树Blob树及其所在Pack的信息,并将此信息存储在存储库中 。当本地缓存索引不再可用时,可以下载索引文件并重建索引 。这些文件像数据和Blob树同样经过加密和鉴权,因此外部结构仍然是 IV || 密文 || Mac 。明文由如下 JSON 文档组成:
{"supersedes": ["ed54ae36197f4745ebc4b54d10e0f623eaaaedd03013eb7ae90df881b7781452"],"packs": [{"id": "73d04e6125cf3c28a299cc2f3cca3b78ceac396e4fcf9575e34536b26782413c","blobs": [{"id": "3ec79977ef0cf5de7b08cd12b874cd0f62bbaf7f07f3497a5b1bbcc8cb39b1ce","type": "data","offset": 0,"length": 25},{"id": "9ccb846e60d90d4eb915848add7aa7ea1e4bbabfc60e573db9f7bfb2789afbae","type": "tree","offset": 38,"length": 100},{"id": "d3dc577b4ffd38cc4b32122cabf8655a0223ed22edfd93b353dc0c3f2b0fdf66","type": "data","offset": 150,"length": 123}]}, [...]]}此 JSON 文档列出了Packs和其中包含的 blob 。在此示例中,Pack 73d04e61 包含两个数据 Blob 和一个 Blob树,明文哈希随后列出 。
字段 supersedes 列出了已被当前索引文件替换的索引文件的存储 ID 。这发生在重新打包索引文件时,例如删除旧快照并重新组合包时 。
可能有任意数量的索引文件,其中包含关于不相交的Pack集合的信息 。选择在单一文件中描述的Packs数量,让文件大小保持在8MiB一下 。
密钥(Keys),加密和MAC所有存放在restic存储库中的数据使用计数器模式AES-256加密,鉴权使用Poly1305-AES 。为了加密新数据,从密码安全的伪随机数生成器中读取前 16 个字节作为随机随机数 。这既用作计数器模式的 IV,也用作 Poly1305 的随机数 。该操作需要三个密钥:用于加密的 AES-256 的 32 字节、用于 Poly1305 的 16 字节的 AES 密钥和 16 字节的密钥 。有关详细信息,请参阅 Dan Bernstein 的原始论文 The Poly1305-AES 消息身份验证代码 。然后使用 AES-256 对数据进行加密,然后在密文上计算消息验证码 (MAC),然后将所有内容存储为 IV || 密文 || MAC 。
【Restic设计原理】目录 keys 包含密钥文件 。这些是简单的 JSON 格式文档,包含从用户密码派生存储库的主加密和消息身份验证密钥所需的所有数据 。例如,可以使用 Python 模块 json(缩短以提高可读性)来优化打印存储库中的 JSON 文档:
$ python -mjson.tool /tmp/restic-repo/keys/b02de82*{"hostname": "kasimir","username": "fd0""kdf": "scrypt","N": 65536,"r": 8,"p": 1,"created": "2015-01-02T18:10:13.48307196+01:00","data": "tGwYeKoM0C4j4/9DFrVEmMGAldvEn/+iKC3te/QE/6ox/V4qz58FUOgMa0Bb1cIJ6asrypCx/Ti/pRXCPHLDkIJbNYd2ybC+fLhFIJVLCvkMS+trdywsUkglUbTbi+7+Ldsul5jpAj9vTZ25ajDc+4FKtWEcCWL5ICAOoTAxnPgT+Lh8ByGQBH6KbdWabqamLzTRWxePFoYuxa7yXgmj9A==","salt": "uW4fEI1+IOzj7ED9mVor+yTSJFd68DGlGOeLgJELYsTU5ikhG/83/+jGd4KKAaQdSrsfzrdOhAMftTSih5Ux6w==",}当打开restic存储库时,会提示用户输入存储库密码 。然后将其与 scrypt、密钥派生函数 (KDF) 和提供的参数(N、r、p 和salt)一起派生 64 个密钥字节 。前 32 个字节用于加密密钥(用于 AES-256),最后 32 个字节用作消息验证密钥(用于 Poly1305-AES) 。这最后 32 个字节被分成一个 16 字节的 AES 密钥“k”,然后是 16 个字节的密钥“r” 。然后将密钥r 屏蔽,与 Poly1305 一起使用(有关详细信息,请参阅论文) 。


推荐阅读