i.MX943 IPED (Inline PRINCE Encryption/Decryption)#

This notebook demonstrates how to encrypt firmware images for i.MX943 using the IPED feature. IPED provides transparent on-the-fly encryption/decryption of data on the FlexSPI bus using the PRINCE lightweight block cipher.

The encrypted image can then be flashed to external NOR/NAND memory, and the SoC decrypts it transparently during XIP (execute-in-place) execution.

1. Prerequisites#

  • SPSDK is needed with examples extension: pip install spsdk[examples]

  • Optionally install the C++ backend for faster encryption: pip install spsdk-iped

IPED Overview#

Concept

Description

User Key

128-bit key programmed into OTP fuses (IPED0_KEY0-3)

Context Key

Per-region key from SoC ROM (XORed with user key to form effective key)

IV Derivation

ELE derives the PRINCE IV from nonce + fw_version using AES-ECB

PRINCE

Lightweight 64-bit block cipher with 128-bit key, used in CTR mode

Region

Up to 15 independently configurable memory regions

Encryption Flow#

user_key (fuses) ──┐
                   XOR ──► effective_key ──┐
context_key (ROM) ─┘                       │
                                           ▼
nonce + fw_version ──► AES-ECB(user_key) ──► IV
                                           │
plaintext ──► PRINCE_CTR(effective_key, IV, address) ──► ciphertext (flash)
# This env variable sets colored logger output to STDOUT
%env JUPYTER_SPSDK=1
# Set a magic for command execution and echo
%alias execute echo %l && %l
%alias_magic ! execute

WORKSPACE = "workspace/"
INPUTS = "inputs/"
env: JUPYTER_SPSDK=1
Created `%!` as an alias for `%execute`.

2. IPED Configuration#

First, generate the IPED configuration template for i.MX943:

from spsdk.utils.jupyter_utils import YamlDiffWidget

YamlDiffWidget("inputs/iped_mimx943.diffc").html
nxpimage iped get-template -f mimx943 -o workspace/iped_mimx943.yaml --force
Creating workspace/iped_mimx943.yaml template file.

Configuration Differences

2.1 Configuration Explanation#

The IPED configuration YAML defines:

  • user_key — The 128-bit key that will be programmed into fuses. This is the key that ELE reads from OTP at boot time.

  • xspi_instance — Which FlexSPI interface to use (xspi1 on i.MX943)

  • regions — Memory regions to encrypt (up to 15). Each region has:

    • start_address / end_address — Aligned to 1KB boundary

    • nonce — 64-bit value used in IV derivation

    • fw_version — 32-bit firmware version (anti-rollback)

  • data_blobs — Firmware files to encrypt, with their load addresses

Let’s look at our prepared configuration:

The diff widget above highlights the customizations made to the generated template. Key changes from the default template:

  • Added a specific user_key (128-bit PRINCE key for fuse programming)

  • Set fw_version to 0 (anti-rollback counter)

  • Configured region 0 with start/end addresses for the encrypted flash area

  • Added a data_blob pointing to our firmware binary

3. Prepare Firmware Image#

In this example we create a simple test firmware. In production, this would be your application binary (placed after the FCB header at offset 0x1000).

# Create a sample firmware binary for demonstration
import os

firmware_size = 4096
pattern = bytes(range(256)) * (firmware_size // 256)
firmware_path = f"{INPUTS}firmware.bin"
with open(firmware_path, "wb") as f:
    f.write(pattern)
print(f"Created: {firmware_path} ({firmware_size} bytes)")
Created: inputs/firmware.bin (4096 bytes)

4. Export IPED Encrypted Image#

The nxpimage iped export command generates:

  1. IPED config table (iped_config.bin) — Binary structure loaded by ELE at boot

  2. Encrypted firmware (encrypted_blob.bin) — PRINCE-encrypted data

  3. Fuse script (iped_fuses_nxpele.bcf) — Commands to program IPED key fuses

%! nxpimage iped export -c {INPUTS}iped_config.yaml
nxpimage iped export -c inputs/iped_config.yaml
Exporting IPED table
workspace/iped_config.bin
workspace/encrypted_blob.bin
workspace/iped_fuses_nxpele.bcf

5. Program IPED Fuses#

The following fuses must be programmed to enable IPED on i.MX943:

Fuse

OTP Index

Description

IPED0_KEY0

180

User key bits [31:0] (LSB)

IPED0_KEY1

181

User key bits [63:32]

IPED0_KEY2

182

User key bits [95:64]

IPED0_KEY3

183

User key bits [127:96] (MSB)

BOOT_CFG9 (bit 26)

33

IPED0_ENABLE — must be set to 1

⚠️ Warning: Fuse programming is irreversible! Double-check all values before burning.

Fuse Word Mapping#

For user_key = 0x7bfb7a1cd3c2a9a98bcf48daaa081bbe:

IPED0_KEY3 (fuse 183) = 0x7BFB7A1C   ← MSB
IPED0_KEY2 (fuse 182) = 0xD3C2A9A9
IPED0_KEY1 (fuse 181) = 0x8BCF48DA
IPED0_KEY0 (fuse 180) = 0xAA081BBE   ← LSB
BOOT_CFG9  (fuse 33)  = bit 26 = 1   ← IPED0_ENABLE

5.1 Program Fuses using nxpele#

The export command generates a .bcf script that can be executed with nxpele batch:

# Display the generated BCF fuse programming script

bcf_files = [f for f in os.listdir(WORKSPACE) if f.endswith(".bcf")]
if bcf_files:
    print(f"Generated fuse script: {bcf_files[0]}")
    print("=" * 60)
    with open(os.path.join(WORKSPACE, bcf_files[0])) as f:
        print(f.read())
Generated fuse script: iped_fuses_nxpele.bcf
============================================================
# nxpele IPED fuses programming script
# Generated by SPSDK 3.11.0.dev52+g79331e3e8
# Family: mimx943, Revision: latest

# Value: 0xAA081BBE
# Description: IPED Key for IPED connected to cSPI interface 0
# OTP ID: IPED0_KEY0

write-fuse --index 180 --data 0xAA081BBE

# Value: 0x8BCF48DA
# Description: IPED Key for IPED connected to cSPI interface 0
# OTP ID: IPED0_KEY1

write-fuse --index 181 --data 0x8BCF48DA

# Value: 0xD3C2A9A9
# Description: IPED Key for IPED connected to cSPI interface 0
# OTP ID: IPED0_KEY2

write-fuse --index 182 --data 0xD3C2A9A9

# Value: 0x7BFB7A1C
# Description: IPED Key for IPED connected to cSPI interface 0
# OTP ID: IPED0_KEY3

write-fuse --index 183 --data 0x7BFB7A1C

# Value: 0x04000000
# Description: Boot Configuration See tab BOOT_CFG
# Bitfield: IPED0_ENABLE, Description: Enable IPED0 at boot, Value: 0x1
# WARNING! Partially set register, check all bitfields before writing
# OTP ID: BOOT_CFG9

write-fuse --index 33 --data 0x4000000

To program the fuses on a connected device:

nxpele batch -c "workspace/iped_fuses_nxpele.bcf"

5.2 Additional Security Fuses#

For production, also consider programming:

Fuse

Purpose

IPED0_KEY_LOCK

Prevents modification of IPED0_KEY fuses

IPED0_DISABLE

If set, IPED cannot be used (do NOT set this!)

Refer to the i.MX943 Security Reference Manual for the complete fuse map.

6. Verify Encrypted Output#

Let’s confirm the encryption produced valid output:

# Compare plaintext vs encrypted output
with (
    open(f"{INPUTS}firmware.bin", "rb") as plain_file,
    open(f"{WORKSPACE}encrypted_blob.bin", "rb") as encrypted_file,
):
    plain = plain_file.read()
    encrypted = encrypted_file.read()

print(f"Plaintext size:  {len(plain)} bytes")
print(f"Encrypted size:  {len(encrypted)} bytes")
print(f"Sizes match:     {len(plain) == len(encrypted)}")
print()
print("First 32 bytes:")
print(f"  Plain:     {plain[:32].hex()}")
print(f"  Encrypted: {encrypted[:32].hex()}")
print(f"  Different: {plain[:32] != encrypted[:32]}")
Plaintext size:  4096 bytes
Encrypted size:  4096 bytes
Sizes match:     True

First 32 bytes:
  Plain:     000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f
  Encrypted: 3988c52d6de850bb99ff131c0349a68a3c709a1b61c770e27dcc3471a8b3afc6
  Different: True

7. Flash and Boot#

After generating the encrypted image and programming the fuses:

  1. Flash iped_config.bin to the IPED config region in external flash

  2. Flash encrypted_blob.bin to the firmware region (offset 0x1000)

  3. On boot, ELE reads IPED0_KEY from fuses and derives the IV

  4. IPED hardware decrypts data transparently as the CPU fetches instructions

Production Checklist#

☐ Generate random 128-bit user_key (use a secure RNG)
☐ Program IPED0_KEY0-3 fuses with the key
☐ Set IPED0_ENABLE in BOOT_CFG9
☐ Lock IPED0_KEY fuses (IPED0_KEY_LOCK)
☐ Encrypt firmware: nxpimage iped export -c config.yaml
☐ Flash encrypted image + IPED config table to external memory
☐ Verify boot from encrypted flash

Performance Note#

For large firmware images, install the optional C++ backend for ~100x faster offline encryption:

pip install spsdk-iped

SPSDK auto-detects the native backend when available. The on-chip IPED hardware operates at full bus speed regardless of the offline tool used.