Why I Built This?
I've always been curious about how Cryptography works on real hardware, not just in code, but on actual devices that Store Keys and do Encryption securely. I’d seen examples of Software-based cryptography, but I wanted to build something more hands-on, a device that does cryptographic stuff on its own, without relying on a PC for any of it.
That’s where TrustX started. I wanted to build my own simple Hardware Security Module using just a microcontroller, an STM32H5 in my case, and see how far I could go. The goal wasn’t to build a commercial or certified HSM, but something I could learn from, something that handles Keys securely, does Cryptography operations, and responds to Tamper events, all in hardware.
What does this Device actually do?
TrustX isn’t a full-scale enterprise HSM; it’s more like a secure, USB-connected crypto helper. The host PC sends commands, and the device takes care of the actual processing. It can:
- Encrypt and Decrypt data using AES-128 in CTR mode
- Generate Random Numbers using the Hardware RNG
- Hash data with SHA-224, 256, 384, or 512
- Run HMAC using SHA-224 or SHA-256
- Generate One-Time Pads
- Store and Manage Keys securely in Flash
- Erase everything if tampering is detected
One of the key things I focused on was making sure all stored keys are locked to the specific MCU. So even if someone clones the external Flash, the data won't work on another board. Since this chip doesn’t give access to a built-in Hardware Unique Key (HUK) for general-purpose use, I had to improvise, combining a constant Key Material with a Unique Salt to derive a Master Key that’s kept only in RAM and never stored.
Users can interact with the device using the TrustX GUI on the host PC, which talks to the HSM over a USB CDC connection.
Under the Hood
The Microcontroller That Runs Everything
This microcontroller is the central component of the device. It runs the firmware, manages USB communication with the host, performs all cryptographic operations, and handles key storage. I used the STM32H563ZI for development since it was readily available as a Nucleo-144 Development board.

Even though the STM32H563ZI uses a Cortex-M33 core, this specific variant doesn’t include the hardware crypto accelerators available in other STM32H5 models, like the AES Hardware Accelerator, the Secure AES (SAES) Coprocessor, or the On-The-Fly Decryption Engine (OTFDEC). Because of that, I handled Encryption and Decryption in Software using the
Intel TinyCrypt library. For the final PCB design, I plan to switch to the
STM32H573RIV6, which comes in a compact 68-pin VFQFPN package and includes full hardware cryptography support, including RSA, ECC, ECDSA, and a built-in Hardware Unique Key (HUK). It also supports Secure Boot and Root of Trust (RoT), which makes it more suited for secure applications.
Non-Volatile Memory for Key Storage
The external Flash, a
Winbond W25Q64FV SPI Flash Memory, stores all encryption keys, but never in plaintext. Every key is wrapped with a master key derived at boot from the MCU’s Unique Device ID and a One‑Time Random Salt, so an external dump reveals only the Ciphertext bound to this specific chip. Each Key entry is about 88 bytes. I reserved an entire Flash sector for each blob, eliminating overflow issues and giving room for roughly 2,000 keys, far more than the project is ever likely to need.
Serial Communication Over USB
This is the sole communication link between the host and the device. The host PC sends commands over a USB CDC (serial) connection, and TrustX Firmware replies with results like Ciphertext, Hash Digests, Random Numbers, or Status Updates. I used STMicroelectronics' USB Device Middleware, which works across the STM32 series, and customized the CDC Transmit and Receive functions to fit the command-handling logic and to ensure buffer stability when dealing with larger data transfers. Host Application for Running Operations
This is the host-side application, a simple GUI built with Python and Tkinter. It allows the user to choose an operation, enter the required input, and view or download the result. Communication with the TrustX device happens over USB serial, using structured commands and responses.
The TrustX Console is programmed to automatically identify and connect to the correct USB device by matching its Vendor ID (VID) and Product ID (PID) from the available ports. Once connected, the user can provide the necessary data, start the operation, and wait for the result to be displayed. The console also keeps an internal database of Initialization Vectors (IVs) used during encryption. When decrypting, the user can provide the ciphertext and key ID; the console calculates the hash of the ciphertext and searches for a match in its database. It verifies both the ciphertext hash and key ID to ensure the correct IV is retrieved for decryption.
How Data Moves Through the System
When the user selects an operation in the TrustX Console, like Encryption, and provides the required inputs, such as Plaintext, a Key ID, and optionally an Initialization Vector (IV), the console formats this information into a Command Packet and sends it to the TrustX device over USB. The USB communication is interrupt-driven, with incoming data stored in a buffer for processing. Once the command is received, the firmware parses it and routes it to the appropriate handler, Encryption, Decryption, Hash, HMAC, RNG, or Key Management, based on the operation code.
If a Key is needed but not provided directly, the firmware loads the Encrypted Key from external Flash (using a Key ID) and decrypts it in RAM using a Master Key that’s derived on the fly using the STM32’s Unique Hardware ID and a Stored Salt. Once the requested operation is completed, the result, such as Ciphertext or a Hash digest, is sent back to the TrustX Console over USB, where the user can View, Copy, or Download it.
At no point is key material exposed to the host PC. All keys remain encrypted in Flash, and the master key is never stored, it’s derived during runtime and exists only in RAM. This design separates the untrusted host from the trusted crypto engine and, while not tamper-proof or certified, moves closer to how real secure hardware is structured.
Each type of operation uses a different packet format, which the firmware identifies and parses accordingly. Packets begin with a 4-byte Transaction ID (TXID), which is stored on the device and echoed in the response to help match commands with results. Both outgoing and incoming packets also include an End-of-Data marker, a fixed 4-byte value that signals the end of transmission. All Packet formats and structures are documented in the project repository.
Deriving, Saving, and Protecting Keys
The real challenge was figuring out how to derive the Master Key without ever storing it. Hardcoding it into the firmware was out of the question; it could be easily extracted. I needed a way to generate the key at runtime, inside the device itself, in a way that binds the external Flash to this specific microcontroller. That meant even if someone cloned the Flash, it wouldn’t work with another device, not even one with the same model. This approach opened up a path to store keys in Flash securely, in a well-defined format. The idea of TrustX was always about securely deriving and managing keys, not with the goal of perfect security, but with the goal of: "How far can I push this, using only what I’ve got?"
Secure Key Storage in Flash Memory
TrustX stores its encryption keys in external SPI Flash, but never as raw plaintext. Instead, each key entry is a mix of cleartext and encrypted data, following a specific structure. The Key ID and Usage Count are kept in cleartext so the device can efficiently search for and locate a key in Flash. However, the more sensitive parts, such as the Origin of the key (whether it was generated or provided), its intended Use (Encryption or Decryption or HMAC), the Key Size, and the Key Value itself, are all encrypted. An HMAC-SHA256 digest is computed over the encrypted portion to ensure its integrity and is stored alongside the data. The final part of the entry is the Initialization Vector (IV) used for encryption, which takes up 16 bytes. This layout allows to secure management and validation of keys, even while using external memory.
Master Key Derivation and Usage
Since the STM32H5 series doesn’t offer a Hardware Unique Key (HUK) for general-purpose use, I had to take a different approach. I combined the MCU’s 96-bit unique ID with a 20-byte random salt, which is generated during the first boot and stored in internal Flash. This Flash sector is protected using RDP Level 2 to prevent external reads. Whenever the system needs to derive the Master Key, it retrieves the Salt from Internal Flash and the UID from the system registers, combines them as a 3-word input, and feeds them into a custom Key Derivator that outputs a fixed 16-byte key. This Master Key is used to encrypt or decrypt key blobs stored in external Flash, but it’s never stored permanently; it exists only in RAM during runtime. This way, key storage remains tightly bound to that specific MCU instance.
The only way the user interacts with the Key Management System (KMS) is through the Key Store and Key Delete operations, which send specific commands from the host to the device. These commands directly invoke the corresponding KMS functions on the firmware. Once stored, keys remain securely on the device for their entire lifetime unless the user explicitly decides to delete them, making it possible to manage keys based on their relevance or expiration.
What’s Missing (for Now)
One major feature I’d like to add next is support for Asymmetric Cryptography, such as RSA and ECDSA. The current STM32H563ZI microcontroller doesn’t offer full hardware support for these algorithms; it only allows ECDSA signature verification through a limited-mode PKA (Public Key Accelerator) peripheral. To work around this, I’m thinking of using a lightweight software library like BearSSL, which provides an easy implementation of RSA, ECC, and other cryptographic primitives.
In addition to Asymmetric operations, I’m also considering using BearSSL to secure the USB communication channel with TLS. Currently, TrustX transfers data in plaintext over USB-CDC, which is fine for basic testing, but encrypting the entire session using TLS would improve confidentiality and protect against sniffing.