Factory-reset a CanoKey with a Python script

Unlike most of my technology articles, which I write myself and then refine with an LLM, this article was largely written by an LLM. I used an LLM to find a solution, checked that it worked, and then organised the LLM’s response into this article.

The procedure was tested on my CanoKey Canary with firmware 3.0.3-0-g30917b92.

1. Prepare the required tools

On Debian or Ubuntu:

$ sudo apt install python3-usb libusb-1.0-0

Then use this script to send commands:

#!/usr/bin/env python3

import time
import usb.core
import usb.util

VID = 0x20A0
PID = 0x42D4
INTERFACE = 1


def apdu(device: usb.core.Device, command: str) -> bytes:
    data = bytes.fromhex(command)

    # Send command APDU.
    device.ctrl_transfer(
        0x41,       # Host-to-device | Vendor | Interface
        0x00,       # CMD
        0x0000,
        INTERFACE,
        data,
    )

    # Wait for execution to finish.
    while True:
        status = device.ctrl_transfer(
            0xC1,       # Device-to-host | Vendor | Interface
            0x02,       # STAT
            0x0000,
            INTERFACE,
            1,
        )

        if bytes(status)[0] == 0x00:
            break

        time.sleep(0.05)

    # Retrieve response APDU.
    response = device.ctrl_transfer(
        0xC1,
        0x01,       # RESP
        0x0000,
        INTERFACE,
        1500,
    )

    return bytes(response)


def main() -> None:
    device = usb.core.find(idVendor=VID, idProduct=PID)

    if device is None:
        raise SystemExit("CanoKey 20A0:42D4 not found")

    print(f"Found {VID:04X}:{PID:04X}")

    while True:
        command = input("> ")
        if command == "":
            break
        response = apdu(device, command)
        print(f"< {response.hex(' ').upper()}")


if __name__ == "__main__":
    main()

2. Select the Admin applet

The Admin applet’s application identifier (AID) is F000000000. Send:

00 A4 04 00 05 F0 00 00 00 00

Expected response:

90 00

This means the Admin applet was selected successfully.

3. Exhaust the Admin PIN retry counter

This step is required by the documented factory-reset procedure when you cannot authenticate for a normal reset.

First, check the counter with:

00 20 00 00

The initial response should be:

63 C3

This indicates that three retries remain.

Send the following APDU with a deliberately incorrect PIN three times:

00 20 00 00 06 30 30 30 30 30 30

The PIN bytes represent 000000. The expected responses are:

  1. 63 C2 (Two retries remain)
  2. 63 C1 (One retry remains)
  3. 69 83 (PIN is blocked)

Important: Do not exhaust the retries if you still need to use the existing PIN normally. In this procedure, deliberately blocking the PIN enables the locked-out factory-reset route.

4. Send the factory-reset command

Use the WebUSB script again and select the Admin applet before sending the reset APDU:

00 A4 04 00 05 F0 00 00 00 00
00 50 00 00 05 52 45 53 45 54

The second APDU is the factory-reset command:

00 50 00 00 05 RESET

Here, RESET is the five-byte ASCII string 52 45 53 45 54. CanoKey specifies this instruction as INS=50; the Admin PIN must already be blocked.

5. Follow the LED prompts

When you send the command, the CanoKey begins blinking.

  1. Touch the key promptly when it blinks.
  2. Continue touching it in response to the successive fast and slow blinking sequences.
  3. Keep the key connected and allow your Python script to continue polling.
  4. Wait until the script returns the final response:
90 00

The final 90 00 confirms that the operation succeeded. Your device returns it after the repeated touch sequence.

The complete sequence at a glance

Select the Admin applet

00 A4 04 00 05 F0 00 00 00 00

Block the Admin PIN

Send 00 20 00 00 06 30 30 30 30 30 30 three times.

Start the factory reset

00 50 00 00 05 52 45 53 45 54

Touch until the reset is complete

Respond to every requested blink until the result is 90 00.

Built with Hugo
Theme Stack designed by Jimmy