Switch > NAX0 Files
A NAX0 file encrypts data with AES-XTS. The Nintendo Switch uses it to encrypt files on the SD card. The sector size for AES-XTS is 0x4000 bytes.
File Structure
| Offset | Size | Description |
|---|---|---|
| 0x0 | 128 | Header |
| 0x80 | 0x3F80 | Padding |
| 0x4000 | Encrypted file |
Header
Remember that AES-XTS requires two encryption keys by design.
| Offset | Size | Description |
|---|---|---|
| 0x0 | 32 | Header MAC |
| 0x20 | 4 | Magic number (NAX0) |
| 0x24 | 4 | Padding (always 0) |
| 0x28 | 16 | Encrypted AES-XTS key 1 |
| 0x38 | 16 | Encrypted AES-XTS Key 2 |
| 0x48 | 8 | Size of encrypted file (little endian) |
| 0x50 | 48 | Padding (always 0) |
Header MAC
This is a HMAC-SHA256. The HMAC key is bytes 0x20 - 0x80 of the header after decrypting the AES-XTS keys. The message is derived using the following sources:
| Type | Key source |
|---|---|
| Save data | 9a951517b16e8f7f1f68263152ea296a |
| Titles | 67c793f0f24fded075495dca006d99c2 |
| Custom storage | 4f2ca5a3fc999a47c03ee004485b2fd0 |
AES-XTS Key Encryption
The AES-XTS keys in the header are encrypted with AES-ECB.
First, a 16-byte HMAC key is derived using the following sources:
| Type | Key source |
|---|---|
| Save data | 2449b722726703a81965e6e3ea582fdd |
| Titles | 5841a284935b56278b8e1fc518e99f2b |
| Custom storage | 370c345e12e4cefe21b58e64db52af35 |
Then, a HMAC-SHA256 is calculated over the filesystem path using the derived key. The filesystem path is a path from the following roots:
| Type | Key source |
|---|---|
| Save data | /Nintendo/save |
| Titles | /Nintendo/Contents |
For example, in order to decrypt /Nintendo/save/8000000000000000 the path /8000000000000000 should be used for the HMAC computation.
The first 16 bytes of the HMAC are used to encrypt the first AES-XTS key in the header. The second 16 bytes of the HMAC are used to encrypt the second AES-XTS key in the header.
Key Derivation
This section describes how keys are derived for the header MAC and AES-XTS key encryption.
The following algorithm is used to derive the keys, using AES-ECB:
aes_kek_generation_sourceis decrypted withmaster_key_00.sd_card_kek_sourceis decrypted with the key from step 1.aes_key_generation_sourceis decrypted with the key from step 2.- A key source that depends on the filesystem type is XORed with the SD card encryption seed.
- The data from step 4 is decrypted with the key from step 3.
sd_card_kek_source is hardcoded in the FS sysmodule.
File Encryption
The file data, which comes after the header, is encrypted with AES-XTS.
AES-XTS requires two different keys \(K1\) and \(K2\). First, the file is divided into sectors of 0x4000 bytes each. Every sector can be independently encrypted.
To encrypt a sector, it is first divided into 16-byte blocks. Every block is XORed with a tweak \(T\), encrypted with \(K1\) using AES, and XORed with \(T\) again.
The tweak \(T\) for the first block is the sector index encrypted with \(K2\) using AES. To generate the tweak for the next block, take the tweak of the previous block and multiply it by \(x\) under \(GF(2^{128})\) with the modulus \(x^{128} + x^7 + x^2 + x + 1\).
The sector index is encoded in big endian byte order. For example, the tweak for the first block of the third sector can be generated by encrypting 00000000000000000000000000000002 with AES.
Note: sectors are only encrypted when they are actually written. If an application creates a file with a specific size and does not write to a specific sector later, this sector contains uninitialized garbage data.
Python Implementation
The following code demonstrates how a NAX0 header can be validated and decrypted with Python:
from Crypto.Cipher import AES
# Insert hardcoded keys and SD card encryption seed here
KEYS = {
...
}
# Insert file path here
PATH = "/8000000000000000"
def xor(a: bytes, b: bytes) -> bytes:
return bytes(p ^ q for p, q in zip(a, b))
def decrypt_key(key: bytes, kek: bytes) -> bytes:
aes = AES.new(kek, AES.MODE_ECB)
return aes.decrypt(key)
def derive_key(source: bytes) -> bytes:
key = decrypt_key(KEYS["aes_kek_generation_source"], KEYS["master_key_00"])
key = decrypt_key(KEYS["sd_card_kek_source"], key)
key = decrypt_key(KEYS["aes_key_generation_source"], key)
key = decrypt_key(xor(source, KEYS["sd_seed"]), key)
return key
def next_tweak(tweak: bytes) -> bytes:
value = int.from_bytes(tweak, "little")
value <<= 1
if value & (1 << 128):
value ^= 0x87
value = value & ((1 << 128) - 1)
return value.to_bytes(128, "little")
def decrypt_xts(data: bytes, key1: bytes, key2: bytes) -> bytes:
aes1 = AES.new(key1, AES.MODE_ECB)
aes2 = AES.new(key2, AES.MODE_ECB)
sectors = []
for offset in range(0, len(data), 0x4000):
sector = data[offset : offset + 0x4000]
sector_index = offset // 0x4000
tweak = aes2.encrypt(sector_index.to_bytes(16, "big"))
plaintext = b""
for i in range(0, len(sector), 16):
block = sector[i : i + 16]
block = xor(aes1.decrypt(xor(block, tweak)), tweak)
plaintext += block
tweak = next_tweak(tweak)
sectors.append(plaintext)
return b"".join(sectors)
# We use the save data key sources here
key1 = derive_key(bytes.fromhex("2449b722726703a81965e6e3ea582fdd"))
key2 = derive_key(bytes.fromhex("9a951517b16e8f7f1f68263152ea296a"))
mac = hmac.digest(key1, PATH.encode(), "sha256")
with open(PATH, "rb") as f:
data = f.read()
xts_key_1 = decrypt_key(data[0x28:0x38], mac[:16])
xts_key_2 = decrypt_key(data[0x38:0x48], mac[16:])
mac_key = data[0x20:0x28] + xts_key_1 + xts_key_2 + data[0x48:0x80]
if hmac.digest(mac_key, key2, "sha256") != data[:0x20]:
raise ValueError("HMAC validation failed!")
plaintext = decrypt_xts(data[0x4000:], xts1, xts2)
with open("decrypted.bin", "wb") as f:
f.write(plaintext)
References
In addition to my own reverse engineering, the following information was used to write this page: