Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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

OffsetSizeDescription
0x0128Header
0x800x3F80Padding
0x4000Encrypted file

Remember that AES-XTS requires two encryption keys by design.

OffsetSizeDescription
0x032Header MAC
0x204Magic number (NAX0)
0x244Padding (always 0)
0x2816Encrypted AES-XTS key 1
0x3816Encrypted AES-XTS Key 2
0x488Size of encrypted file (little endian)
0x5048Padding (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:

TypeKey source
Save data9a951517b16e8f7f1f68263152ea296a
Titles67c793f0f24fded075495dca006d99c2
Custom storage4f2ca5a3fc999a47c03ee004485b2fd0

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:

TypeKey source
Save data2449b722726703a81965e6e3ea582fdd
Titles5841a284935b56278b8e1fc518e99f2b
Custom storage370c345e12e4cefe21b58e64db52af35

Then, a HMAC-SHA256 is calculated over the filesystem path using the derived key. The filesystem path is a path from the following roots:

TypeKey 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:

  1. aes_kek_generation_source is decrypted with master_key_00.
  2. sd_card_kek_source is decrypted with the key from step 1.
  3. aes_key_generation_source is decrypted with the key from step 2.
  4. A key source that depends on the filesystem type is XORed with the SD card encryption seed.
  5. 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: