This repository demonstrates a prototype for protecting sensitive data in embedded systems using a combination of Physical Unclonable Functions (PUFs) and encryption.
It is designed for educational and research purposes and is tested on the NUCLEO-F767ZI development board.
For details on this prototype, see our paper Software-Hardware
Binding for Protection of Sensitive Data in Embedded Software.
Disclaimer: This repository contains cryptographic operations and sensitive data (e.g., keys and PUF responses). Ensure that you replace the provided keys and PUF responses with your own secure values before deploying this project in production. Do not publish sensitive files like
Key.binorPUF.hin public repositories.
-
Second Layer: Boolean Expressions. On the second layer, the constants are "encoded" as Boolean expressions. Using a PUF response these Boolean expressions get evaluated and return a constant set. With the correct PUF response they return the correct constant set, otherwise they return other constants that are degraded but still allow safe operation.
-
First Layer: Encryption. On the first layer, the Boolean expressions are encrypted. If the decryption fails, there are fallback Boolean expressions which will return degraded but still safe-to-operate constants, but do not include the correct constants for optimal performance.
Follow these steps to build and flash the project:
- Install Visual Studio Code with the PlatformIO IDE extension.
- If using WSL, pass the MCU to WSL using USBIPD.
- Install required Python packages:
pip3 install -r scripts/requirements.txt
The key/PUF response data is automatically generated by the tool described in Manuel Penz et al. "SRAM PUFs for Device Authentication on Resource-Constrained Systems". In: CSP’25.
- The file
PUF.hneeds to be replaced in include. Note that#define MaskLength 0x12defines the "PUF size". - The file
Key.binincludes the key. On Linux one can dump it usingxxd:
user@linux:~/PlatformIO/pid-protection-prototype$ xxd Key.bin
00000000: 4e39 f18e d84d e564 8203 e541 9514 13d1 N9...M.d...A....
00000010: d3caThe first 16 bytes 4e39f18ed84de5648203e541951413d1 are used as key in encrypt_data.py.
The last 2 bytes d3ca are used as PUF response in create_bools.py. Note that because of endianness it needs to be reversed: cad3 -> 1100101011010011.
1.2 create_bools.py
- Add the correct constant set to the
pid_configurationslist. It should be the first entry with the "correct one" comment beside.
pid_configurations = [
(800.10, 1000.5, 30.05), # correct one
(100.15, 1000.002, 5.1),
]- The other values in this list are the alternative values if the decryption succeeds but the PUF response is wrong. Add them.
- Add alternative values to the
pid_configuration2list. These are the values used if the decryption fails.
pid_configurations2 = [
(400.20, 800.10, 30.04),
(100.1, 1000.03, 5.5),
]- Add the PUF response value to
puf, e.g.:
puf = (1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1)[-arbitrary_partition_bits:]1.3 encrypt_data.py
- Add the key for en-/decryption to
key, e.g.:
key = bytes.fromhex('4e39f18ed84de5648203e541951413d1')cd scripts
python3 ./create_bools.py --number_of_constants <number> --arbitrary_partition_bits <bits>- number_of_constants = how many constant sets to include (2-16; there are predefined sets in the script)
- arbitrary_partition_bits = how many bits to use for the arbitrary partition (2-30; this correlates with the used PUF size)
- ensure that
$number \le 2^{bits}$
This will produce two files surjective_function.txt containing the correct Boolean expressions and arbitrary_surjective_function.txt containing the Boolean expressions without the correct constants.
python3 ./encrypt_data.py --puf_size <bits>- puf_size = PUF size (2-30; use same value as for arbitrary_partition_bits above)
This will create the header encrypted_data.h inside the ./include directory.
Get output on Linux with
screen -L /dev/ttyACM0 115200- -L is for writing the output to a log file in the current directory, usually screenlog.X