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 解密,由於是流密碼,所以解密後面的密碼需要之前的密碼流