diff --git a/README.md b/README.md index b4001f7..ffd79bb 100644 --- a/README.md +++ b/README.md @@ -25,13 +25,13 @@ We have employed various *devious* individuals to add forms of “hardening” t We challenge you to prove us wrong! (again) -Unlike the first challenge, we're running this one as a "first across the line" competition, with a single winner (or winning team). -- It'll run for 3 months (We may choose to extend this if required) -- The prize will be $20,000 USD +Unlike the first challenge, we're running this one as a "first across the line" competition, with a single winner (or winning team). +- It'll run for 3 months (We may choose to extend this if required) +- The prize will be $20,000 USD ## Things in scope: - Any and all forms of side-channel analysis -- Any version of RP2350 or RP2354 (Die version A2/A3 or A4) +- Any version of RP2350 or RP2354 (Die version A2/A3 or A4) (Feel free to email us at [doh@raspberrypi.com](mailto:doh@raspberrypi.com) with ideas or questions on what could be in scope) ## Out of scope: @@ -47,16 +47,15 @@ secure boot enabled) RP2350 device. ## How to report findings If you think you're onto something that looks promising, we'd like to hear from you early. - -We'll be looking for you to be able to articulate your findings and any hypotheses you may have, but don't feel like you have to write a dissertation before you get in touch! +We'll be looking for you to be able to articulate your findings and any hypotheses you may have, but don't feel like you have to write a dissertation before you get in touch! Please email us via [doh@raspberrypi.com](mailto:doh@raspberrypi.com) ## Example code In this repository you can find a full, already instrumentalized example of our AES implementation: -- The provided aes.S has been modified slightly from the version used by the bootloader [Picotool](https://github.com/raspberrypi/picotool/blob/develop/enc_bootloader/aes.S), to make it easier to set up and run from regular flash -- rp2350_hacking_challenge_2.c contains a small example program that calls into this AES implementation and already provides a trigger for your initial SCA needs +- The provided `aes.S` has been modified slightly from the version used by the bootloader in [Picotool](https://github.com/raspberrypi/picotool/blob/develop/enc_bootloader/aes.S), to make it easier to set up and run from regular flash +- `rp2350_hacking_challenge_2.c` contains a small example program that calls into this AES implementation and already provides a trigger for your initial SCA needs - If you want to trigger somewhere within the assembly, you can simply add "bl trigger" into the assembly @@ -70,19 +69,33 @@ The AES implementation splits the AES key into a 4-way split. To make this easie Using `keytool.py` you can split an existing AES key into a 4-way key: ``` -$ python3 ./keytool.py encode 0000000000000000000000000000000000000000000000000000000000000000 +$ python3 ./keytool.py encode 0000000000000000000000000000000000000000000000000000000000000000 66b3ca75e02ad9c8abb06c0b2d297fb660ed5c58c9029ec883f9dbcd2a16195d5e75fadfd32acb297ca03930f1ff08c6714d3f79eb3a26cdc9ef28f553983141 8ae55043c5a225f84ee3b58e01a4c035cae62eba5e622490ce27736a5aa3794053fe7285c513072124ac2f7bb2415adf1c4bf2d87c7b9c6d3643e43356738a86 ``` +Note that the splits are randomly generated, so you'll get a different 4-way key each time you run the command. However, each different 4-way key still decodes back to the original key: + +``` +$ python3 ./keytool.py decode 66b3ca75e02ad9c8abb06c0b2d297fb660ed5c58c9029ec883f9dbcd2a16195d5e75fadfd32acb297ca03930f1ff08c6714d3f79eb3a26cdc9ef28f553983141 8ae55043c5a225f84ee3b58e01a4c035cae62eba5e622490ce27736a5aa3794053fe7285c513072124ac2f7bb2415adf1c4bf2d87c7b9c6d3643e43356738a86 +0000000000000000000000000000000000000000000000000000000000000000 +``` + If you need a C-array, you can add the "-c" option: ``` -$ python3 ./keytool.py -c encode 0000000000000000000000000000000000000000000000000000000000000000 +$ python3 ./keytool.py -c encode 0000000000000000000000000000000000000000000000000000000000000000 \x5a\xad\x34\x6b\x42\x3e\x8d\xff\xe6\xa7\x78\xeb\xfe\x34\xc1\x7f\xb6\x86\x2f\xb5\xd7\xd7\x7f\x11\xdd\x7f\x84\x8c\xbc\x2e\xd4\x28\x04\x20\xfb\x00\x22\xb2\x8d\xb9\xcb\x6f\xd5\x55\xed\xfd\xa3\xec\x14\x85\x20\x2e\x60\x7a\x59\x46\xca\x91\x3e\x77\xbe\x6e\x47\x1f \xa8\xc2\x2b\x7d\xc6\x6e\xaa\x4d\xc9\x97\x47\xf0\xa7\x3b\xc6\xc0\x4b\x2e\x5b\xe7\xcd\x4b\x8d\x1f\x36\x0a\x4e\x14\xb0\x6f\x98\xec\xef\xce\x3e\x4b\xed\xfe\x66\x60\x93\xb5\xf2\x67\x91\x85\xaa\x4c\xcd\x24\x0c\x46\xe5\xb0\x6d\x4d\x13\x69\x11\xad\x3b\xfd\x70\xa6 ``` +This too can also be decoded back to the original key, as long as you remember to put quotes around the C-array strings: + +``` +$ python3 ./keytool.py decode "\x5a\xad\x34\x6b\x42\x3e\x8d\xff\xe6\xa7\x78\xeb\xfe\x34\xc1\x7f\xb6\x86\x2f\xb5\xd7\xd7\x7f\x11\xdd\x7f\x84\x8c\xbc\x2e\xd4\x28\x04\x20\xfb\x00\x22\xb2\x8d\xb9\xcb\x6f\xd5\x55\xed\xfd\xa3\xec\x14\x85\x20\x2e\x60\x7a\x59\x46\xca\x91\x3e\x77\xbe\x6e\x47\x1f" "\xa8\xc2\x2b\x7d\xc6\x6e\xaa\x4d\xc9\x97\x47\xf0\xa7\x3b\xc6\xc0\x4b\x2e\x5b\xe7\xcd\x4b\x8d\x1f\x36\x0a\x4e\x14\xb0\x6f\x98\xec\xef\xce\x3e\x4b\xed\xfe\x66\x60\x93\xb5\xf2\x67\x91\x85\xaa\x4c\xcd\x24\x0c\x46\xe5\xb0\x6d\x4d\x13\x69\x11\xad\x3b\xfd\x70\xa6" +0000000000000000000000000000000000000000000000000000000000000000 +``` + ## Instructions To build the project, follow these instructions: @@ -93,15 +106,15 @@ cmake -DPICO_PLATFORM=rp2350 -DPICO_BOARD=pico2 .. make ``` -This will generate a rp2350_hacking_challenge_2.uf2, that you can then copy to your RP2350 board. +This will generate a `rp2350_hacking_challenge_2.uf2`, that you can then copy to your RP2350 board. Usage of the example program is easy, it supports two commands: -- `K` followed by 128 bytes of 4-way key-data (as created by keytool.py) sets the encryption key. The default-key is all 0. -- `E` executes an encryption and then prints out the result as hex. +- `K` followed by 128 bytes of 4-way key-data (as created by `keytool.py`) sets the encryption key. The default-key is all 0. +- `E` executes an encryption and then prints out the result as hex. ## Further reading -Please see [here](aes_report_monospace.md) for a detailed report into the AES hardening work. +Please see [here](aes_report_monospace.md) for a detailed report into the AES hardening work. ## Acknowledgements A big thank you to the following folks: diff --git a/keytool.py b/keytool.py old mode 100644 new mode 100755 index 4ca9238..f80a948 --- a/keytool.py +++ b/keytool.py @@ -12,22 +12,24 @@ --- Encode a 32‑byte key (64 hex chars) into the layout above: - python xor_split.py encode 0123456789abcdeffedcba9876543210\ + python3 keytool.py encode 0123456789abcdeffedcba9876543210\ 00112233445566778899aabbccddeeff Same, but emit as a C‑style escaped string (two lines = two 64‑byte blocks): - python xor_split.py encode -c + python3 keytool.py -c encode Decode the two 64‑byte blocks back into the key: - python xor_split.py decode - python xor_split.py decode --c-array + python3 keytool.py decode + python3 keytool.py --c-array decode + python3 keytool.py decode "" "" """ import os import sys import argparse +import re from functools import reduce from typing import List, Tuple @@ -54,6 +56,21 @@ def to_c_array(data: bytes) -> str: return "".join(f"\\x{b:02x}" for b in data) +def from_c_array(data: str) -> bytes: + """Convert an escaped C‑style hex string to raw bytes.""" + return bytes(int(re.match(r'^\\x([0-9a-f]{2})$', data[i:i+4]).group(1), 16) for i in range(0, len(data), 4)) + + +def to_hex_string(data: bytes) -> str: + """Convert raw bytes to a hexadecimal string.""" + return data.hex() + + +def from_hex_string(data: str) -> bytes: + """Convert a hexadecimal string to raw bytes.""" + return bytes.fromhex(data) + + # ---------- core ------------------------------------------------------------ def encode(key_hex: str) -> Tuple[bytes, bytes]: @@ -64,10 +81,16 @@ def encode(key_hex: str) -> Tuple[bytes, bytes]: --------------------------------------------------- a0 b0 c0 d0 ... a3 b3 c3 d3 a4 ... d7 """ - try: - key = bytes.fromhex(key_hex) - except ValueError: - raise ValueError("Key must be valid hexadecimal.") + if key_hex[:2] == r'\x': + try: + key = from_c_array(key_hex) + except AttributeError: + raise ValueError("Key must be valid C‑style hex string.") + else: + try: + key = from_hex_string(key_hex) + except ValueError: + raise ValueError("Key must be valid hexadecimal.") if len(key) != WORD * WORDS: raise ValueError("Key must be exactly 32 bytes (64 hex characters).") @@ -93,10 +116,16 @@ def decode(blocks_hex: List[str]) -> bytes: if len(blocks_hex) != 2: raise ValueError("Exactly two blocks (block0, block1) are required.") - try: - block0, block1 = (bytes.fromhex(h) for h in blocks_hex) - except ValueError: - raise ValueError("Blocks must be valid hexadecimal.") + if blocks_hex[0][:2] == r'\x': + try: + block0, block1 = (from_c_array(h) for h in blocks_hex) + except AttributeError: + raise ValueError("Blocks must be valid C‑style hex string.") + else: + try: + block0, block1 = (from_hex_string(h) for h in blocks_hex) + except ValueError: + raise ValueError("Blocks must be valid hexadecimal.") if len(block0) != BLOCK or len(block1) != BLOCK: raise ValueError("Each block must be exactly 64 bytes (128 hex characters).") @@ -128,7 +157,11 @@ def main() -> None: help="output in C‑style escaped hex (\\x00\\x11...)", ) - subparsers = parser.add_subparsers(dest="command", required=True) + try: + subparsers = parser.add_subparsers(dest="command", required=True) + except TypeError: + print(f"Please use Python 3.7 or newer", file=sys.stderr) + sys.exit(1) enc = subparsers.add_parser("encode", help="split key into shares") enc.add_argument("key_hex", metavar="KEY", help="64‑hex‑character key") @@ -140,20 +173,21 @@ def main() -> None: args = parser.parse_args() + if args.c_array: + # Escaped C‑style hex string(s) + output_function = to_c_array + else: + # Plain hexadecimal string(s) + output_function = to_hex_string + try: if args.command == "encode": block0, block1 = encode(args.key_hex) - if args.c_array: - # Print each 64‑byte block as a standalone escaped string - print(to_c_array(block0)) - print(to_c_array(block1)) - else: - # Raw lowercase hex, one line per block - print(block0.hex()) - print(block1.hex()) + print(output_function(block0)) + print(output_function(block1)) elif args.command == "decode": key = decode(args.blocks) - print(to_c_array(key) if args.c_array else key.hex()) + print(output_function(key)) except ValueError as exc: print(f"Error: {exc}", file=sys.stderr) sys.exit(1)