KDBX 解密プロセス#
データ検証を行わずに、解読手順を簡素化するために、解読プロセスを行います。
- パスワードを入力し、パスワードの sha256 ハッシュ計算を行い、パスワードハッシュ値を取得します。
- パスワードハッシュ値に key ファイルの内容を加え、再度 sha256 ハッシュ計算を行います。key ファイルがない場合は、パスワードハッシュ値を再度 sha256 ハッシュ計算して新しいパスワードハッシュ値を取得します。つまり、SHA256 (SHA256 (password) + keyfile) です。
- ファイルヘッダーから暗号化アルゴリズム KDF に関連するパラメータを取得します。デフォルトは AES-KDF で、KDF パラメータから 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に従ってファイルヘッダーを以下の図のように分割します。
16 進数の詳細分析#
1. ヘッダー#
ファイルの最初の 12 バイトは 3 つのグループに分けられ、各グループは UInt32 型で 4 バイトを占め、その後に複数のフィールドがあります。
フィールドは id、値の長さ、値から構成され、id は 1 バイト、値の長さは Int32 型で 4 バイトを占め、値の長さに基づいて対応するバイトデータを値として取得します。
詳細なファイル形式の説明はKDBX File Format Specification - KeePassを参照してください。
kdbx ファイルはリトルエンディアンバイトオーダーを使用し、数値型は右から左に読み取る必要があります。
Signature1#
Signature1 は固定値0x9AA2D903
で、型は UInt32、4 バイトを占めます。
Signature2#
Signature2 は固定値0xB54BFB67
で、型は UInt32、4 バイトを占めます。
フォーマットバージョン#
フォーマットバージョンはファイルフォーマットのバージョンで、型は UInt32、4 バイトを占め、現在のタイプは0x00040000
、現在のバージョンは 4 です。
暗号化アルゴリズム#
0x02
を Int32 に変換すると 2 になり、id は 2 で暗号化アルゴリズムを示し、型は UUID、長さ0x00000010
を Int32 に変換すると 16 になり、16 バイトの長さを示します。UUID は 16 進数の文字列で、値は31C1F2E6BF714350BE5805216AFC5AFF
、上の図の AES-256 に対応します。
圧縮アルゴリズム#
0x03
を Int32 に変換すると 3 になり、id は 3 で圧縮アルゴリズムを示し、型は 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 パラメータ#
0x0B
を Int32 に変換すると 11 になり、KDF パラメータに対応し、キー導出関数 (KDF) のパラメータを示します。長さ0x0000005D
を Int32 に変換すると 93 になり、値はその後の 93 文字です。値の型は Variant Dictionary で、変体辞書として理解できます。特別なオブジェクト構造であり、バージョン情報と複数の辞書オブジェクトからなる配列に分割できます。辞書オブジェクトは値の型、名前の長さ、名前、値の長さ、値から構成されます。
Variant Dictionary は以下のように分割できます。最後のバイト0x00
は実際には用途がありません。
KDF フォーマットバージョン#
最初は 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 暗号用の塩です。
暗号化 IV/nonce#
0x07
を Int32 に変換すると 7 になり、id は 7 で暗号化 IV/nonce を示し、暗号化アルゴリズムの初期化ベクトルまたはランダム数を示します。長さ0x00000010
を Int32 に変換すると 16 になり、値はその後の 16 バイトからなる配列です。初期化ベクトルはバイト配列[0x6C, 0x77, 0x12, 0xF7, 0xE2, 0xD0, 0x2E, 0xAE, 0x63, 0x9D, 0x53, 0x92, 0xD6, 0x03, 0xFD, 0x08]
です。
ヘッダーの終わり#
0x00
を Int32 に変換すると 0 になり、id は 0 でヘッダーの終わりを示し、長さ0x00000004
を Int32 に変換すると 4 になり、値はその後の 4 バイトで、固定値[0x0D, 0x0A, 0x0D, 0x0A]
です。
2. ヘッダーの SHA-256 ハッシュ#
[0x0D, 0x0A, 0x0D, 0x0A]
の後の 32 バイトはヘッダーの SHA-256 ハッシュ値で、ヘッダー情報の完全性を検証するために使用されます。
3. ヘッダーの HMAC-SHA-256 ハッシュ#
ヘッダーのハッシュの後の 32 バイトはヘッダーの HMAC-SHA-256 値で、検証を完了するには主キーが必要です。
4. HMAC 保護されたブロックストリーム内#
不明なデータ#
不明な部分で、解読と検証にはこの部分のデータは使用されず、文書には関連する説明がありません。解読するにはこの部分をスキップする必要があります。そうしないとエラーが発生します。
暗号化データ#
その後、ファイルの末尾までが暗号化部分(最後の 4 バイト0x00000000
を削除する必要があります)。
解読後、Gzip 圧縮データが得られ、Gzip 圧縮内容のヘッダーは0x1F8B
です。
Gzip 解凍#
Gzip 解凍を行い、Inner Header と XML 内容を取得します。
Inner Header#
内部暗号化アルゴリズム#
0x01
を Int32 に変換すると 1 になり、id は 1 で内部暗号化アルゴリズムを示し、長さ0x00000004
を Int32 に変換すると 4 になり、値の長さは 4 バイトです。値0x0000003
を Int32 に変換すると 3 になり、ChaCha20 暗号化を使用することを示します。
内部暗号化キー#
0x02
を Int32 に変換すると 2 になり、id は 2 で内部暗号化キーを示します。長さ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 文書#
XML 文書内のパスワードにはProtected="True"
属性が含まれており、この文字列を最初に Base64 デコードし、その後 ChaCha20 で解読する必要があります。ストリーム暗号であるため、解読後のパスワードは前のパスワードストリームが必要です。