KeePass kdbx4 文件解密

KDBX 解密过程

为了简化解密步骤，解密过程没有进行数据校验

输入密码，对密码进行 sha256 哈希计算得到密码哈希值

密码哈希值加上 key 文件内容再次进行 sha256 哈希计算，如果没有 key 文件就密码哈希值再次进行 sha256 哈希计算得到新的密码哈希值，也就是 SHA256 (SHA256 (password) + keyfile)

从文件头得到加密算法 KDF 相关参数，默认是 AES-KDF, 从 KDF parameters 中得到 KDF 的 Salt/seed 和 Rounds

使用二次 sha256 密码得到的哈希值，还有 KDF 的 Salt/seed 和 Rounds, 进行 AES-KDF 计算，得到转换后的密钥

从文件头得到 Master salt/seed 和转化后的密钥再次进行 sha256 计算，得到最终的解密密钥

从文件头得到 Encryption IV/nonce, 加上解密密钥对加密数据进行 AES 解密

解密后的数据进行 Gzip 解压，得到 Inner Header 和 XML 内容，XML 中的密码默认使用 ChaCha20 加密

从 Inner Header 中得到 Inner encryption key, 使用 sha512 进行哈希计算，哈希字符串索引从 0 到 31 字节为 ChaCha20 的 key, 32 到 43 为 ChaCha20 加密的 nonce

根据 ChaCha20 的 key 和 nonce 解密加密字符串得到明文密码，由于是流密码，所以需要根据加密字符串顺序依次进行解密

创建 kdbx 文件

使用 KeePass 客户端创建 kdbx 文件，全部使用默认参数，使用 Hex 编辑器打开 kdbx 文件，根据 KeePass 文档KDBX File Format Specification - KeePass将文件头部拆分如下图

十六进制详细分析

1. header

文件开头 12 个字节分成三组，每组都是 UInt32 类型，占用四个字节，之后为多个 Fields

Fields 由 id, 值长度，值组成，id 为 1 个字节，值长度为为 Int32 类型，占用 4 个字节，根据值长度截取对应字节数据作为值

详细文件格式说明参考KDBX File Format Specification - KeePass

kdbx 文件使用小端序字节序，数字类型需要从右至左读取

Signature1

Signature1 是固定值0x9AA2D903, 类型为 UInt32, 占用 4 字节

Signature2

Signature2 是固定值0xB54BFB67, 类型为 UInt32, 占用 4 字节

Format version

Format version 是文件格式版本，类型为 UInt32, 占用 4 字节，当前为类型为0x00040000, 当前版本为 4

Encryption algorithm

0x02转 Int32 为 2, id 为 2 对应 Encryption algorithm, 表示加密算法，类型为 UUID, 长度0x00000010转 Int32 为 16, 表示长度为 16 个字节，UUID 为 16 进制字符串，值为31C1F2E6BF714350BE5805216AFC5AFF, 对应上图中的 AES-256

Compression algorithm

0x03转 Int32 为 3, id 为 3 对应 Compression algorithm, 表示压缩算法，类型为 UInt32, 长度0x00000004转 Int32 为 4, 表示值长度为之后 4 个字节，16 进制0x00000001转 UInt32 为1, 表示使用 GZip 压缩

Master salt/seed

0x04转 Int32 为 4, id 为 4 对应 Master salt/seed, 表示加密的盐 / 种子，类型为 32 个字节的数组，长度0x00000020转 Int32 为 32, 表示值长度为之后 32 字节，对应字节数组为[0xFA, 0xC9, 0x50, 0x39, 0x79, 0x5F, 0x19, 0x83, 0x21, 0x22, 0x8F, 0x24, 0x8F, 0x59, 0x63, 0xD5, 0x30, 0x38, 0x7A, 0x55, 0xD8, 0x08, 0xD5, 0x19, 0xC4, 0xD6, 0xEC, 0xEC, 0xEE, 0xDF, 0xA8, 0x24], 这是 AES 加密用的盐

KDF parameters

0x0B转为 Int32 为 11, 对应 KDF parameters, 表示密钥派生函数 (KDF) 的参数，长度0x0000005D转 Int32 为 93, 表示值为之后 93 个字符，值类型为 Variant Dictionary, 翻译为变体词典，可以理解为特殊的对象结构，可以拆分为版本信息和多个词典对象组成的数组，词典对象由值类型，名称长度，名称，值长度，值组成

可将 Variant Dictionary 进行如下拆分，最后一个字节0x00没有实际用途

KDF Format version

首先是 UInt16 类型的版本信息，值为0x0100, 占用 2 字节

KDF UUID

0x42表示 [] byte 类型，0x000005转 Int32 为 5, 表示名称长度 5 个字节，0x2455554944对应 ASCII 字符串为$UUID, 之后0x00000010转 Int32 为 16, 表示 UUID 长度为 16 个字符，对应 16 进制字符串是C9D9F39A628A4460BF740D08C18A4FEA, 默认使用 AES-KDF

KDF Rounds

0x05表示 UInt64 类型，0x000001转 Int32 为 1, 表示名称长度为 1 个字节，0x52对应 ASCII 字符为R, 值长度0x000008转 Int32 为 8, 表示 Rounds 值为之后 8 个字节，0x00000000000927C0转 UInt64 为 600000, 表示迭代次数为 600000

KDF Salt/seed

0x42表示 [] byte 类型，0x000001转 Int32 为 1, 表示名称长度 1 个字节，0x53对应 ASCII 字符串S, 之后0x00000020转 Int32 为 32, 表示 Salt/Seed 长度为 32 个字符，对应字节数组为[0x0C, 0x34, 0x88, 0x19, 0x8E, 0x90, 0xA3, 0x65, 0x40, 0xB4, 0x1C, 0x4E, 0xCC, 0x85, 0x5D, 0x11, 0xC3, 0x0A, 0x20, 0xD6, 0xF4, 0xAA, 0x20, 0xE4, 0xEC, 0xF3, 0xCF, 0xF3, 0xD9, 0x81, 0xC5, 0xED], 也是就是 KDF 加密用的盐

Encryption IV/nonce

0x07转为 Int32 为 7, id 为 7 对应 Encryption IV/nonce, 表示加密算法的初始化向量或随机数，长度0x00000010转 Int32 为 16, 表示值为之后的 16 个字节组成的数组，初始化向量为字节数组[0x6C, 0x77, 0x12, 0xF7, 0xE2, 0xD0, 0x2E, 0xAE, 0x63, 0x9D, 0x53, 0x92, 0xD6, 0x03, 0xFD, 0x08]

End of header

0x00转 Int32 为 0, id 为 0 对应 End of header, 表示头部结束，长度0x00000004转 Int32 为 4, 值为之后的 4 个字节，是固定值[0x0D, 0x0A, 0x0D, 0x0A]

2. SHA-256 hash of the header

在[0x0D, 0x0A, 0x0D, 0x0A]之后的 32 个字节为 header 的 SHA-256 哈希值，用来校验头部信息的完整性

3. HMAC-SHA-256 hash of the header

在 hash of the header 之后的 32 个字节为 header 的 HMAC-SHA-256 值，需要主密钥才能完成校验

4. In HMAC-protected block stream

Unknown Data

未知部分，解密与校验未使用这部分数据，文档没有相关说明，解密需要跳过这部分，否则会出错

Encrypted Data

之后到文件结尾为加密部分 (需要去掉最后四个字节0x00000000)
解密后得到 Gzip 压缩的数据，Gzip 压缩内容的头部是0x1F8B

Gzip Decompress

进行 Gzip 解压后得到 Inner Header 和 XML 内容

Inner Header

Inner encryption algorithm

0x01转 Int32 为 1, id 为 1 对应 Inner encryption algorithm, 表示内部加密算法，长度0x00000004转 Int32 为 4, 表示值长度为 4 字节，值0x0000003 转 Int32 为 3, 表示使用 ChaCha20 加密

Inner encryption key

0x02转 Int32 为 2, id 为 2 对应 Inner encryption key, 表示内部加密密钥，长度0x00000040表示长度为 64 字节的数组，也就是[0x68, 0x34, 0xD6, 0x1D, 0xFA, 0x98, 0x64, 0xB1, 0x8A, 0x51, 0x05, 0xAB, 0x26, 0x10, 0x35, 0xC8, 0xB0, 0x22, 0xF5, 0x14, 0x91, 0x0F, 0x47, 0x8C, 0x3D, 0xE7, 0x63, 0x86, 0x9C, 0x87, 0xC1, 0x26, 0xC6, 0xB7, 0xDE, 0x59, 0x77, 0x3B, 0xC6, 0x13, 0xF7, 0x17, 0x6C, 0x05, 0x7E, 0x1C, 0xF3, 0xDC, 0x78, 0x25, 0x64, 0xCA, 0x44, 0x7E, 0xFD, 0xB4, 0x6F, 0xEB, 0x4D, 0xDC, 0xD5, 0x91, 0x7B, 0x5C]

此处的值需要进行 sha512 哈希计算，然后哈希值数组索引从 0 到 31 是 key, 32 到 43 是 nonce

XML document

XML 文档中密码包含Protected="True"属性，需要将该字符串先进行 Base64 解码，然后进行 ChaCha20 解密，由于是流密码，所以解密后面的密码需要之前的密码流