tyler

2026-07-1215 minsecurityembeddedhardwarecryptopuf

Embedded Security In Practice: Root of Trust and Key Storage on FRDM-MCXN947

M4: using the SRAM PUF and EdgeLock Secure Enclave to make ECU keys device-bound at rest. What this gives you, and what it does not.

M4 closes the TS4 risk the M1 Threat Analysis and Risk Assessment (TARA) scored at Risk 3: a device-side secret sitting in plaintext flash is dumpable by anyone who can read flash. ATM-P0196 shows the damage concretely. On a 2021 Toyota RAV4 Prime EPS Electronic Control Unit (ECU), researchers extracted a Secure Onboard Communication (SecOC) AES master key from RAM over Controller Area Network (CAN) after a voltage fault injection gave firmware access, then used it to forge the very CAN messages SecOC is supposed to authenticate. The M4 control is device-bound key storage: the SRAM Physical Unclonable Function (PUF) wraps a key into a device-bound key code that reconstructs only on this die, and the EdgeLock Secure Enclave (ELS) uses the unwrapped key without ever writing it to flash. The stored key is bound to this die and cannot be cloned to another ECU.

It does not give you a key that never lives in RAM. When the ELS uses the key, the key transits RAM at unwrap, a running-code memory-scrape threat, not a flash-at-rest one. This post states that limit up front because it matters for what risk score this control actually earns.

Table 1: the risk before the control
IDThreat scenarioImpact (S/F/O/P)FeasibilityRisk (1-5)
TS4RoT keys extractedNegligible / Major / Major / NegligibleMedium3

S/F/O/P: Safety / Financial / Operational / Privacy, the impact categories from the M1 TARA. TS4 is Medium against keys sitting in plaintext flash and Low once they live in the ELS. That gap is the whole argument for M4.

The series roadmap

The series milestone roadmap
MilestoneWhat it covers
M0Baseline: a working hello_world on the board, before any security
M1Target, threat model, and lifecycle plan. No fuses burned
M2Secure boot: only signed firmware runs
M3Authenticated debug: keep debug, behind a credential
M4 (this post)Hardware root of trust and key storage
M5TrustZone-M isolation between secure and non-secure code
M6Flash encryption: a raw read returns ciphertext
M7Secure firmware update: signed, fail-safe, anti-rollback
M8The basics: TRNG, disabled ISP and DFU, no defaults

The security control: SRAM PUF + ELS key wrapping

The SRAM PUF is a hardware entropy source built into the MCX N947’s SRAM. On power-up, each cell settles to a 0 or 1 based on tiny manufacturing variations: the resulting bit pattern is unique to this die and reproducible across boots. Enrollment runs once, reading that raw fingerprint and computing a 1000-byte Activation Code (AC), which is helper data the PUF uses to reconstruct the same root key reliably even as the silicon ages. The AC is not sensitive and can be stored off-chip. The important thing is what it enables: a 256-bit Device Unique Key (DUK) that the ELS loads over a dedicated hardware secret key bus (KEYIN), making it unknown to any software or firmware outside the ELS and impossible to unload from it. That is confirmed directly in the MCX Nx4x Security Reference Manual Rev. 5 (public, doc MCXNX4XSRM, Sec. 9.8.1): “ELS is designed such that the value of NXP_DIE_MK_SK as well as keys derived from this key remains unknown to any software or firmware running outside of ELS.” The only ELS export command, KEYOUT, produces an RFC3394-wrapped blob (AES Key Wrap), never plaintext (SRM Sec. 13.4.2.8).

Protecting the SecOC master key

The demo is custom firmware I wrote for this milestone, not a stock NXP sample. It lives at firmware/m4-key-storage/m4_key_storage.c and builds inside the SDK’s els_pkc els_symmetric example slot, so it inherits that example’s working ELS build and runs on cm33_core0. It drives ELS and PUF calls directly: mcuxClMac_compute for the SecOC CMAC, and PUF_Enroll, PUF_Start, PUF_GetKey, PUF_Wrap, and PUF_Unwrap for the key storage.

The firmware runs in three stages; the serial log tags each line with its stage prefix, and the excerpts appear in the order 1, 3, 2 below. Stage 1 is the anti-pattern: the SecOC key in plaintext flash. Stage 3 enrolls the SRAM PUF and derives the device-unique root. Stage 2 is the fix: it wraps the key under that root and reconstructs it on-die. Stage 2 comes after stage 3 in the argument because the fix needs the root the enrollment produces.

The anti-pattern: key in plaintext flash

The firmware places the SecOC AES-128 master key as a const in flash and prints its address at boot. From m4-plaintext-key-dumped.log:

[stage1] SecOC master key stored in PLAINTEXT flash at E968
[stage1] key bytes (also visible in the binary): 5365634f434d61737465724b65792121
[stage1] SecOC AES-CMAC over the sample frame: 9ef592eee3732cb19b7cc863d28433b5
[stage1] a debug/ISP read of that address dumps the key in the clear (extractable).

Those bytes are "SecOCMasterKey!!". With the board in ISP mode, one blhost command retrieves them verbatim:

nxpdebugmbox -i mcu-link -s JABXJL3CFXOL0 cmd -f mcxn947 ispmode -m 0
blhost -u 0x1fc9:0x014f read-memory 0xE968 16
53 65 63 4f 43 4d 61 73 74 65 72 4b 65 79 21 21

Anyone who can read flash has the key and can forge SecOC Cipher-based Message Authentication Codes (CMACs).

The device-unique root: SRAM PUF enrollment

Before the fix can work, the PUF needs to produce a device-unique binding root. From m4-puf-enroll.log:

[stage3] SRAM PUF enrolled: activation code 1000 bytes, quality score 0 (<=7 is good)
[stage3] PUF started from the activation code, score 0
[stage3] derived a 256-bit device-unique key (non-zero: yes; bytes not printed)
[stage3] same context re-derives an identical key: yes
[stage3] a different context derives a different key: yes
[stage3] this root comes from the die's SRAM, not flash; it binds stage 2's key here.

Quality score 0 at enroll and 0 at start (lower is better; 7 is the threshold before the PUF reports a reconstruction problem). The derived key is deterministic per context and context-separated: the same PUF_GetKey call with a fixed context label always reconstructs the same key, and a different label gives a different key. That context-separation is what lets multiple distinct keys coexist under one PUF root. The SRM confirms the reconstruction property directly (Chapter 14, p.735): “When the AC of another device is used, the reconstruction will fail.”

The fix: a device-bound key code

PUF_Wrap turns the SecOC key into a 68-byte device-bound key code. From m4-els-key-not-readable.log:

[stage2] SecOC key wrapped to a device-bound PUF key code (68 bytes) at 20000570
[stage2] read that address over the debug probe: it is the wrapped code, not the key
[stage2] SecOC AES-CMAC (key unwrapped on this die): 9ef592eee3732cb19b7cc863d28433b5
[stage2] CMAC matches stage 1 (same SecOC key, reconstructed on-die): yes

A live debug-probe read of the code buffer:

pyocd cmd -t cortex_m -c "read8 0x20000570 68"
20000570:  80 00 10 38  03 03 00 00  00 00 00 00  00 00 00 00
20000580:  ae fb 7c 89  c5 50 2d 43  03 93 64 f5  7e 91 bf a1
20000590:  38 19 b7 97  64 e9 32 d6  0b a3 75 01  0c 53 76 58
200005a0:  1a 53 9a c9  9e 18 f3 b5  92 c4 8b ff  14 52 ae 6a
200005b0:  11 b8 16 9d

The SecOC key bytes (53 65 63 4f ...) appear nowhere in that code. PUF_Unwrap on this die recovers the key; the ELS CMAC comes back as 9ef592eee3732cb19b7cc863d28433b5, identical to the CMAC over the plaintext key. Same key, different stored form: a device-bound blob that is useless on any other part. The code also randomizes on each wrap call, so two wraps of the same key produce two different codes. Each code is valid only on this die.

Before and after. The plaintext key dumps verbatim from flash; the device-bound key code dumps as a blob that only this die's PUF can turn back into the key. The computed CMAC is identical either way.

Second verse: the mTLS device-identity key

The SecOC key is symmetric. An ECU commonly holds a second kind of secret: the private key for a mutual-TLS (mTLS) client certificate it presents when connecting to a back office or cloud service. Lifting that key from flash lets an attacker impersonate the vehicle indefinitely. Same TS4 risk, same protection, shown lighter because the mechanism is already established.

The demo wraps a host-generated NIST P-256 private scalar. A shipped device identity would be generated on-die instead, using mcuxClEcc_KeyGen, so no private key ever exists off the part (the els_pkc_asymmetric example lists P-256 key generation as supported on FRDM-MCXN947). The host-generated demo key is a deliberate simplification.

PUF_Wrap accepted the 32-byte scalar and produced an 84-byte device-bound key code. From m4-mtls-key-wrapped.log, the probe read of that code:

pyocd cmd -t cortex_m -c "read8 0x200005B8 84"
200005b8:  00 01 10 38 03 03 00 00 00 00 00 00 01 00 00 00
200005c8:  58 c0 55 25 c4 ff af 76 d2 16 ef fb 3a 71 d7 8c
200005d8:  77 f6 fb bf 56 68 a2 f6 07 be 39 ad 5b 54 0b 16
200005e8:  29 67 65 e9 05 44 a9 07 24 77 b3 7d c0 d8 ba dc
200005f8:  f7 d9 33 d0 e9 5d 12 ac 7e 8f a8 a5 bf e3 20 d1
20000608:  48 b4 59 e3

The private key bytes appear nowhere in that code. The scalar was never printed to the log, since it is a private key, so its absence from the stored code is confirmed not by a byte comparison but by the on-die signature that verifies below. On-die, mcuxClEcc_Sign ran ECDSA (Elliptic Curve Digital Signature Algorithm) P-256 over a fixed 32-byte challenge, then zeroed the scalar. Off-die, verify-mtls-sig.py verified the signature against the matching public key. From m4-mtls-sign-verify.log:

PASS: on-die signature verifies against the mTLS device public key
  challenge (digest): 6d544c532d6368616c6c656e67652d76656869636c652d6964656e7469747921
  signature (r||s):   646b737a81224d011842c662e54e07daac4b41db41104a6f614d824ef07f7e152ddb0a527ffb7d68423937f8af1d1fe5370d02baf6c620cb947b820c91a43f7f

Flipping one bit in the challenge (6d to 7d) with the same signature: refused (FAIL: signature does not verify against the public key). The same before/after that applies to the SecOC key applies here: the private scalar’s at-rest stored form is a device-bound blob, not the key. The limit is identical: the scalar is briefly in RAM during unwrap and signing.

TS4 re-score

Table 2: the risk after the control
IDResidual attack pathFeasibilityRisk (1-5)Closes fully at
TS4Key at rest is device-bound; key in RAM at unwrap is a residual; fuse lock deferredLow2Series-end capstone: PUF_ENROLL_DIS fuse burn + all controls committed

TS4 drops from Risk 3 (Medium feasibility, key in plaintext flash) to Risk 2 (Low feasibility, key at rest is a device-bound PUF code). Three residuals remain.

Residual 1: key in RAM at unwrap. The key is briefly in RAM when PUF_Unwrap runs, before the ELS uses it. An attacker who can execute arbitrary code on the live part during that window can scrape it. This is a different attacker class from the flash-dump path M4 closes: flash extraction requires only read access to storage; a RAM scrape during a live execution window requires code execution. M3’s debug lockdown mitigates it in depth by closing the easiest code-execution entry point.

Residual 2: PUF_ENROLL_DIS fuse not burned. The PUF_ENROLL_DIS fuse (fuse2 bit 20) is not yet burned, so a factory reset could allow re-enrollment with a different activation code. It will not be burned until the series-end capstone, after the board is completely configured and proven in DEVELOP.

Residual 3: TS5 fault injection and side-channel attacks. The control M4 adds is device-bound key storage; it does not address fault injection or side-channel leakage during crypto operations. Those are M5+ territory.

SRAM PUF remanence is a real attack class: researchers at TU Darmstadt demonstrated that, on a TI Stellaris LM4F120H5QR (Cortex-M4F, the same broad class as an ARM Cortex-M with on-die SRAM, not the MCX N947 itself), an attacker with cryogenic cooling, physical access, and the ability to write SRAM can write a chosen pattern and have the bootloader read it back as the PUF response, forging the derived key (Anagnostopoulos et al., IEEE Euromicro DSD 2018). Preconditions include cryogenic temperatures (the paper’s tested range was around -110C to -40C), physical access to the board, and a writable SRAM window. A production ELS may carry countermeasures not documented in the public SRM. With that said, this goes back to a risk assessment, taking into account the value of what you are protecting, the scalability of the attack, and the effort required to exploit it.

The class of problem M4 closes is “key extractable from storage.” The residual it leaves open is “key in RAM during use.” Those are two different attack classes, and conflating them is how security claims get oversold. On a microcontroller without a hardware path from the PUF directly into the crypto engine, you are always choosing between “never in storage” and “never in RAM.” The stock SDK gives you the first, not the second. Knowing which property you have, and which you don’t, is what makes the risk score defensible.

Staged now, committed at the series end

The board ran this entire milestone in DEVELOP, OTP blank. The PUF enrollment, wrap, and unwrap operations are reversible; the PUF_ENROLL_DIS fuse and the CUST_MK_SK One-Time Programmable (OTP) lock are the key-storage hardening steps reserved for the series-end capstone. Same commit-once posture as M2 and M3.

M5 opens the TrustZone-M isolation layer, separating the secure-side key operations from untrusted application code. A future SecOC key-management implementation on this part would derive or wrap per-ECU keys under the PUF, never storing them in the clear; that design implication is the subject of the deferred SecOC post.

Further reading

Acronyms

Acronyms used in this post
AcronymTermWhat it is
ACActivation Code1000-byte helper data from SRAM PUF enrollment; used to reconstruct the same root key across boots
AES-128Advanced Encryption Standard, 128-bitSymmetric cipher used for SecOC MAC computation; the key M4 protects
ATMAutomotive Threat MatrixISAC catalog of automotive attack techniques; ATM-P0196 is the SecOC key extraction example
CANController Area NetworkIn-vehicle bus; SecOC authenticates CAN frames
CMACCipher-based Message Authentication CodeThe SecOC authentication primitive; AES-CMAC-128 per NIST SP 800-38B
ECUElectronic Control UnitAutomotive embedded computer; the hardware role the FRDM-MCXN947 plays in this series
ECDSAElliptic Curve Digital Signature AlgorithmAsymmetric signing algorithm; P-256 ECDSA sign/verify/keygen demonstrated on this part
ELSEdgeLock Secure EnclaveNXP S50 secure subsystem on the MCX N947; handles AES, CMAC, ECDSA, and key wrapping/unwrapping
mTLSMutual TLSTLS variant where both client and server authenticate; the ECU’s private key is the device-identity credential
OTPOne-Time ProgrammableFuse bank; writes are permanent
PUFPhysical Unclonable FunctionHardware entropy source deriving a device-unique key from SRAM startup values; the root M4 builds on
SecOCSecure Onboard CommunicationAUTOSAR stack component authenticating CAN frames with AES-CMAC; the symmetric key it uses is what M4 protects
SRMSecurity Reference ManualNXP MCX Nx4x Security Reference Manual, Rev. 5 (2025-05-19)
TARAThreat Analysis and Risk AssessmentISO/SAE 21434 security analysis method; the M1 output that motivates this series
← OlderAutomotive threat model as code on Threagile