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¶
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_PWDINDEX != 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¶
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¶
Response¶
INDEX = 1— Password accepted, proceed to Phase 3INDEX = 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¶
Request¶
Response¶
INDEX = 1— Authentication 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:
- PRE_COMM is sent as normal
- If
INDEX != 0in the response, the app checks for a stored password - If found, SET_PWD is skipped entirely
- AUTH is attempted directly with the stored password
- 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.