Skip to content

crypto: add Nordic SoftDevice AES-128 ECB backend for nRF5 SDK - #340

Draft
buckleypaul wants to merge 1 commit into
HubbleNetwork:mainfrom
buckleypaul:crypto/nrf-softdevice-backend
Draft

buckleypaul wants to merge 1 commit into
HubbleNetwork:mainfrom
buckleypaul:crypto/nrf-softdevice-backend

Conversation

@buckleypaul

@buckleypaul buckleypaul commented Sep 18, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds src/crypto/nrf_softdevice.c, a crypto backend for legacy nRF5 SDK products that builds AES-CTR and AES-CMAC on the SoftDevice's AES-128 ECB primitive (sd_ecb_blocks_encrypt). Products that already ship a SoftDevice can then use Hubble encryption without linking mbedTLS, which saves flash on constrained nRF52 parts.

  • 128-bit keys only. The SoftDevice ECB only implements AES-128, so the file fails with an #error unless CONFIG_HUBBLE_KEY_SIZE is 16. nRF51 is also rejected at compile time.
  • Calling-context guard. A SoftDevice SVC call made at or above the SVC priority escalates to a HardFault. So before each call the backend checks IPSR, PRIMASK and BASEPRI, and returns -EACCES instead of crashing.
  • No Kconfig or Zephyr integration, by design. nRF Connect SDK builds keep using PSA. A bare-metal build selects this backend by compiling the file.
  • Docs. The bare-metal quickstart gets a "Nordic SoftDevice Backend" section covering key size and calling context, and the project-organization page lists the new backend.

Testing

  • Host test harness: nrf.h/nrf_soc.h were stubbed and the ECB call was backed by OpenSSL.
    • CMAC matches all four RFC 4493 AES-128 test vectors (message lengths 0, 16, 40 and 64 bytes).
    • CTR output matches OpenSSL AES-128-CTR for every length from 0 to 64 bytes, including a rollover of the counter's last byte.
  • Building with CONFIG_HUBBLE_NETWORK_KEY_256 fails at compile time, as intended.
  • clang-format is clean.
  • Verified on real hardware: ran on an nRF52 DK using the reference application, confirming the SoftDevice ECB backend builds and produces working encrypted advertisements outside the host-stub harness.

Footprint vs mbedTLS

These are the flash, RAM, stack and heap that the Hubble SDK adds to an nRF5 SDK firmware: the core (hubble.c, hubble_ble.c, hubble_crypto.c), the crypto backend, the mbedTLS library objects, and any C library code they pull in. The rest of the application is not counted.

Crypto backend Flash -Os Flash -O3 Static RAM Peak stack, hubble_ble_advertise_get (-Os) Heap per CMAC
SoftDevice ECB (this PR) 2,076 B 6,068 B 23 B 448 B (588 B if the error log fires) 0 B
mbedTLS 2.16.10 (nRF5 SDK), AES tables in flash 8,346 B 13,332 B 27 B 704 B 316 B
mbedTLS 2.16.10 (nRF5 SDK), AES tables in RAM 6,426 B 11,740 B 8,775 B 2,712 B on the first call, 712 B after 316 B
mbedTLS 3.6.6, AES tables in flash 9,280 B 15,944 B 31 B 752 B 316 B
mbedTLS 3.6.6, AES tables in RAM 7,296 B 14,240 B 8,779 B 1,184 B on the first call, 760 B after 316 B

Compared with mbedTLS 2.16.10 with AES tables in flash (what the reference app currently ships):

  • Flash: 6,270 B less at -Os (−75%) and 7,264 B less at -O3 (−54%).
  • Heap: none, against 316 B per CMAC.
  • Stack: 256 B less on the advertise path.

Compared with mbedTLS 2.16.10 with AES tables in RAM:

  • Flash: 4,350 B less at -Os.
  • Static RAM: 8,752 B less.
  • Stack: about 2.2 KB less on the first call, which is when mbedTLS builds its tables.

Methodology

  • Firmware and target: hubble-reference-nordic-softdevice built for nRF52840 / S140 (BOARD=pca10056) against this branch (HUBBLE_SDK_ROOT=<this tree>).
  • Toolchain: GNU Arm Embedded 10.3-2021.10 with newlib-nano, the reference app's own toolchain.
  • Configuration:
    • Every variant uses a 128-bit key, the device-uptime counter, the nonce check and the BLE network.
    • Only the crypto backend and the optimization level change between builds.
    • Each variant was built at both -Os and -O3.
  • mbedTLS builds:
    • Minimal config with only MBEDTLS_AES_C, MBEDTLS_CIPHER_C, MBEDTLS_CMAC_C and MBEDTLS_CIPHER_MODE_CTR.
    • "AES tables in flash" adds MBEDTLS_AES_ROM_TABLES and MBEDTLS_AES_FEWER_TABLES. "AES tables in RAM" leaves both out.
    • Sources are aes.c, cipher.c, cipher_wrap.c, cmac.c and platform_util.c. 3.6.6 also needs constant_time.c.
  • Flash and RAM:
    • Each object's size is taken from the linker map file (.text + .rodata + .data initialisers for flash, .data + .bss for RAM).
    • As a cross-check, each total was compared with arm-none-eabi-size on the same app with the SDK stubbed out.
    • malloc/free/memcpy are already linked in through the app's printf, so they aren't charged to any backend. mbedTLS adds calloc (76 B). An app with no printf would pay about 520 B more flash for the mbedTLS variants.
  • Stack:
    • Worst case from -fstack-usage / -fcallgraph-info=su, plus reading the newlib function prologues in the disassembly.
    • The mbedTLS cipher_wrap function pointers were resolved by hand.
    • Every frame is fixed-size and nothing recurses.
    • The SVC exception frame and the SoftDevice's own stack use are not included.
  • Heap: mbedTLS cipher_setup and cmac_starts allocate the AES context (280 B) and the CMAC context (36 B). Both are freed before hubble_crypto_cmac() returns. newlib-nano's per-allocation overhead is not included.
  • Not counted: the SoftDevice's own flash and RAM, which are already on the device.
  • Why -O3 is larger: GCC unrolls the loops in hubble_crypto.c and in the backend.
  • Hardware: these figures come from builds only. They were not measured on a device.

Legacy nRF5 SDK products already ship a SoftDevice with an AES-128 ECB
primitive. Building AES-CTR and CMAC on top of it avoids linking mbedTLS
just for Hubble encryption, which matters on flash-constrained nRF52
parts. The SoftDevice only implements AES-128, so the backend refuses to
build with 256-bit keys.

SoftDevice SVC calls escalate to a HardFault when made at or above the
SVC priority, so the backend checks the execution priority first and
returns -EACCES instead of crashing. The calling-context rules are
documented in the bare-metal quickstart.

The backend is intentionally not exposed via Kconfig: nRF Connect SDK
builds should keep using PSA. Bare-metal builds select it by compiling
src/crypto/nrf_softdevice.c.

Signed-off-by: Paul Buckley <paul@hubble.com>
``src/crypto/nrf_softdevice.c`` builds AES-CTR and AES-CMAC on the
SoftDevice's AES-128 ECB (``sd_ecb_blocks_encrypt``). It is intended for the
legacy nRF5 SDK only and works with the nRF52 SoftDevices (S112, S113, S122,
S132, S140). nRF51 devices are not supported. nRF Connect SDK builds should use

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this work with S3XX SoftDevices (BLE + ANT)?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure. I would cross that bridge if it came - I don't think that's a particularly common choice.

return NVIC_GetPriority((IRQn_Type)((int32_t)ipsr - 16)) > svc_prio;
}

static int _ecb_encrypt(const uint8_t key[HUBBLE_AES_BLOCK_SIZE],

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have found more than 1 instance where ECB exists, but CMAC and/or CTR don't (some Arduinos, Renesas).

What do you think about some ecb.c crypto implementation that is effectively this file, but it calls hubble_ecb_encrypt() which would live in its own file ecb_nrf_softdevice.c or ecb_renesas_da14xxx.c.

I see good value in making lines 94 to the end of the file 1 shared file, and everything above it modular for various devices

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll leave that to you and @ceolin - it might make sense and since ECB is needed for BLE it might make things a bit more portable? This isn't my area of expertise though.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants