Last active
June 17, 2018 19:14
-
-
Save MCMrARM/87a3e71032b24e3bf43ece0490ef2c58 to your computer and use it in GitHub Desktop.
A Python script to decrypt Switch's SSL private key
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# A Python script to decrypt Switch's SSL private key | |
# Thanks to roblabla for help | |
# Heavily based on Atmosphere's exosphere | |
# Usage: kek.py <source> <destination> | |
# Make sure to fill in the proper keys in the script | |
import struct | |
import sys | |
from Crypto.Cipher import AES | |
from Crypto.Util import Counter | |
master_keys = [ | |
bytes.fromhex("## master_key_00 ##") | |
] | |
generate_aes_kek_seed = bytes.fromhex("## seed here ##") | |
decrypt_rsa_private_key_seed = bytes.fromhex("## seed here ##") | |
keygen_key = None | |
def aes_ecb_encrypt(data, key): | |
return AES.new(key, AES.MODE_ECB).encrypt(data) | |
def aes_ecb_decrypt(data, key): | |
return AES.new(key, AES.MODE_ECB).decrypt(data) | |
def aes_ctr_decrypt(data, ctr, key): | |
iv1, iv2 = struct.unpack(">QQ", ctr) | |
iv = (iv1 << 64) | iv2 | |
return AES.new(key, AES.MODE_CTR, counter=Counter.new(128, initial_value=iv)).decrypt(data) | |
MASTERKEY_NUM_NEW_DEVICE_KEYS = 2 | |
device_keys = None | |
# these seeds came from: https://github.com/Atmosphere-NX/Atmosphere/blob/05b8b4216404c9b73c03f15bfc2f0c22bf4aacb7/exosphere/src/package2.c#L26 | |
new_device_key_sources = [ | |
bytes.fromhex("8B4E1C224207C8735694088BCC470F5D"), | |
bytes.fromhex("6CEFC6278BEC8A9199AB24AC4F1C8F1C") | |
] | |
new_device_keygen_sources = [ | |
bytes.fromhex("8862346EFAF7D83FE1303950F0B75D5D"), | |
bytes.fromhex("061E7BE96D478C77C5C8E7949AA85F2E") | |
] | |
def derive_new_device_keys(keygen_key): | |
global device_keys | |
device_keys = [] | |
for revision in range(0, MASTERKEY_NUM_NEW_DEVICE_KEYS): | |
work_buffer = aes_ecb_decrypt(new_device_key_sources[revision], keygen_key) | |
temp_key = aes_ecb_decrypt(new_device_keygen_sources[revision], master_keys[0]) | |
work_buffer = aes_ecb_decrypt(work_buffer, temp_key) | |
device_keys[revision] = work_buffer | |
# these seeds came from: https://github.com/Atmosphere-NX/Atmosphere/blob/c2eed3caf6b7d04e7e51f1b07ccf019eb4ef6fb6/exosphere/src/smc_user.c#L166 | |
kek_seeds = [ | |
bytes.fromhex("00000000000000000000000000000000"), | |
bytes.fromhex("A2ABBF9C922FBBE378799BC0CCEAA574"), | |
bytes.fromhex("57E2D945E492F4FDC3F9863889789F3C"), | |
bytes.fromhex("E54D9A02F04F5FA8AD760AF6329559BB") | |
] | |
kek_masks = [ | |
bytes.fromhex("4D870986C45D20722FBA1053DA92E8A9"), | |
bytes.fromhex("250331FB25260B798C80D26998E22277"), | |
bytes.fromhex("76141D34932DE184247B666555046581"), | |
bytes.fromhex("AF3DB7F308A2D8A208CA18A86946C90B") | |
] | |
def generate_aes_kek(wrapped_kek, mkey_rev, packed_options): | |
mask_id = (packed_options >> 1) & 3 | |
usecase = (packed_options >> 5) & 3 | |
is_personalized = packed_options & 1 | |
kek_source = bytes([a^b for (a, b) in zip(kek_seeds[usecase], kek_masks[mask_id])]) | |
key = None | |
if is_personalized: | |
key = device_keys[mkey_rev] | |
else: | |
key = master_keys[mkey_rev] | |
temp_key = aes_ecb_decrypt(kek_source, key) | |
kek = aes_ecb_decrypt(wrapped_kek, temp_key) | |
return kek | |
def gf128_mul(x, y): | |
x1, x2 = struct.unpack("<QQ", x[0x10::-1]) | |
y1, y2 = struct.unpack("<QQ", y[0x10::-1]) | |
x = (x2 << 64) | x1 | |
y = (y2 << 64) | y1 | |
# https://github.com/bozhu/AES-GCM-Python/blob/master/aes_gcm.py#L32 | |
res = 0 | |
for i in range(127, -1, -1): | |
res ^= x * ((y >> i) & 1) | |
x = (x >> 1) ^ ((x & 1) * 0xE1000000000000000000000000000000) | |
return struct.pack("<QQ", res & ((1 << 64) - 1), (res >> 64) & ((1 << 64) - 1))[::-1] | |
def ghash(key, data, j_block, encrypt): | |
x = bytearray(0x10) | |
h = aes_ecb_encrypt(bytes(x), key) | |
data = bytearray(data) # make it mutable | |
for i in range(0, len(data) - 0xf, 0x10): | |
for j in range(0, 0x10): | |
x[j] ^= data[i + j] | |
x = bytearray(gf128_mul(x, h)) | |
if (len(data) - (len(data) - 0xf) // 0x10) & 0xf: | |
x = bytearray(gf128_mul(x, h)) | |
xor_size = struct.pack(">Q", len(data) << 3) | |
for j in range(0, 8): | |
x[j + (0 if encrypt else 8)] ^= xor_size[j] | |
x = bytearray(gf128_mul(x, h)) | |
if encrypt: | |
h = aes_ecb_encrypt(j_block, key) | |
for j in range(0, 0x10): | |
x[j] ^= h[j] | |
return bytes(x) | |
def gcm_decrypt_key(src, kek, wrapped_key, usecase, is_personalized): | |
temp_key = aes_ecb_decrypt(wrapped_key, kek) | |
intermediate_buf = aes_ctr_decrypt(src[0x10:], src[:0x10], temp_key) | |
if not is_personalized: | |
return intermediate_buf | |
j_block = ghash(temp_key, src[:0x10], None, False) | |
calc_mac = ghash(temp_key, intermediate_buf[:-0x10], j_block, True) | |
different = 0 | |
for b in range(0, 0x10): | |
different |= src[-0x10 + b] ^ calc_mac[b] | |
if different != 0: | |
return None | |
return intermediate_buf[0:-0x20] | |
def decrypt_rsa_private_key(kek, is_personalized, user_data, wrapped_key): | |
return gcm_decrypt_key(user_data, kek, wrapped_key, 1, is_personalized) | |
src_data = None | |
with open(sys.argv[1], 'rb') as src_file: | |
src_data = src_file.read() | |
# derive_new_device_keys(bytes.fromhex(keygen_key)) | |
kek = generate_aes_kek(generate_aes_kek_seed, 0, 0x20) | |
dest_data = decrypt_rsa_private_key(kek, True, src_data, decrypt_rsa_private_key_seed) | |
if dest_data == None: | |
print("Error") | |
exit(0) | |
with open(sys.argv[2], 'wb') as dest_file: | |
dest_file.write(dest_data) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment