Skip to content

Instantly share code, notes, and snippets.

@Popax21
Created January 5, 2023 01:53
Show Gist options
  • Save Popax21/a85f4dce14bd486b924be7c1e097e238 to your computer and use it in GitHub Desktop.
Save Popax21/a85f4dce14bd486b924be7c1e097e238 to your computer and use it in GitHub Desktop.
A script to reencrypt / fix 3DS ROM title keys
#!/usr/bin/python3
import sys, secrets
from Crypto.Cipher import AES
if len(sys.argv) < 2:
print(f"Usage: {sys.argv[0]} <3ds ROM file>")
sys.exit(1)
#Load the ROM file into memory
rom = None
with open(sys.argv[1], "rb") as f: rom = f.read()
#Get the current crypto type, key seed, encrypted title key, MAC and nonce
crypto_type = (rom[0x207] >> 6) & 0b11 #CardInfo.Flag.CryptoType
key_seed = rom[0x1000:0x1010] #InitialData.Seed
enc_title_key = rom[0x1010:0x1020] #InitialData.TitleKey
title_key_mac = rom[0x1020:0x1030] #InitialData.Mac
title_key_nonce = rom[0x1030:0x103c] #InitialData.Nonce
print("ROM Info:")
print(f" - Crypto Type: {crypto_type:x} [%s]" % ("PROD" if crypto_type != 3 else "DEV"))
print(f" - Key Seed: {key_seed.hex()}")
print(f" - Encrypted Title Key: {enc_title_key.hex()}")
print(f" - Title Key MAC: {title_key_mac.hex()}")
print(f" - Title Key Nonce: {title_key_nonce.hex()}")
print()
#Derive production title key encryption key
#Algorithm taken from libnintendo-n3ds / CTRtool
def gen_key(keyX, keyY):
KEYGEN_CONST = int.from_bytes(bytes.fromhex("1FF9E9AAC5FE0408024591DC5D52768A"), "big")
def lrot(v, b): return ((v << b) | (v >> (128 - b))) & ((1 << 128) - 1)
def rrot(v, b): return ((v >> b) | (v << (128 - b))) & ((1 << 128) - 1)
assert len(keyX) == 16 and len(keyY) == 16
keyX, keyY = int.from_bytes(keyX, "big"), int.from_bytes(keyY, "big")
key = rrot((lrot(keyX, 2) ^ keyY) + KEYGEN_CONST, 41)
return key.to_bytes(16, "big")
BOOTROM_INITIAL_DATA_KEY = bytes.fromhex("B529221CDDB5DB5A1BF26EFF2041E875")
prod_title_key_enc_key = gen_key(BOOTROM_INITIAL_DATA_KEY, key_seed)
#Determine the key used for the encrypted title key
title_key_enc_key = prod_title_key_enc_key if crypto_type != 3 else bytes(16)
print("Determined Title Key Encryption Keys:")
print(f" - Derived Production Title Key Encryption Key: {prod_title_key_enc_key.hex()}")
print(f" - Used Title Key Encryption Key for decryption: {title_key_enc_key.hex()} [%s]" % ("PROD" if crypto_type != 3 else "DEV"))
print()
#Attempt to decrypt the title key
title_key = None
print("Attempting decryption of title key...")
try:
title_key = AES.new(title_key_enc_key, AES.MODE_CCM, title_key_nonce).decrypt_and_verify(enc_title_key, title_key_mac)
print(f" - Decrypted Title Key: {title_key.hex()}")
if crypto_type != 3:
print("Title Key already is a valid production one - exiting...")
sys.exit(0)
else:
crypto_type = 3
print("Tile Key is valid, but a development one - reencrypting as production key...")
except ValueError:
print(" - MAC check failed - reencryping key as production key...")
#Check for development card title key
title_key = rom[0x1400:0x1410] #TitleKeyData
if all(b == 0xff for b in title_key):
print(" - utilizing randomly generated title key")
title_key = secrets.token_bytes(16)
else:
print(" - utilizing development card title key")
print()
#Reencrypt the title key
print(f"Reencrypting Title Key: {title_key.hex()}...")
enc_title_key, title_key_mac = AES.new(prod_title_key_enc_key, AES.MODE_CCM, title_key_nonce).encrypt_and_digest(title_key)
print(f" - Encrypted Title Key: {enc_title_key.hex()}")
print(f" - Title Key MAC: {title_key_mac.hex()}")
#Patch the ROM keys and save the patched ROM
rom = rom[:0x1010] + enc_title_key + title_key_mac + rom[0x1030:]
print("Patched ROM keys")
with open(sys.argv[1], "wb") as f: f.write(rom)
print("Saved patched ROM")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment