mirror of
https://github.com/qtmleap/revkit.git
synced 2026-09-16 08:11:51 +02:00
find_phase2_hmac_chain.py に以下のセクションを追加:
- pre_appboot HMAC 中間値 (19def2f9/e60e376f/58c4e3d1) を PSK 代替として使用
- enc_key_0 (0817065e) を起点とした逆算セクション
- Phase 3 整合性確認: enc_key_0→enc_key_1, sign_key_0a→sign_key_1 の
KDF チェーンが正しく一致することを確認 (sign_key_0 = 91f752f7 を特定)
- enc_key_0 の DH 由来ブルートフォース
find_phase2_kdf_variants.py に以下のセクションを追加:
- enc_key_0 (0817065e) を別ターゲットとした全 KDF バリアント試行
- AES Key Wrap (RFC 3394) による key 33.6 アンラップ試行
tools/decrypt_key_response.py を新規作成:
- key 33.6 (96B) を [IV:16][CT:48][HMAC:32] 構造として仮定し復号試行
- 仮説 A/A2/B/C の全パターン (IV なし/あり, wrap_key 66 候補)
- 全 232 件の appboot レスポンスファイルへの一括適用
- HMAC 署名検証: HMAC(sign_key, IV||CT) == key336[64:96] の独立確認
- 平文のオフセット探索 (enc_key が先頭以外にある場合)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
565 lines
19 KiB
Python
565 lines
19 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Phase 2 初期セッション鍵の復号試行ツール
|
|
|
|
key 33.6 (96 bytes) の暗号文を候補鍵で復号し、
|
|
平文が既知のセッション鍵 (enc_key_0, sign_key_0) を含むか検証する。
|
|
|
|
構造仮説:
|
|
仮説 A: [CT:64B][HMAC:32B] — IV なし (zeros または別フィールド)
|
|
仮説 B: [IV:16B][CT:48B][HMAC:32B] — 最有力 (MSL 標準構造)
|
|
仮説 C: [IV:16B][CT:64B][HMAC:16B] — HMAC が 16B に短縮
|
|
|
|
テストベクター:
|
|
DH shared_secret = 052a8bfe... (128 bytes)
|
|
PSK = 027617984f6227539a630b897c017d69
|
|
nonce (hardcode) = 809f82a7addf548d3ea9dd067ff9bb91
|
|
server nonce = e73104a8f4a9ed430d90a330d7978432
|
|
client nonce = a97e47477522ab39e39b322bdf818031
|
|
|
|
既知セッション鍵 (ターゲット):
|
|
enc_key_0 = 0817065e29e6d1c8668473af9e13b3c2 (DH 直後最初の AES 鍵)
|
|
enc_key_1 = 97b99f4e88e8e73779aa20ac11877c5d (Phase 3 KDF 出力)
|
|
sign_key_0 = 91f752f76d7ab4c2dc6e5b3ec1c0e5a16864421fe449be5457459602e298ebc1
|
|
sign_key_1 = d45443fa11efec622c83b27c55f7a73143bdfa0d51820ac597b9e3fb5c28dbb0
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import hashlib
|
|
import hmac as hmac_mod
|
|
import struct
|
|
from pathlib import Path
|
|
|
|
from cryptography.hazmat.backends import default_backend
|
|
from cryptography.hazmat.primitives import hashes
|
|
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
|
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
|
|
|
|
# ===================================================================
|
|
# テストベクター
|
|
# ===================================================================
|
|
|
|
DH_SHARED = bytes.fromhex(
|
|
"052a8bfe9f1a1a9becdd67672338191b"
|
|
"d7b5aff7fffe1f4cfbd97a0b14f8d59a"
|
|
"f54697a0bc1cf96ad6f6e84af98ffd1c"
|
|
"ebbc0fb5b04360878710f215f1261129"
|
|
"652808fd11f5164d84a501b40e63ba91"
|
|
"fcdcc932b6dbd61017099673f552db6e"
|
|
"94ff19934bfdef21b9701c4c9d312b78"
|
|
"f2cb8911e446313c2cb0567f7de865b3"
|
|
)
|
|
|
|
# バイナリ埋め込み固定値 (NFWebCrypto.framework @ 0x1ac8f5)
|
|
PSK = bytes.fromhex("027617984f6227539a630b897c017d69")
|
|
NONCE_HARD = bytes.fromhex("809f82a7addf548d3ea9dd067ff9bb91")
|
|
|
|
# セッション固有 nonce
|
|
SERVER_NONCE = bytes.fromhex("e73104a8f4a9ed430d90a330d7978432")
|
|
CLIENT_NONCE = bytes.fromhex("a97e47477522ab39e39b322bdf818031")
|
|
|
|
# 既知セッション鍵 (全ターゲット)
|
|
ENC_KEY_0 = bytes.fromhex("0817065e29e6d1c8668473af9e13b3c2")
|
|
ENC_KEY_1 = bytes.fromhex("97b99f4e88e8e73779aa20ac11877c5d")
|
|
|
|
KNOWN_ENC_KEYS: list[bytes] = [
|
|
ENC_KEY_0,
|
|
ENC_KEY_1,
|
|
bytes.fromhex("f4b5e0519e8022b2801768cdc88816d6"), # enc_key_2
|
|
bytes.fromhex("834327638d92f129c9da8a5ab72bca3b"),
|
|
]
|
|
|
|
KNOWN_HMAC_KEYS: list[bytes] = [
|
|
bytes.fromhex(
|
|
"91f752f76d7ab4c2dc6e5b3ec1c0e5a16864421fe449be5457459602e298ebc1"
|
|
), # sign_key_0
|
|
bytes.fromhex("38b2030dd55e3367290213ca0d16ee079524ccd24fb7221a52145fb6de016fd8"),
|
|
bytes.fromhex("05ffd2d7407a6da255dfd89cde00504d1803ed81a8e5c17ea196c4498d01d825"),
|
|
bytes.fromhex(
|
|
"d45443fa11efec622c83b27c55f7a73143bdfa0d51820ac597b9e3fb5c28dbb0"
|
|
), # sign_key_1
|
|
bytes.fromhex("b733c6b8bd3c2d02098c6c679daa9b138f9e0d9d76f95d2c85240937d53c66c9"),
|
|
bytes.fromhex("a4333e99a34eef3663f8e38e217e696949cd3bf57598c5c260fedb8997afa82b"),
|
|
bytes.fromhex("b319e54dce7117932439aad5e1dfed1e8d18fe033aabe00ab786dc4239e8dd85"),
|
|
]
|
|
|
|
# appboot レスポンス key 33.6 (96 bytes)
|
|
KEY336_HEX = (
|
|
"bb73317f907f7a5a3f924bece878d6a6"
|
|
"8db3b8f354d2207a224a323297523f58"
|
|
"2d72dfb28ea593b584d096c861561be8"
|
|
"b7d72ef4dc404e076943130aa0303200"
|
|
"af5d720284876f9b1c076aacc2ad7fc8"
|
|
"6b5a242b0cf9beb28b2a87ad00f3db38"
|
|
)
|
|
KEY336 = bytes.fromhex(KEY336_HEX)
|
|
|
|
print(f"key 33.6 ({len(KEY336)} bytes):")
|
|
print(f" IV (bytes 0-15): {KEY336[:16].hex()}")
|
|
print(f" CT (bytes 16-63): {KEY336[16:64].hex()}")
|
|
print(f" HMAC(bytes 64-95): {KEY336[64:96].hex()}")
|
|
print()
|
|
|
|
# ===================================================================
|
|
# ユーティリティ
|
|
# ===================================================================
|
|
|
|
found: list[str] = []
|
|
|
|
|
|
def aes_cbc_decrypt(key: bytes, iv: bytes, ct: bytes) -> bytes | None:
|
|
"""AES-128-CBC 復号。失敗時は None を返す。"""
|
|
if len(key) not in (16, 24, 32):
|
|
return None
|
|
if len(iv) != 16:
|
|
return None
|
|
if len(ct) % 16 != 0:
|
|
return None
|
|
try:
|
|
cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend())
|
|
dec = cipher.decryptor()
|
|
return dec.update(ct) + dec.finalize()
|
|
except Exception:
|
|
return None
|
|
|
|
|
|
def pkcs7_unpad(data: bytes) -> bytes | None:
|
|
"""PKCS7 アンパディング。不正なパディングは None を返す。"""
|
|
if not data:
|
|
return None
|
|
pad_len = data[-1]
|
|
if pad_len == 0 or pad_len > 16:
|
|
return None
|
|
if data[-pad_len:] != bytes([pad_len]) * pad_len:
|
|
return None
|
|
return data[:-pad_len]
|
|
|
|
|
|
def hmac256(key: bytes, msg: bytes) -> bytes:
|
|
return hmac_mod.new(key, msg, hashlib.sha256).digest()
|
|
|
|
|
|
def verify_hmac(hmac_key: bytes, data: bytes, expected_hmac: bytes) -> bool:
|
|
"""HMAC-SHA256(hmac_key, data) == expected_hmac"""
|
|
computed = hmac256(hmac_key, data)
|
|
return computed == expected_hmac
|
|
|
|
|
|
def check_plaintext(label: str, pt: bytes) -> bool:
|
|
"""平文が既知のセッション鍵 (enc_key または sign_key) を含むか検査"""
|
|
matched = False
|
|
for enc_key in KNOWN_ENC_KEYS:
|
|
if len(pt) >= 16 and pt[:16] == enc_key:
|
|
hmac_part = pt[16:]
|
|
hmac_matched = any(
|
|
hmac_part == sk or hmac_part[:32] == sk for sk in KNOWN_HMAC_KEYS
|
|
)
|
|
if hmac_matched:
|
|
msg = (
|
|
f"[FULL MATCH] {label} enc={enc_key.hex()[:8]}... hmac_match=True"
|
|
)
|
|
print(msg)
|
|
found.append(msg)
|
|
else:
|
|
msg = f"[ENC_ONLY] {label} enc={enc_key.hex()[:8]}... rest={hmac_part[:16].hex()}"
|
|
print(msg)
|
|
found.append(msg)
|
|
matched = True
|
|
return matched
|
|
|
|
|
|
# ===================================================================
|
|
# 候補ラッピング鍵の生成
|
|
# ===================================================================
|
|
|
|
ss_int = int(DH_SHARED.hex(), 16)
|
|
|
|
sha256_ss = hashlib.sha256(DH_SHARED).digest()
|
|
sha384_ss = hashlib.sha384(DH_SHARED).digest()
|
|
sha512_ss = hashlib.sha512(DH_SHARED).digest()
|
|
|
|
WRAP_KEY_CANDIDATES: list[tuple[str, bytes]] = [
|
|
# --- ハッシュ切り出し ---
|
|
("sha256(DH)[:16]", sha256_ss[:16]),
|
|
("sha256(DH)[16:32]", sha256_ss[16:32]),
|
|
("sha384(DH)[:16]", sha384_ss[:16]),
|
|
("sha384(DH)[16:32]", sha384_ss[16:32]),
|
|
("sha384(DH)[32:48]", sha384_ss[32:48]),
|
|
("sha512(DH)[:16]", sha512_ss[:16]),
|
|
("sha512(DH)[16:32]", sha512_ss[16:32]),
|
|
("sha512(DH)[32:48]", sha512_ss[32:48]),
|
|
("sha512(DH)[48:64]", sha512_ss[48:64]),
|
|
# --- DH 直接切り出し ---
|
|
("DH[:16]", DH_SHARED[:16]),
|
|
("DH[16:32]", DH_SHARED[16:32]),
|
|
("DH[-16:]", DH_SHARED[-16:]),
|
|
# --- PSK / nonce 由来 ---
|
|
("PSK", PSK),
|
|
("NONCE_HARD", NONCE_HARD),
|
|
("SERVER_NONCE", SERVER_NONCE),
|
|
("CLIENT_NONCE", CLIENT_NONCE),
|
|
# --- 既知セッション鍵 (Phase 4 では enc_key_1 が wrap_key として使われた) ---
|
|
("enc_key_0", ENC_KEY_0),
|
|
("enc_key_1", ENC_KEY_1),
|
|
# --- HMAC チェーン由来 ---
|
|
("HMAC(PSK,DH)[:16]", hmac256(PSK, DH_SHARED)[:16]),
|
|
("HMAC(DH,PSK)[:16]", hmac256(DH_SHARED, PSK)[:16]),
|
|
("HMAC(PSK,DH)[16:32]", hmac256(PSK, DH_SHARED)[16:32]),
|
|
("HMAC(NONCE,DH)[:16]", hmac256(NONCE_HARD, DH_SHARED)[:16]),
|
|
("HMAC(SERVER_NONCE,DH)[:16]", hmac256(SERVER_NONCE, DH_SHARED)[:16]),
|
|
# --- 2 段 HMAC ---
|
|
(
|
|
"HMAC(HMAC(PSK,DH),NONCE)[:16]",
|
|
hmac256(hmac256(PSK, DH_SHARED), NONCE_HARD)[:16],
|
|
),
|
|
(
|
|
"HMAC(HMAC(PSK,DH),SRV_NONCE)[:16]",
|
|
hmac256(hmac256(PSK, DH_SHARED), SERVER_NONCE)[:16],
|
|
),
|
|
(
|
|
"HMAC(HMAC(DH,PSK),NONCE)[:16]",
|
|
hmac256(hmac256(DH_SHARED, PSK), NONCE_HARD)[:16],
|
|
),
|
|
# --- HKDF 由来 (サーバー nonce を salt/info に) ---
|
|
]
|
|
|
|
# HKDF 候補を動的生成
|
|
for hash_alg, hash_name in [
|
|
(hashes.SHA256(), "SHA256"),
|
|
(hashes.SHA384(), "SHA384"),
|
|
]:
|
|
for salt_val, salt_name in [
|
|
(None, "None"),
|
|
(SERVER_NONCE, "SRV_NONCE"),
|
|
(CLIENT_NONCE, "CLI_NONCE"),
|
|
(PSK, "PSK"),
|
|
]:
|
|
for info_val, info_name in [
|
|
(b"", "empty"),
|
|
(SERVER_NONCE, "SRV_NONCE"),
|
|
(PSK, "PSK"),
|
|
(b"wrap", "wrap"),
|
|
(b"session", "session"),
|
|
]:
|
|
try:
|
|
hkdf = HKDF(
|
|
algorithm=hash_alg,
|
|
length=16,
|
|
salt=salt_val,
|
|
info=info_val,
|
|
)
|
|
k = hkdf.derive(DH_SHARED)
|
|
label = f"HKDF({hash_name},s={salt_name},i={info_name})"
|
|
WRAP_KEY_CANDIDATES.append((label, k))
|
|
except Exception:
|
|
pass
|
|
|
|
print(f"Total wrap_key candidates: {len(WRAP_KEY_CANDIDATES)}")
|
|
print()
|
|
|
|
# ===================================================================
|
|
# HMAC 署名鍵の候補
|
|
# ===================================================================
|
|
|
|
SIGN_KEY_CANDIDATES: list[tuple[str, bytes]] = [
|
|
("PSK", PSK),
|
|
("NONCE_HARD", NONCE_HARD),
|
|
("SERVER_NONCE", SERVER_NONCE),
|
|
("DH_SHARED", DH_SHARED),
|
|
("sha256(DH)", sha256_ss),
|
|
("sha384(DH)", sha384_ss),
|
|
("enc_key_0", ENC_KEY_0),
|
|
("enc_key_1", ENC_KEY_1),
|
|
("HMAC(PSK,DH)", hmac256(PSK, DH_SHARED)),
|
|
("HMAC(DH,PSK)", hmac256(DH_SHARED, PSK)),
|
|
("HMAC(PSK,DH+NONCE)", hmac256(PSK, DH_SHARED + NONCE_HARD)),
|
|
]
|
|
# 全 known HMAC keys も候補として追加
|
|
for i, sk in enumerate(KNOWN_HMAC_KEYS):
|
|
SIGN_KEY_CANDIDATES.append((f"known_hmac_{i}_{sk.hex()[:8]}", sk))
|
|
|
|
# ===================================================================
|
|
# Section 1: 仮説 B — [IV:16B][CT:48B][HMAC:32B]
|
|
# ===================================================================
|
|
|
|
print("=" * 70)
|
|
print("仮説 B: [IV:16B][CT:48B][HMAC:32B]")
|
|
print(" IV = key336[0:16]")
|
|
print(" CT = key336[16:64]")
|
|
print(" HMAC = key336[64:96]")
|
|
print("=" * 70)
|
|
|
|
IV_B = KEY336[:16]
|
|
CT_B = KEY336[16:64]
|
|
HMAC_B = KEY336[64:96]
|
|
|
|
print(f" IV: {IV_B.hex()}")
|
|
print(f" CT: {CT_B.hex()}")
|
|
print(f" HMAC: {HMAC_B.hex()}")
|
|
print()
|
|
|
|
for wrap_name, wrap_key in WRAP_KEY_CANDIDATES:
|
|
pt = aes_cbc_decrypt(wrap_key, IV_B, CT_B)
|
|
if pt is None:
|
|
continue
|
|
pt_unpad = pkcs7_unpad(pt)
|
|
if pt_unpad is None:
|
|
continue
|
|
label = f"HypB(wrap={wrap_name})"
|
|
check_plaintext(label, pt_unpad)
|
|
|
|
# HMAC 検証: 候補 sign_key で HMAC(IV||CT) を計算して HMAC_B と比較
|
|
if any(pt_unpad[:16] == enc for enc in KNOWN_ENC_KEYS):
|
|
for sign_name, sign_key in SIGN_KEY_CANDIDATES:
|
|
if verify_hmac(sign_key, IV_B + CT_B, HMAC_B):
|
|
msg = f"[HMAC_VERIFIED] HypB wrap={wrap_name} sign={sign_name}"
|
|
print(msg)
|
|
found.append(msg)
|
|
|
|
# ===================================================================
|
|
# Section 2: 仮説 A — [CT:64B][HMAC:32B] (IV=zeros)
|
|
# ===================================================================
|
|
|
|
print()
|
|
print("=" * 70)
|
|
print("仮説 A: [CT:64B][HMAC:32B] — IV=zeros_16")
|
|
print(" CT = key336[0:64]")
|
|
print(" HMAC = key336[64:96]")
|
|
print("=" * 70)
|
|
|
|
CT_A = KEY336[:64]
|
|
HMAC_A = KEY336[64:96]
|
|
|
|
for wrap_name, wrap_key in WRAP_KEY_CANDIDATES:
|
|
pt = aes_cbc_decrypt(wrap_key, b"\x00" * 16, CT_A)
|
|
if pt is None:
|
|
continue
|
|
pt_unpad = pkcs7_unpad(pt)
|
|
if pt_unpad is None:
|
|
continue
|
|
label = f"HypA(wrap={wrap_name})"
|
|
check_plaintext(label, pt_unpad)
|
|
|
|
# ===================================================================
|
|
# Section 3: 仮説 A2 — [CT:64B][HMAC:32B] (IV=CT[:16])
|
|
# ===================================================================
|
|
|
|
print()
|
|
print("=" * 70)
|
|
print("仮説 A2: [CT:64B][HMAC:32B] — IV=CT[:16] (先頭 16B を IV として再利用)")
|
|
print("=" * 70)
|
|
|
|
for wrap_name, wrap_key in WRAP_KEY_CANDIDATES:
|
|
pt = aes_cbc_decrypt(wrap_key, CT_A[:16], CT_A[16:])
|
|
if pt is None:
|
|
continue
|
|
pt_unpad = pkcs7_unpad(pt)
|
|
if pt_unpad is None:
|
|
continue
|
|
label = f"HypA2(wrap={wrap_name})"
|
|
check_plaintext(label, pt_unpad)
|
|
|
|
# ===================================================================
|
|
# Section 4: 仮説 C — [IV:16B][CT:64B][HMAC:16B]
|
|
# key 33.6 が 96 bytes では収まらないため、代わりに CT を 48B として残り 16B が HMAC[:16]
|
|
# ===================================================================
|
|
|
|
print()
|
|
print("=" * 70)
|
|
print("仮説 C: [IV:16B][CT:48B][HMAC:32B] — wrap_key に enc_key_1 を使用")
|
|
print(" (Phase 4 では enc_key_1 が wrap_key として使われた)")
|
|
print("=" * 70)
|
|
|
|
# Phase 4 で確認済み: enc_key_1 が AES-CBC 復号鍵として機能
|
|
# 同じパターンが Phase 2 にも当てはまるか確認
|
|
for iv_val, iv_name in [
|
|
(IV_B, "key336[:16]"),
|
|
(b"\x00" * 16, "zeros"),
|
|
(SERVER_NONCE, "SERVER_NONCE"),
|
|
(CLIENT_NONCE, "CLIENT_NONCE"),
|
|
]:
|
|
pt = aes_cbc_decrypt(ENC_KEY_1, iv_val, CT_B)
|
|
if pt is None:
|
|
continue
|
|
pt_unpad = pkcs7_unpad(pt)
|
|
if pt_unpad is None:
|
|
continue
|
|
label = f"HypC(wrap=enc_key_1,iv={iv_name})"
|
|
check_plaintext(label, pt_unpad)
|
|
print(f" enc_key_1 wrap: iv={iv_name} pt(raw)={pt.hex()}")
|
|
|
|
# ===================================================================
|
|
# Section 5: 全 appboot レスポンスファイルへの適用
|
|
# ===================================================================
|
|
|
|
print()
|
|
print("=" * 70)
|
|
print("全 appboot レスポンスファイルへの適用")
|
|
print("=" * 70)
|
|
|
|
import gzip
|
|
|
|
try:
|
|
import cbor2
|
|
|
|
RAWS_DIR = Path("/home/vscode/app/raws/ios/20260408/raw")
|
|
res_files = sorted(RAWS_DIR.glob("res_*appboot*.bin"))
|
|
print(f"Found {len(res_files)} appboot response files")
|
|
|
|
unique_key336: dict[str, str] = {} # hex -> filename
|
|
|
|
for res_file in res_files:
|
|
raw = res_file.read_bytes()
|
|
if raw[:2] == b"\x1f\x8b":
|
|
try:
|
|
raw = gzip.decompress(raw)
|
|
except Exception:
|
|
continue
|
|
try:
|
|
obj = cbor2.loads(raw)
|
|
except Exception:
|
|
continue
|
|
|
|
k33 = obj.get(33)
|
|
if k33 is None:
|
|
continue
|
|
try:
|
|
if isinstance(k33, bytes):
|
|
inner = cbor2.loads(k33)
|
|
elif isinstance(k33, dict):
|
|
inner = k33
|
|
else:
|
|
continue
|
|
except Exception:
|
|
continue
|
|
|
|
k6 = inner.get(6)
|
|
if not isinstance(k6, bytes) or len(k6) != 96:
|
|
continue
|
|
|
|
hex_val = k6.hex()
|
|
if hex_val not in unique_key336:
|
|
unique_key336[hex_val] = res_file.name
|
|
|
|
print(f"Unique key 33.6 values (96B): {len(unique_key336)}")
|
|
|
|
for hex_val, fname in unique_key336.items():
|
|
blob = bytes.fromhex(hex_val)
|
|
iv = blob[:16]
|
|
ct48 = blob[16:64]
|
|
hmac32 = blob[64:96]
|
|
|
|
print(f"\n File: {fname}")
|
|
print(f" IV: {iv.hex()}")
|
|
print(f" CT: {ct48.hex()}")
|
|
|
|
for wrap_name, wrap_key in WRAP_KEY_CANDIDATES:
|
|
pt = aes_cbc_decrypt(wrap_key, iv, ct48)
|
|
if pt is None:
|
|
continue
|
|
pt_unpad = pkcs7_unpad(pt)
|
|
if pt_unpad is None:
|
|
continue
|
|
label = f"File[{fname}] HypB(wrap={wrap_name})"
|
|
check_plaintext(label, pt_unpad)
|
|
|
|
except ImportError:
|
|
print("[skip] cbor2 not available")
|
|
|
|
# ===================================================================
|
|
# Section 6: HMAC 署名の検証 (独立)
|
|
# ===================================================================
|
|
|
|
print()
|
|
print("=" * 70)
|
|
print("HMAC 署名の検証 — どの鍵で HMAC(IV||CT) == key336[64:96] になるか")
|
|
print("=" * 70)
|
|
|
|
for sign_name, sign_key in SIGN_KEY_CANDIDATES:
|
|
# 仮説 B: HMAC over IV||CT
|
|
if verify_hmac(sign_key, IV_B + CT_B, HMAC_B):
|
|
msg = f"[HMAC_MATCH HypB IV||CT] sign={sign_name}"
|
|
print(msg)
|
|
found.append(msg)
|
|
# HMAC over CT only
|
|
if verify_hmac(sign_key, CT_B, HMAC_B):
|
|
msg = f"[HMAC_MATCH CT_only] sign={sign_name}"
|
|
print(msg)
|
|
found.append(msg)
|
|
# HMAC over CT_A (full 64B)
|
|
if verify_hmac(sign_key, CT_A, HMAC_A):
|
|
msg = f"[HMAC_MATCH HypA CT64] sign={sign_name}"
|
|
print(msg)
|
|
found.append(msg)
|
|
# HMAC over entire 96 bytes
|
|
if len(KEY336) >= 64 + 32:
|
|
if verify_hmac(sign_key, KEY336[:64], KEY336[64:96]):
|
|
msg = f"[HMAC_MATCH full96[:64]] sign={sign_name}"
|
|
print(msg)
|
|
found.append(msg)
|
|
|
|
# ===================================================================
|
|
# Section 7: 平文構造の仮説追加 — enc_key || sign_key ではなく別の構造
|
|
# ===================================================================
|
|
|
|
print()
|
|
print("=" * 70)
|
|
print("平文構造の代替仮説テスト")
|
|
print(" 平文が enc_key(16B) + sign_key(32B) ではなく別の構造の可能性")
|
|
print("=" * 70)
|
|
|
|
for wrap_name, wrap_key in WRAP_KEY_CANDIDATES:
|
|
# 仮説 B の復号
|
|
pt_b = aes_cbc_decrypt(wrap_key, IV_B, CT_B)
|
|
if pt_b is None:
|
|
continue
|
|
|
|
# パディングなしで直接確認
|
|
if len(pt_b) == 48:
|
|
# enc_key はどこかにある?
|
|
for offset in range(0, 33):
|
|
for enc in KNOWN_ENC_KEYS:
|
|
if pt_b[offset : offset + 16] == enc:
|
|
msg = (
|
|
f"[ENC_AT_OFFSET_{offset}] wrap={wrap_name} enc={enc.hex()[:8]}"
|
|
)
|
|
print(msg)
|
|
found.append(msg)
|
|
|
|
# ===================================================================
|
|
# 結果サマリー
|
|
# ===================================================================
|
|
|
|
print()
|
|
print("=" * 70)
|
|
print("結果サマリー")
|
|
print("=" * 70)
|
|
if found:
|
|
print(f"一致 ({len(found)} 件):")
|
|
for item in found:
|
|
print(f" {item}")
|
|
else:
|
|
print("一致なし")
|
|
print()
|
|
print("考察:")
|
|
print()
|
|
print(" 1. enc_key_0 (0817065e) と enc_key_1 (97b99f4e) のいずれも")
|
|
print(" key 33.6 の復号で得られなかった。")
|
|
print()
|
|
print(" 2. 可能性 A: DH 共有秘密とセッション鍵のペアリングが誤っている")
|
|
print(" (別セッションのデータが混在している)")
|
|
print()
|
|
print(" 3. 可能性 B: wrap_key が DH 共有秘密から標準 KDF では導出されない")
|
|
print(" NFWebCrypto 内部の TFIT ホワイトボックス変換が関与")
|
|
print()
|
|
print(" 4. 可能性 C: key 33.6 の構造が [IV:16][CT:48][HMAC:32] ではなく")
|
|
print(" 全く異なる (例: プロトコル独自の AES Key Wrap)")
|
|
print()
|
|
print("推奨アクション:")
|
|
print(" - Frida で AES_set_decrypt_key の直前の caller を特定する")
|
|
print(
|
|
" - AES_set_decrypt_key に渡される鍵と key 33.6 の IV/CT を同時キャプチャする"
|
|
)
|
|
print(" - キャプチャした wrap_key を此処の WRAP_KEY_CANDIDATES に追加する")
|