54 lines
1.9 KiB
Python

# "tkey-gen.py", licensed under the MIT license
# Copyright 2024 NinjaCheetah
import binascii
import hashlib
import libWiiPy
from libWiiPy.types import _ContentRecord
def _secret(start, length):
ret = b''
add = start + length
for _ in range(length):
unsigned_start = start & 0xFF # Compensates for how Python handles negative values vs PHP.
ret += bytes.fromhex(f"{unsigned_start:02x}"[-2:])
nxt = start + add
add = start
start = nxt
return ret
def _mungetid(tid):
# Remove leading zeroes from the TID.
while tid.startswith("00"):
tid = tid[2:]
if tid == "":
tid = "00"
# In PHP, the last character just gets dropped if you make a hex string from an odd-length input, so this
# replicates that functionality.
if len(tid) % 2 != 0:
tid = tid[:-1]
return bytes.fromhex(tid)
def _derive_key(tid, passwd):
key_secret = _secret(-3, 10)
salt = hashlib.md5(key_secret + _mungetid(tid)).digest()
# Had to reduce the length here from 32 to 16 when converting to get the same length keys.
return hashlib.pbkdf2_hmac("sha1", passwd.encode(), salt, 20, 16).hex()
def find_tkey(tid: str, banner_enc: bytes, content_record: _ContentRecord) -> bytes:
# Find a working Title Key by generating a key with a password, then decrypting content 0 and comparing it to the
# expected hash. If the hash matches, then we generated the correct key.
passwds = ["nintendo", "mypass"]
for passwd in passwds:
key = binascii.unhexlify(_derive_key(tid, passwd).encode())
banner_dec = libWiiPy.title.decrypt_content(banner_enc, key, content_record.index, content_record.content_size)
banner_dec_hash = hashlib.sha1(banner_dec).hexdigest()
content_record_hash = content_record.content_hash.decode()
if banner_dec_hash == content_record_hash:
return key
raise Exception("Valid Title Key could not be generated")