Every time you open a website with the little padlock at the top, your computer and the server do a quick round of math before the page shows up. They agree on a secret key that nobody listening in can work out, and the server proves it's really who it says it is. That's the TLS handshake. It's the "S" in HTTPS.
On the desktop I'm writing this on, the math for one handshake takes about a tenth of a millisecond. You never notice it, because you can't. Almost all of the time you spend waiting on a page is the network, not the math.
Now take that same handshake and run it on a thumbnail-sized microcontroller clocked at 39 MHz. The math alone takes two and a half seconds. If your browser made you wait an extra two seconds on every page, you'd switch browsers... but that's basically the deal every low-cost connected device gets by default: the same security, the same algorithms, and anywhere from 600 to 20,000 times longer to run them. A thermostat, a sensor, a smart plug: they all have to open a secure connection to the cloud, and they often do it on a chip like that.
I've helped companies launch a lot of connected products, and this is one of the things people commonly don't understand: the math needed to securely connect and send data to the cloud takes real time to process. That means when you want a low-cost device to get online quickly, part selection is one of the big factors. It matters even more for battery-powered devices, which want to connect quickly, send some data, and disconnect to save power... but if the processor takes several seconds just to connect, that isn't possible.
So I wanted to know something simple: if one of these microcontrollers had to open a TLS connection, how long would it spend on the math? Not the Wi-Fi, not the sockets... just the crypto.
I timed the primitives that TLS 1.2 and 1.3 actually use on the same 16-board fleet from the arithmetic article: SHA-256, HMAC and HKDF, AES-128-GCM, ChaCha20-Poly1305, X25519, P-256 key exchange and ECDSA verification, and RSA-2048.
If those names don't mean anything to you, here's the plain version. These aren't special embedded algorithms. They're the same ones your browser and your phone use to talk to nearly every HTTPS site today, so the only thing that changes here is the chip doing the math. A handshake has two expensive jobs: agreeing on a secret key out in the open (X25519 and P-256 are two ways to do that), and checking the server's ID card, which means verifying a digital signature (ECDSA and RSA are the two common kinds). Once that's done, every byte that goes over the wire gets scrambled (AES-GCM or ChaCha20-Poly1305) and fingerprinted (SHA-256, and the HMAC and HKDF built on it) so nobody can read it or quietly change it. The handshake is the slow, once-per-connection part. The scrambling is the fast, every-byte part. I've split the article the same way.
The short version: the public-key part of one handshake costs about 70 milliseconds on the 600 MHz Cortex-M7, half a second to a second and a quarter on the rest of the 32-bit parts, and two and a half seconds on the 39 MHz XIAO MG24. Five of the sixteen boards couldn't run the library at all. And one board's hardware crypto engine ran every test and failed every single known-answer check... until an agent tracked the problem down to two bugs in the driver, and then it turned out to be the fastest hash in the fleet.
What I measured
This is a smaller experiment than the arithmetic or FIR ones, so I'll keep the method short.
Every board runs the same C code: a constant-time subset of BearSSL (73 files). That's the "software" number, and it's the fair comparison because it's byte-for-byte the same code on every core. Where a board has a reachable hardware accelerator or a vendor crypto library, I also timed that as a "hardware" number and labeled exactly what's accelerated, because "hardware SHA-256" often still means software HMAC wrapped around it.
Each measurement is the median of three samples, with a no-op baseline loop subtracted so the harness overhead doesn't count. Bulk operations (hash, AEAD) run at 64 bytes up through 16 KB. Every result has to pass a known-answer test on the device first, or it doesn't get a number. Firmware bounds any one operation at 20 seconds.
The whole capture ran unattended on the rig overnight: 137 firmware images, 1,097 ledger rows, and every non-result carries a reason. Honestly, the reasons are half the story.
The five that couldn't play
The 8- and 16-bit parts never got a timing row. It's not that they were slow; the library didn't fit, didn't build, or didn't run right:
- Arduino Uno: every BearSSL image exceeded 32 KB of flash, 2 KB of SRAM, or both.
- MSP430FR2433: every image overflowed the 15 KB FRAM link region.
- PIC16F17146: BearSSL hit XC8 error 714, and the smaller symmetric-only fallbacks ran out of SRAM.
- PIC18F57Q84: the fallbacks compiled and ran, and every SHA-256, AES-GCM, and ChaCha20 identity failed its on-device known-answer check.
- PIC24FJ64GU205: the hash image never produced a clean capture, and every ECC and RSA image blew through a 185-second response deadline after programming.
None of that is really a surprise. The useful thing is that it's now written down with the exact failure for each part, instead of "you probably can't." If you need TLS on one of those, you're looking at an external module (a Wi-Fi co-processor that terminates TLS for you, or a secure element, basically a second chip whose whole job is the security math), or a different protocol entirely.
The handshake math
The expensive part of a TLS handshake is the public-key work. For a typical TLS 1.3 connection with an ECDSA certificate, that's a P-256 key generation, one ECDH, and an ECDSA verification (plus one more verify for each certificate in the chain). Here's the verify on its own:
Now the back-of-envelope sum. I want to be careful here: the experiment deliberately doesn't model handshakes, and this is just me adding three primitives together. A real handshake also hashes the transcript, runs HKDF, verifies a chain of two or three certificates, and does all the networking. So treat this as a floor.
| Board | P-256 keygen + ECDH + ECDSA verify | X25519 + RSA-2048 verify |
|---|---|---|
| Teensy 4.0 (Cortex-M7, 600 MHz) | 0.07 s | 0.01 s |
| Nucleo G474RE (Cortex-M4F, 170 MHz) | 0.58 s | 0.10 s |
| ESP32-S3 (240 MHz) | 0.61 s | 0.14 s |
| Pi Pico 2W (Cortex-M33, 150 MHz) | 0.62 s | 0.13 s |
| dsPIC33AK512MPS506 (200 MHz) | 0.68 s | 0.15 s |
| ESP32-C6 (RISC-V, 160 MHz) | 0.70 s | 0.16 s |
| ESP32 (240 MHz) | 0.71 s | 0.16 s |
| Pi Pico 2W (RISC-V, 150 MHz) | 0.75 s | 0.16 s |
| Feather M4 (Cortex-M4F, 120 MHz) | 1.07 s | 0.22 s |
| Pi Pico (Cortex-M0+, 200 MHz) | 1.28 s | 0.29 s |
| XIAO MG24 (Cortex-M33, 39 MHz) | 2.54 s | 0.48 s |
For scale, the desktop from the top of the article does the left-hand column in about 0.0001 seconds. That's OpenSSL's hand-tuned assembly against BearSSL's portable C, so it isn't a fair fight, but it is the comparison you actually feel: the Teensy is roughly 600x slower than a PC at this, and the MG24 is about 20,000x slower.
Two things jump out at me.
First, that's a 35x spread between the Teensy and the MG24, and the MG24 is a perfectly modern Cortex-M33. Most of that is the 39 MHz Arduino-core clock. Per cycle, the MG24 is one of the better boards in the fleet at this (about 50 million cycles per ECDSA verify, same as the Nucleo and the RP2350). The core matters too, though: the 200 MHz Cortex-M0+ in the Pi Pico needs 2.5x the cycles of those parts and lands behind the 150 MHz Cortex-M33 sitting next to it. Clock rate plus core class is the whole story on the pure software path.
Second, the RSA column. RSA-2048 verification is cheap everywhere (the public exponent is tiny), so an RSA certificate chain verifies 5 to 9x faster than an ECDSA one on these parts. RSA signing is the opposite: 85 ms on the Teensy, 0.6 to 2.1 s on the rest of the 32-bit pack, 2.8 s on the MG24. If your device only ever verifies the server's certificate, RSA certs are actually the fast choice on a microcontroller... which is not what you'd guess from the desktop world.
Bulk data: software AES is the slow one
The record layer is a different question. Once the handshake is done, every byte gets hashed and encrypted, and this is where the "constant-time" part of the software path costs you:
In software, AES-128-GCM lands between 0.35 and 0.6 MB/s on every 32-bit part that ran it, except the Teensy (4 MB/s). ChaCha20-Poly1305, on the same boards with the same library, is 3 to 4x faster: 1.25 to 2.5 MB/s, and 12 MB/s on the Teensy. That ratio is remarkably consistent across ARM, RISC-V, Xtensa, and the dsPIC, so I'd take it as a property of the algorithms rather than any one core. If you're negotiating cipher suites from a microcontroller without an AES accelerator, ChaCha20-Poly1305 is the one you want.
SHA-256 in software runs 50 to 110 cycles per byte on the 32-bit cores, about 22 on the Cortex-M7. At 16 KB that's 1.8 to 3.5 MB/s for most of the pack, 0.7 MB/s on the MG24, and 27 MB/s on the Teensy.
Where the hardware helped, and where it didn't
Eight boards had some kind of accelerator or vendor library the agents could reach. The results are all over the place, which I think is the real finding.
The big wins were real. The dsPIC33A's Crypto Accelerator Module did AES-128-GCM at 55 MB/s, 94x its own software path, and an ECDSA verify in 9.9 ms, 34x faster. The ESP32-S3's SHA engine hit 86 MB/s on 16 KB blocks, and the ESP32-C6's ECC accelerator brought ECDSA verify down to 41 ms. On the two Xtensa ESP parts, the hardware multiplier makes RSA-2048 verify about a millisecond, 45 to 60x faster than software.
But hardware isn't free, and it isn't always complete. On the classic ESP32, hardware SHA-256 on a 64-byte message was slower than software (47 µs vs 38 µs) because of a fixed setup cost per call, and a TLS record layer is full of small messages. On the SAMD51, both RP2350 builds, and the dsPIC, the hash is accelerated but the HMAC and HKDF framing around it is still C. Those are details, but they're the kind that show up when you move from a benchmark to a real connection.
And then there's the Teensy. The i.MX RT1062 has a Data Co-Processor (DCP) with SHA-256 and AES. The agents wrote a driver for it, it built, the disassembly proved the DCP path was really in the image, the board ran every capture without hanging... and every single cell failed its known-answer test. Hash, HMAC, HKDF, AES key schedule, GCM, all of it. So in the first capture, the Teensy's hardware number was "none."
That turned out to be the driver, not the silicon. An agent went back with a register-level diagnostic on the rig and found two bugs, either of which was enough to break every answer: the driver was waiting on a status bit that never gets set, and it treated the engine's "complete" status as an error. Fix those two lines of logic, rebuild, and all 22 known-answer tests pass.
With a working driver, the DCP hashes 16 KB at 104 MB/s. That's about 4x the Teensy's own software path, and the fastest SHA-256 in the fleet, ahead of the ESP32-S3's 86 MB/s. AES-128-GCM only gets 2.2x (8.6 MB/s), because the DCP does AES but not the GCM authentication math, so that half stays in software.
I think that's the most useful story in the experiment. A benchmark that just reported the timing would have shown you a very fast, very wrong hash. The known-answer gate is the reason it didn't, and it's also the reason we went looking for the bug instead of shipping the number.
A few other paths never produced a number, and the figure tooltips carry the reason for each: the MG24's Secure Element probe hit the 120-second deadline before it even said READY, and its AEAD images never answered at all; the SAMD51's hardware AES-GCM timed out in its bounded wait (only the key schedule got through, at 16 nanoseconds); the ESP32-S3's software hash images never synchronized over serial; and Espressif's bundled mbedTLS turned out to be built without ChaCha20-Poly1305 at all.
What I'd take from this
- Budget the handshake in seconds, not milliseconds. Unless you have a Cortex-M7-class part or an ECC accelerator, it's half a second to two and a half seconds of pure math per connection, and five of the sixteen boards can't run it at all. That changes how you design reconnect logic and sleep cycles, and below 32-bit it means offloading TLS to another chip.
- Without an accelerator, pick the cheap algorithms, and they're the opposite of desktop instinct. RSA certificates if your device only verifies (5 to 9x faster than ECDSA; 17 to 19x slower if it has to sign). ChaCha20-Poly1305 over AES-GCM for bulk data (3 to 4x the throughput on every core I tested).
- Don't trust an accelerator until it passes a known-answer test on your board, with your driver. The Teensy's first driver was fast and wrong; the fixed one is the fastest hash in the fleet.
The full table for any primitive is in the figure above; switch the dropdown. The methodology page covers the rig, and the numbers here are from the same overnight capture, so if you'd like a specific primitive on a specific board, ask and I'll dig it out.
All product names and trademarks are the property of their respective owners.