yzbtdiy

yzbtdiy

github
bilibili

KeePass kdbx4 文件解密

KDBX 解密过程#

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

  1. 输入密码,对密码进行 sha256 哈希计算得到密码哈希值
  2. 密码哈希值加上 key 文件内容再次进行 sha256 哈希计算,如果没有 key 文件就密码哈希值再次进行 sha256 哈希计算得到新的密码哈希值,也就是 SHA256 (SHA256 (password) + keyfile)
  3. 从文件头得到加密算法 KDF 相关参数,默认是 AES-KDF, 从 KDF parameters 中得到 KDF 的 Salt/seed 和 Rounds
  4. 使用二次 sha256 密码得到的哈希值,还有 KDF 的 Salt/seed 和 Rounds, 进行 AES-KDF 计算,得到转换后的密钥
  5. 从文件头得到 Master salt/seed 和转化后的密钥再次进行 sha256 计算,得到最终的解密密钥
  6. 从文件头得到 Encryption IV/nonce, 加上解密密钥对加密数据进行 AES 解密
  7. 解密后的数据进行 Gzip 解压,得到 Inner Header 和 XML 内容,XML 中的密码默认使用 ChaCha20 加密
  8. 从 Inner Header 中得到 Inner encryption key, 使用 sha512 进行哈希计算,哈希字符串索引从 0 到 31 字节为 ChaCha20 的 key, 32 到 43 为 ChaCha20 加密的 nonce
  9. 根据 ChaCha20 的 key 和 nonce 解密加密字符串得到明文密码,由于是流密码,所以需要根据加密字符串顺序依次进行解密

创建 kdbx 文件#

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

image-20240822172200397

十六进制详细分析#

image-20240822113406561

1. header#

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

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

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

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

Signature1#

image-20240821190624226
image-20240821174503304

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

Signature2#

image-20240821190749110
image-20240821180053815

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

Format version#

image-20240821190950704
image-20240821180207025

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

Encryption algorithm#

image-20240821181812965
image-20240821180719734

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

Compression algorithm#

image-20240821185423228
image-20240821182532355

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

Master salt/seed#

image-20240821190432826
image-20240821190323382

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#

image-20240821194315863
image-20240821194224498

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

image-20240821194643768

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

image-20240822091916730

KDF Format version#

image-20240822092645747

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

KDF UUID#

image-20240822094338216
image-20240822093441124

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

KDF Rounds#

image-20240822100518464
image-20240822100401677

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

KDF Salt/seed#

image-20240822102413474
image-20240822102318666

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#

image-20240822111052671
image-20240822110900590

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#

image-20240822112151756
image-20240822112040258

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

2. SHA-256 hash of the header#

image-20240822114126587

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

3. HMAC-SHA-256 hash of the header#

image-20240822114637960

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

4. In HMAC-protected block stream#

Unknown Data#

image-20240822130720931

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

Encrypted Data#

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

image-20240823160036633

Gzip Decompress#

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

image-20240823162455324

Inner Header#

Inner encryption algorithm#

image-20240823162909414
image-20240823162938566

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

Inner encryption key#

image-20240823163118692
image-20240823163628791

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

image-20240823164208028

XML document#

image-20240823164722691

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

加载中...
此文章数据所有权由区块链加密技术和智能合约保障仅归创作者所有。