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
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 (xspi1on i.MX943)regions— Memory regions to encrypt (up to 15). Each region has:start_address/end_address— Aligned to 1KB boundarynonce— 64-bit value used in IV derivationfw_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:
IPED config table (
iped_config.bin) — Binary structure loaded by ELE at bootEncrypted firmware (
encrypted_blob.bin) — PRINCE-encrypted dataFuse 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:
Flash
iped_config.binto the IPED config region in external flashFlash
encrypted_blob.binto the firmware region (offset 0x1000)On boot, ELE reads IPED0_KEY from fuses and derives the IV
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.