- cryptboot GD32
cryptboot GD32 is a secure bootloader designed for GigaDevice GD32 microcontrollers. It ensures that only authorized, encrypted, and signed firmware images are executed on the hardware.
- Firmware Encryption & Signing: Protects intellectual property and ensures firmware authenticity using the
firmware_image_builder.pytool. - Arduino Integration: Optimized for development within the Arduino IDE using the ArduinoCore-GD32 project.
- Vector Table Management: Automated handling of application entry points via specialized constructor functions.
The project is designed to work with the Arduino ecosystem.
- Arduino Core: Install ArduinoCore-GD32.
- Python 3: Required for the
firmware_image_builder.pyscript (requirescryptographylibrary). - OpenOCD: Used for flashing and memory management.
Because the bootloader resides at the start of the flash memory, the user application must be linked to a specific offset (typically 0x08005000 [20kB]).
Your application must relocate the Vector Table (VTOR) to its starting address in flash to ensure interrupts work correctly after the bootloader jumps to the application.
To achieve this, you must include the following function in your Arduino sketch. This uses the constructor attribute to ensure it executes before the main application logic starts.
A complete example of how to achieve this using a constructor function can be found in the Blink.ino file:
#if defined(FLASH_OFFSET) && FLASH_OFFSET != 0
__attribute__((constructor(102))) void vtor_init()
{
// Calculate the start address based on the flash base and defined offset
constexpr uint32_t THIS_APP_START_ADDRESS {FLASH_BASE + (uint32_t)(FLASH_OFFSET)};
SCB->VTOR = THIS_APP_START_ADDRESS;
__DSB();
}
#endifTo simplify the development process, it is highly recommended to replace the original boards.txt file in your Arduino Core installation with the custom boards.txt provided in this repository.
By using this version of boards.txt, you gain a new menu item in the Arduino IDE: "FLASH start offset". This allows you to select the correct application offset (e.g., 0x5000) directly from the "Tools" menu, ensuring the code is compiled with the correct FLASH_OFFSET definition.
If you prefer using the command line, you can specify the flash offset during compilation by passing the build property:
arduino-cli compile --fqbn community_gd32:gd32:gd_generic_gd32f30x:pnum=GD32F303CC_GENERIC \
--build-property "build.flash_offset=0x5000" \
--build-property "build.extra_flags=-DFLASH_OFFSET=0x5000" \
./Blink.inoSince cryptboot GD32 is a minimal secure bootloader that operates strictly on the flash memory, the responsibility for receiving and delivering new firmware images is best delegated to the user application.
This approach provides maximum flexibility:
- The application can use any communication interface available on the chip (UART, MODBUS, USB, CAN, LAN, etc.) to fetch the encrypted
.signed.binfile. - Once the new firmware is downloaded and stored in internal flash, the application simply triggers the bootloader update process.
- The OpenOCD method (shown in the Flashing section below) is typically used only for the initial deployment of the bootloader and the first application image. Subsequent updates are handled wirelessly or via the chosen industrial bus.
Use the following commands to manage the flash memory of your GD32 device. These examples utilize the openocd.exe binary and scripts provided in the project.
To back up the existing firmware (e.g., 256KB from bank 0):
- ST-Link:
.\bin\openocd.exe -s .\scripts -f interface/stlink.cfg -c "set CPUTAPID 0" -f target/stm32f1x.cfg -c "transport select hla_swd; init; reset halt; flash read_bank 0 firmware.bin 0 0x40000; reset; shutdown"
- CMSIS-DAP:
.\bin\openocd.exe -s .\scripts -f interface/cmsis-dap.cfg -c "set CPUTAPID 0" -f target/stm32f1x.cfg -c "transport select swd; init; reset halt; flash read_bank 0 firmware.bin 0 0x40000; reset; shutdown"
To perform a full or partial sector erase:
- Full Erase (ST-Link):
.\bin\openocd.exe -s .\scripts -f interface/stlink.cfg -c "set CPUTAPID 0" -f target/stm32f1x.cfg -c "transport select hla_swd; flash init; init; reset halt; flash erase_sector 0 0 last; reset; shutdown"
- Partial Erase (Sectors 9 to last via CMSIS-DAP):
Note: Sector 9 is reserved for internal bootloader metadata, including the firmware CRC32 checksum, AES256 encryption key, timestamp, and firmware size. The user application itself is stored and executed starting from Sector 10.
.\bin\openocd.exe -s .\scripts -f interface/cmsis-dap.cfg -c "set CPUTAPID 0" -f target/stm32f1x.cfg -c "transport select swd; flash init; init; reset halt; flash erase_sector 0 9 last; reset; shutdown"
To upload your processed binary to the specific application offset (e.g., 0x08020000):
- Example for Blink:
Note: The signed application must be located in the correct place in the flash memory (with the default bootloader settings, this is
.\bin\openocd.exe -s .\scripts -f interface/cmsis-dap.cfg -c "set CPUTAPID 0" -f target/stm32f1x.cfg -c "transport select swd; program Blink.ino.signed.bin 0x08020000; reset run; shutdown"
0x08020000). Then, if verification is successful, bootloader moves the application to the start address0x08005000.
Important Note on Debugger Interfaces:
In the commands listed above, depending on your hardware, you must adjust the interface and transport settings:
- For CMSIS-DAP: Use
-f interface/cmsis-dap.cfgandtransport select swd. - For ST-Link: Use
-f interface/stlink.cfgandtransport select hla_swd.
The firmware_image_builder.py script is used to prepare binary firmware images. It handles SHA256 hashing, GD32-compatible hardware CRC32 calculation, AES-256 encryption, and ECDSA P-256 signing.
Before signing any firmware, you must generate your security keys:
python firmware_image_builder.py --P256genThis generates bootloader_private_key.pem (private key) and bootloader_public_key.h (public key to be included in the bootloader source).
To sign a standard .bin file generated by the Arduino IDE:
python firmware_image_builder.py --file Blink.ino.binOutput: Blink.ino.signed.bin containing a 128-byte header with the digital signature.
If your bootloader configuration requires encrypted firmware, provide a 32-byte hex key:
python firmware_image_builder.py --file Blink.ino.bin --cipher AES256 --key 000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1FNote: The first firmware is encrypted with a default key consisting of a string of 32 0xFF numbers, and we provide the --newKey argument with a new AES256 key.
Copyright ยฉ 2025-2026 Michal Protasowicki
This software is released under the MIT License.
If You find my projects interesting and You wanted to support my work, You can give me a cup of coffee or a keg of beer :)