13 KiB
wolfCrypt test and benchmark on EFR32xG25
Runs the wolfCrypt algorithm self-test and benchmark on an EFR32xG25 with crypto routed to the Secure Element through the wolfSSL crypto callback port. Builds headlessly with slc-cli and GNU Arm - no Simplicity Studio GUI needed.
For the port itself, including which algorithms are offloaded and the Secure Vault key management API, see wolfcrypt/src/port/silabs/README.md in the wolfSSL repository.
wolfSSL source
This project compiles the wolfSSL sources directly - there is no prebuilt library. build.sh expects a wolfSSL checkout beside wolfssl-examples:
GitHub/
wolfssl/
wolfssl-examples/embedded/efr32xg25/
Point WOLFSSL_ROOT somewhere else if yours does not sit there:
WOLFSSL_ROOT=/path/to/wolfssl ./build.sh
The Secure Element crypto callback port arrived in wolfSSL PR 11267; until that is in a release, use a checkout that contains it.
Hardware
An xG25 radio board (BRD4270B, BRD4271A or BRD4272A) on a BRD4002A/BRD4001A mainboard. All three radio boards carry EFR32FG25B222F1920IM56: Series 2 Config 5, Secure Vault High, 1920 KB flash. Set BOARD if yours is not the default BRD4270B:
BOARD=brd4271a ./build.sh
The application prints the SE firmware version and the detected Secure Vault level at startup, so a captured log records which silicon produced the numbers under it.
Toolchain
Install headlessly with Silicon Labs Tool (SLT). As of SLC-CLI v6 the CLI is no longer a direct download; SLT fetches it along with the SDK, GNU Arm and Commander from a recipe:
mkdir -p ~/silabs && cd ~/silabs
wget -O slt-cli.zip \
"https://www.silabs.com/documents/public/software/slt-cli-1.2.1-linux-x64.zip"
unzip -o slt-cli.zip && chmod +x slt
cat > pkg.slt <<'RECIPE'
[dependency]
simplicity-sdk = "~"
slc-cli = "~"
gcc-arm-none-eabi = "~"
commander = "~"
RECIPE
./slt install
Check the current SLT version on the command line development page. build.sh and flash.sh read ~/.silabs/sdks.json and ~/.silabs/tools.json to locate everything, so no paths are hardcoded. Override SDK, SLC, GCC_DIR, JAVA_DIR or COMMANDER in the environment if your install lives elsewhere; the scripts say so by name if a registry is missing or a tool was never installed.
Developed against Simplicity SDK 2026.6.1 (platform 6.0.0), slc-cli 6.0.23, Commander 1.24.3, GNU Arm 14.2.rel1.
Build and flash
./build.sh # slc generate + make, output in ./build
./flash.sh # commander device info, flash, reset
Then read the VCOM output at 115200 8N1. On a bench running the uart-monitor daemon, tail its log rather than opening the port directly.
What to expect
wolfcrypt_test must report a full PASS. Because WOLFSSL_SILABS_CRYPTOCB points WC_USE_DEVID at the SE device, every algorithm the port claims runs on the Secure Element, so a PASS is direct evidence the callback is correct against the known-answer vectors.
The benchmark then runs each algorithm twice, labelling rows HW and SW, which gives a hardware-versus-software comparison in one run.
Measured results
Captured on an EFR32FG25B222F1920IM56 (Secure Vault High, SE firmware 2.2.0) with this project, wolfSSL 5.9.2. wolfcrypt_test reports a full PASS in both builds below; the software/Secure Element pairs are the HW and SW rows the benchmark prints in one run.
Both builds define the --enable-harden options (ECC_TIMING_RESISTANT, WC_RSA_BLINDING), so these are hardened numbers. They cost nothing measurable here: this configuration routes P-256 through WOLFSSL_SP_MATH_ALL/WOLFSSL_HAVE_SP_ECC, which is already constant time, and every ECC and RSA row moved by less than 0.3% when the options were added.
The two software columns are two builds of this project. Software (C) is what you get out of the box. Software (Thumb2 asm) additionally enables the WOLFSSL_ARMASM block near the top of user_settings.h -- flip its #if 0 to #if 1 to reproduce it. Both builds already use WOLFSSL_SP_ARM_CORTEX_M_ASM for ECC, so the ECC rows do not move.
| Algorithm | Software (C) | Software (Thumb2 asm) | Secure Element | SE vs C | SE vs asm |
|---|---|---|---|---|---|
| RNG SHA-256 DRBG | 281 KiB/s | 408 KiB/s | 89 KiB/s | 0.3x | 0.2x |
| AES-128-CBC-enc | 856 KiB/s | 986 KiB/s | 2.63 MiB/s | 3.2x | 2.7x |
| AES-128-CBC-dec | 854 KiB/s | 985 KiB/s | 2.65 MiB/s | 3.2x | 2.8x |
| AES-192-CBC-enc | 722 KiB/s | 826 KiB/s | 2.60 MiB/s | 3.7x | 3.2x |
| AES-192-CBC-dec | 722 KiB/s | 820 KiB/s | 2.59 MiB/s | 3.7x | 3.2x |
| AES-256-CBC-enc | 627 KiB/s | 710 KiB/s | 2.58 MiB/s | 4.2x | 3.7x |
| AES-256-CBC-dec | 626 KiB/s | 712 KiB/s | 2.59 MiB/s | 4.2x | 3.7x |
| AES-128-GCM-enc | 293 KiB/s | 629 KiB/s | 2.37 MiB/s | 8.3x | 3.9x |
| AES-128-GCM-dec | 291 KiB/s | 626 KiB/s | 2.26 MiB/s | 8.0x | 3.7x |
| AES-192-GCM-enc | 275 KiB/s | 558 KiB/s | 2.38 MiB/s | 8.9x | 4.4x |
| AES-192-GCM-dec | 274 KiB/s | 556 KiB/s | 2.24 MiB/s | 8.4x | 4.1x |
| AES-256-GCM-enc | 260 KiB/s | 502 KiB/s | 2.38 MiB/s | 9.4x | 4.8x |
| AES-256-GCM-dec | 258 KiB/s | 501 KiB/s | 2.23 MiB/s | 8.8x | 4.6x |
| AES-128-GCM-enc-no_AAD | 296 KiB/s | 632 KiB/s | 2.44 MiB/s | 8.4x | 3.9x |
| AES-128-GCM-dec-no_AAD | 294 KiB/s | 630 KiB/s | 2.28 MiB/s | 7.9x | 3.7x |
| AES-192-GCM-enc-no_AAD | 278 KiB/s | 560 KiB/s | 2.41 MiB/s | 8.9x | 4.4x |
| AES-192-GCM-dec-no_AAD | 276 KiB/s | 559 KiB/s | 2.30 MiB/s | 8.5x | 4.2x |
| AES-256-GCM-enc-no_AAD | 262 KiB/s | 505 KiB/s | 2.35 MiB/s | 9.2x | 4.8x |
| AES-256-GCM-dec-no_AAD | 261 KiB/s | 503 KiB/s | 2.23 MiB/s | 8.8x | 4.5x |
| AES-128-ECB-enc | 876 KiB/s | 992 KiB/s | 2.84 MiB/s | 3.3x | 2.9x |
| AES-128-ECB-dec | 882 KiB/s | 988 KiB/s | 2.77 MiB/s | 3.2x | 2.9x |
| AES-192-ECB-enc | 737 KiB/s | 824 KiB/s | 2.74 MiB/s | 3.8x | 3.4x |
| AES-192-ECB-dec | 741 KiB/s | 833 KiB/s | 2.79 MiB/s | 3.9x | 3.4x |
| AES-256-ECB-enc | 638 KiB/s | 711 KiB/s | 2.70 MiB/s | 4.3x | 3.9x |
| AES-256-ECB-dec | 640 KiB/s | 710 KiB/s | 2.71 MiB/s | 4.3x | 3.9x |
| AES-128-CTR | 852 KiB/s | 977 KiB/s | 2.63 MiB/s | 3.2x | n/a |
| AES-192-CTR | 720 KiB/s | 820 KiB/s | 2.62 MiB/s | 3.7x | n/a |
| AES-256-CTR | 625 KiB/s | 705 KiB/s | 2.63 MiB/s | 4.3x | n/a |
| AES-CCM-enc | 426 KiB/s | 460 KiB/s | 2.20 MiB/s | 5.3x | 4.9x |
| AES-CCM-dec | 425 KiB/s | 459 KiB/s | 2.14 MiB/s | 5.1x | 4.8x |
| AES-CCM-enc-no_AAD | 426 KiB/s | 460 KiB/s | 2.20 MiB/s | 5.3x | 4.9x |
| AES-CCM-dec-no_AAD | 425 KiB/s | 459 KiB/s | 2.11 MiB/s | 5.1x | 4.7x |
| SHA-1 | 3.91 MiB/s | 3.92 MiB/s | 3.63 MiB/s | 0.9x | 0.9x |
| SHA-224 | 1.23 MiB/s | 1.99 MiB/s | 3.67 MiB/s | 3.0x | 1.8x |
| SHA-256 | 1.23 MiB/s | 1.99 MiB/s | 3.67 MiB/s | 3.0x | 1.8x |
| AES-128-CMAC | 793 KiB/s | 856 KiB/s | 2.45 MiB/s | 3.2x | 2.9x |
| AES-256-CMAC | 593 KiB/s | 640 KiB/s | 2.40 MiB/s | 4.1x | 3.8x |
| HMAC-SHA-1 | 3.87 MiB/s | 3.87 MiB/s | 3.01 MiB/s | 0.8x | 0.8x |
| HMAC-SHA-224 | 1.22 MiB/s | 1.97 MiB/s | 3.04 MiB/s | 2.5x | 1.5x |
| HMAC-SHA-256 | 1.22 MiB/s | 1.97 MiB/s | 3.04 MiB/s | 2.5x | 1.5x |
| ECC 256 key gen | 125.4 ops/s | 125.3 ops/s | 180.6 ops/s | 1.4x | 1.4x |
| ECDHE 256 agree | 65.4 ops/s | 65.4 ops/s | 179.8 ops/s | 2.7x | 2.7x |
| ECDSA 256 sign | 67.8 ops/s | 67.8 ops/s | 173.4 ops/s | 2.6x | 2.6x |
| ECDSA 256 verify | 41.9 ops/s | 41.9 ops/s | 163.8 ops/s | 3.9x | 3.9x |
Where the Secure Element is slower the numbers are reported as measured. SHA-1 and HMAC-SHA-1 are cheap enough on this Cortex-M33 that the SE round trip costs more than it saves. The RNG row compares asking the SE for every byte against a SHA-256 DRBG seeded once from that same SE TRNG - seeding a DRBG is the normal way to use a hardware entropy source, so the HW row is raw TRNG throughput rather than the cost of getting random data. AES-GCM gains the most, up to 9.4x.
The RNG row is the only one that moves appreciably between runs: the DRBG reseeds from the SE TRNG at a fixed generate count and one reseed is expensive at 89 KiB/s, so the software figure swings by roughly 15% depending on whether a reseed lands inside the one-second measurement window.
AES-CTR reads n/a in the last column because the port deliberately declines CTR when WOLFSSL_ARMASM is defined -- the Thumb2 keystream remainder and the SE's do not line up -- so that build has no Secure Element CTR number.
RSA and DH are omitted because Series 2 has no RSA hardware, so both columns run in software. SHA-384/512 likewise, and they are always left to software (see the port README). ChaCha20-Poly1305 has no paired row because the benchmark drives it through the one-shot wc_ChaCha20Poly1305_Encrypt(), which carries no devId and so always runs in software. The pre-keyed API the TLS record layer uses does carry one, and that path is offloaded.
To check the software fallback path, offload a subset by adding one or more engine macros to user_settings.h (for example only WOLFSSL_SILABS_CRYPTOCB_HASH) and re-run: everything else must still pass, in software.
TLS 1.3 over the Secure Element
Flip the WOLFSSL_XG25_TLS13 block at the top of user_settings.h to #if 1
and rebuild. The application then runs tls13_test.c between the wolfCrypt
test and the benchmark: a client and a server, both on the device, wired
together through two in-memory byte queues, with the Secure Element devId set
on both. It runs the handshake once per TLS 1.3 cipher suite and then pushes
application data through the record layer.
This covers ground the known answer vectors cannot. Handshake and record buffers are cursors into a larger buffer, so the engines see arbitrary byte offsets rather than aligned stack arrays, and one cipher object is reused across many chained records, so any state the port fails to carry between calls shows up as a decrypt failure instead of passing on a single shot.
The three suites cover different engines. TLS13-AES128-GCM-SHA256 drives the
key schedule and transcript hash through the Secure Element, while
TLS13-AES256-GCM-SHA384 runs those in software, so between them they cover
the offloaded and the fallback hash path. TLS13-CHACHA20-POLY1305-SHA256
covers the ChaCha20-Poly1305 record cipher, which the AES suites never reach.
Measured result on the kit:
--- TLS 1.3 over the Secure Element ---
[TLS13-AES128-GCM-SHA256]
handshake OK: TLSv1.3 TLS_AES_128_GCM_SHA256
record layer OK: 9 payloads, 1 to 255 bytes
[TLS13-AES256-GCM-SHA384]
handshake OK: TLSv1.3 TLS_AES_256_GCM_SHA384
record layer OK: 9 payloads, 1 to 255 bytes
[TLS13-CHACHA20-POLY1305-SHA256]
handshake OK: TLSv1.3 TLS_CHACHA20_POLY1305_SHA256
record layer OK: 9 payloads, 1 to 255 bytes
tls13_test returned 0 (PASS)
The ChaCha suite is the one that needs the devId carried on the cipher
object: the record layer keys a ChaCha once per epoch and then calls the
pre-keyed API, so without a devId on that object there is nothing to route
on. Instrumenting the port's dispatch during this run counts 28
ChaCha20-Poly1305 requests reaching the Secure Element and 28 handled there,
with no software fallback.
The kit has no battery-backed real time clock, so app.c supplies a
_gettimeofday() seeded from the build date, overriding newlib's weak stub.
Without it time() returns -1, every certificate reads as not yet valid and
the CA fails to load with ASN_BEFORE_DATE_E (-150). That clock is good enough
for a self-contained demo and nothing more: a device that cannot tell the time
cannot tell a valid certificate from an expired one, so a real design needs an
RTC, a time server, or a provisioned time that an attacker cannot move.
Notes
- The SE firmware must be recent enough for the SE Manager commands used. xG25 is Series 2 Config 5, so it takes the
s2c5firmware package - not thes2c1package referenced by older wolfSSL SiLabs documentation. Check withcommander device infoand upgrade withcommander flashif needed. user_settings.his shared by the library and the application, as wolfSSL requires. Changing feature macros in one and not the other corrupts struct layouts at runtime.- RSA has no hardware support on Series 2 and stays in software; the benchmark rows for it are software timings.