Skip to content

Authentication Handshake

The Encryption2 protocol uses a 3-phase handshake to establish an encrypted session between the app and the vehicle. This page documents the complete authentication flow.


State machine

stateDiagram-v2
    [*] --> INITIAL
    INITIAL --> PRE_COMM: Start connection
    PRE_COMM --> SET_PWD: Got auth + SN
    PRE_COMM --> AUTH: Has stored password
    SET_PWD --> AUTH: Password accepted
    SET_PWD --> SET_PWD: Waiting for button
    AUTH --> COMM: Authenticated
    AUTH --> SET_PWD: Auth failed (retry)
    COMM --> [*]
INITIAL ──► PRE_COMM ──► SET_PWD ──► AUTH ──► COMM
  │            │            │          │        │
  │            │            │          │        └── Normal operation (encrypted)
  │            │            │          └── Verify password + SN
  │            │            └── Exchange session password
  │            └── Get auth params + serial number
  └── Not started

Key evolution

The encryption keys change at each phase of the handshake:

Phase key1 key2 Counter mode
PRE_COMM BLE device name null (zeros) Non-SN (counter = 0)
SET_PWD BLE device name auth parameter SN (counter > 0)
AUTH session password auth parameter SN (counter > 0)
COMM session password auth parameter SN (counter > 0)

At each phase, the AES key is derived as SHA-1(key1_pad16 ‖ key2_pad16)[0:16].


Phase 1: PRE_COMM (cmd = 0x5B / 91)

Purpose: Retrieve authentication parameters and device serial number.

Setup

crypto.reset_sn()                                   # counter = 0 (non-SN mode)
crypto.set_key(bt_name.encode(), None)               # key1 = device name, key2 = null

Request

CMD = 0x5B, INDEX = 0x00, DATA = [] (empty)
Target: BLE board (0x04)

Frame: [5A B5 00 3E 04 5B 00] (before encryption)

Response

The device responds with 30+ bytes of payload:

Offset  Size  Field
  0      16   auth_param     Authentication challenge (random)
 16      14   serial_number  Device serial number (ASCII)

The INDEX byte in the response indicates whether the device has a stored password:

  • INDEX = 0 — No stored password, must do SET_PWD
  • INDEX != 0 — Has stored password, can skip to AUTH

Processing

auth_param = response.data[0:16]     # 16-byte random challenge
serial_number = response.data[16:30]  # 14-byte ASCII serial number

crypto.set_auth_param(auth_param)     # Store for nonce construction
crypto.start_sn()                     # Enable SN mode (counter = 1)

Phase 2: SET_PWD (cmd = 0x5C / 92)

Purpose: Set a session password on the device.

Setup

crypto.set_key(bt_name.encode(), auth_param)  # key1 = device name, key2 = auth

Password generation

The session password is generated from the auth parameter and current time:

import hashlib, time

def generate_password(auth_param: bytes, time_ms: int = None) -> bytes:
    if time_ms is None:
        time_ms = int(time.time() * 1000)

    # Derive seed from auth_param
    seed_value = 0
    for i, b in enumerate(auth_param):
        signed_byte = b if b < 128 else b - 256
        shift = (i % 8) * 8
        val = signed_byte << (shift & 31)  # Java int shift wraps at 32
        seed_value += val

    seed = time_ms + seed_value

    # Java LCG PRNG
    rng = JavaRandom(seed)
    random_bytes = rng.next_bytes(16)

    # SHA-256 hash, take first 16 bytes
    return hashlib.sha256(random_bytes).digest()[:16]

Java LCG

The PRNG is java.util.Random, a 48-bit Linear Congruential Generator. The seed combines currentTimeMillis() with a function of the auth parameter. Java uses 32-bit int semantics for shift operations (shift amount masked with & 31).

Request

CMD = 0x5C, INDEX = 0x00, LENGTH = 16, DATA = password[16]
Target: BLE board (0x04)

Response

  • INDEX = 1 — Password accepted, proceed to Phase 3
  • INDEX = 0 — Password pending, device may require user interaction (e.g., pressing a button on the vehicle)

Waiting for user interaction

Some devices require the user to press a physical button to confirm pairing. The app retries every 2 seconds until the device accepts or timeout is reached.

Default timeout: 60 seconds (COMM_WAIT_USER_TIMEOUT)


Phase 3: AUTH (cmd = 0x5D / 93)

Purpose: Authenticate using the password and serial number.

Setup

crypto.set_key(password, auth_param)  # key1 = password, key2 = auth

Request

CMD = 0x5D, INDEX = 0x00, LENGTH = 14, DATA = serial_number[14]
Target: BLE board (0x04)

Response

  • INDEX = 1Authentication successful. State transitions to COMM.
  • INDEX = 0 — Authentication failed.

On success

The password is stored for future reconnection. On the next connection, if the PRE_COMM response indicates a stored password (INDEX != 0), the app can skip SET_PWD and go directly to AUTH with the saved password.

On failure

The app retries from SET_PWD (up to 5 times with a new password each attempt).


Retry logic

Phase Max retries Timeout per attempt
PRE_COMM 10 2,000 ms
SET_PWD timeout / 2000 2,000 ms
AUTH 3 per attempt, 5 total restarts from SET_PWD 2,000 ms

Password persistence

After a successful AUTH, the password is persisted (typically in app local storage, keyed by device MAC address). On subsequent connections:

  1. PRE_COMM is sent as normal
  2. If INDEX != 0 in the response, the app checks for a stored password
  3. If found, SET_PWD is skipped entirely
  4. AUTH is attempted directly with the stored password
  5. If AUTH fails, the stored password is cleared and SET_PWD is retried

Complete handshake example

sequenceDiagram
    participant App
    participant Vehicle

    Note over App: key = SHA-1(bt_name ‖ zeros)
    Note over App: counter = 0 (non-SN)

    App->>Vehicle: PRE_COMM [5A B5 00 enc(3E 04 5B 00)]
    Vehicle->>App: [5A B5 1E enc(board 3E 5B idx auth[16] sn[14])]

    Note over App: Extract auth_param, serial_number
    Note over App: key = SHA-1(bt_name ‖ auth_param)
    Note over App: counter = 1 (SN mode)
    Note over App: Generate password

    App->>Vehicle: SET_PWD [5A B5 10 enc(3E 04 5C 00 pwd[16])]
    Vehicle->>App: [5A B5 00 enc(board 3E 5C 01)] (accepted)

    Note over App: key = SHA-1(password ‖ auth_param)

    App->>Vehicle: AUTH [5A B5 0E enc(3E 04 5D 00 sn[14])]
    Vehicle->>App: [5A B5 00 enc(board 3E 5D 01)] (authenticated)

    Note over App,Vehicle: Session established — all further communication uses password-derived key

Security notes

  • The BLE device name is used as key material in the first two handshake phases. An attacker who knows the device name (visible during BLE scanning) and can observe the PRE_COMM exchange has partial key material for Phase 1.

  • The session password's PRNG seed includes currentTimeMillis(), which has millisecond resolution. Combined with the known auth parameter, the password space is approximately 2^13 per second of time uncertainty. However, the password is transmitted encrypted, so an attacker would need to break the Phase 2 encryption first.

  • Replay protection via the monotonically increasing counter prevents replay of captured packets within a session.