Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 27 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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


Expand All @@ -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:
Expand All @@ -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:
Expand Down
78 changes: 56 additions & 22 deletions keytool.py
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -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 <key‑hex> -c
python3 keytool.py -c encode <key‑hex>

Decode the two 64‑byte blocks back into the key:

python xor_split.py decode <block0‑hex> <block1‑hex>
python xor_split.py decode <block0‑hex> <block1‑hex> --c-array
python3 keytool.py decode <block0‑hex> <block1‑hex>
python3 keytool.py --c-array decode <block0‑hex> <block1‑hex>
python3 keytool.py decode "<block0‑c-style-hex>" "<block1‑c-style-hex>"
"""

import os
import sys
import argparse
import re
from functools import reduce
from typing import List, Tuple

Expand All @@ -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]:
Expand All @@ -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).")
Expand All @@ -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).")
Expand Down Expand Up @@ -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")
Expand All @@ -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)
Expand Down