Medtronic uses a custom cryptographic protocol that is implemented over the GATT layer. The protocol consists of a handshake procedure which contains a common Session Key derivation and a Permit Exchange part. After the handshake has been completed, selected GATT traffic is sequenced, encrypted and signed using AES.
Presumably due to the used BT protocol (see more here) SAKE consists of a server and a client part. The BT Peripheral is the server and the connecting Central is the client.
The key databases are static between devices and each device type / model. See the detailed description of the format and the actual keys here.
TODO
After a successful advertisement and subsequent BLE connection of a SAKE-compatible device the following steps are necessary to perform a successful handshake:
- The connecting client will enable notifications on the SAKE characteristic.
- The server sends 20 zero bytes using BLE notification.
- The client responds with 20 zero bytes using BLE write characteristic messages. This will trigger the handshake procedure on both devices.
- The client and server will exchange the remaining 6 messages defined by the protocol.
Note: all messages will be identified by their overall index in the whole process, followed by the sender's first character. So for example message 0_s is the first message ever during pairing and it is sent by the server.
All handshake messages are 20 byte long. Random padding bytes are used if the actual content is smaller.
If something goes wrong during the handshake, the protocol is implemented in a way where it will send random garbage, instead of nothing. This is presumably a security feature and als the other side can fail quicker instead of hanging on a timeout.
This message just contains the device type of the server and probably some version flag.
| Byte index | Meaning |
|---|---|
| 0 | Server Device Type |
| 1 | Constant 0x1. Probably protocol version. |
This message contains the randomly generated key material and nonce of the client. It also contains the client device type.
| Byte index | Meaning |
|---|---|
| 0-7 | Client key material |
| 8 | Client device type |
| 9-12 | Client nonce |
This message is the first complex message. The server also generates its key material and nonce, just like the client and it will perform the key derivation, since it now know both the client's and its own key materials.
| Byte index | Meaning |
|---|---|
| 0-7 | Server authentication tag |
| 8-15 | Server key material |
| 16-19 | Server nonce |
For the Auth tag calculation we have to use the following keys from the pre-shared Key Database:
handshake_auth_key: it is used to sign the merged keysderivation_key: this is appended to the server and client key material, just before signing
The Auth tag will be calculated the following way:
server_tag = CMAC_AES_8(
key=handshake_auth_key,
data=(server_key_material + client_key_material + derivation_key)
)
We now verify the the server's authentication message (2_s) and generate the client's.
| Byte index | Meaning |
|---|---|
| 0-7 | Client authentication tag |
client_tag = CMAC_AES_8(
key=handshake_auth_key,
data=(server_tag + server_key_material + derivation_key)
)
Now, everything has been created in order to have an encrypted session on both sides. So all traffic from now on will be sequenced and encrypted. TODO: write more about SeqCrypt.
This message is used to establish a "permit" on the remote side (client). It is a signed and encrypted blob that only the remote side can decrypt. Every key database has two keys for exactly this, the permit_decrypt_key and permit_auth_key.
| Byte index | Meaning |
|---|---|
| 0-15 | Session-encrypted permit |
The permit is padded with 1 random byte BEFORE session-level encryption.
Session-encrypted permit = 12 bytes of data, 4 bytes of CMAC at the end.
permit_decrypt_key can be used to decrypt a permit using AES ECB mode.
CMAC is then verified using permit_auth_key.
The decrypted permit format is currently not very well understood. The first byte has to be 0x00, the second has to equal to the remote sides' (prover) device type.
TODO: improve this TODO: 5_c
Warning
This is a lazy AI autogenerated documentation from now! dont look at it!
- Auth primitives: AES-CMAC and AES-ECB/CTR
- Static keys (stored in a KeyDatabase) contain:
derivation_key(16 bytes)handshake_auth_key(16 bytes)permit_decrypt_key(16 bytes)permit_auth_key(16 bytes)handshake_payload(16 bytes)
The protocol performs a 6-message handshake to authenticate both parties and derive a symmetric session key for AES-CTR payload encryption with a small, embedded MAC scheme.
sequenceDiagram
participant Client
participant Server
Note over Client,Server: All handshake messages are 20 bytes.
Client->>Server: Msg 0 (20B) — advert / device type (bytes: [0]=device_type, [1]=version?)
Server->>Client: Msg 1 (20B) — client key material + device type + client nonce
Client->>Server: Msg 2 (20B) — [MAC(8)] || server_key_material(8) || server_nonce(4)
Server->>Client: Msg 3 (20B) — [MAC(8)] (mutual auth)
Client->>Server: Msg 4 (20B, encrypted) — encrypted payload (permit or static payload)
Server->>Client: Msg 5 (20B, encrypted) — encrypted payload (permit or static payload)
Note over Client,Server: After Msg 2/3 verified, both derive session keys and start using encrypted frames.
-
Msg0 (checked by
handshake_0_s): 20 bytes;msg[1]expected == 1;msg[0]taken as a device type indicator. -
Msg1 (parsed by
handshake_1_c):client_key_material= bytes 0..7 (8B)client_device_type= byte 8client_nonce= bytes 9..12 (4B)
-
Msg2 (parsed by
handshake_2_s):received_mac= bytes 0..7 (8B)server_key_material= bytes 8..15 (8B)server_nonce= bytes 16..19 (4B)received_macis verified with CMAC-AES-8 over the messageserver_key_material || client_key_material || derivation_keyusinghandshake_auth_key(seeauth8).
-
Msg3 (parsed by
handshake_3_c):- Client computes
auth1 = CMAC(handshake_auth_key, msg=(server_key_material||client_key_material||derivation_key))(8B). inner = auth1.digest() || server_key_material || derivation_key(32B)- Server's Msg3 contains CMAC-AES-8 over
inner; client verifies first 8 bytes.
- Client computes
- Once Msg2/Msg3 succeed the code derives the session cipher key:
session_key = AES-ECB(derivation_key).encrypt(server_key_material || client_key_material)— 16 bytes.
- Nonce material:
nonce = client_nonce || server_nonce(8 bytes total). SeqCrypt then constructs a per-message CTR nonce asseq (5 bytes) || nonce (8 bytes)before left-padding to 16 bytes for CMAC.
-
Each encrypted frame structure (as used in
SeqCrypt.decrypt) is:ciphertext || seq_byte || mac_first2bytes- Total trailer size = 3 bytes where:
seq_byte= transmitted sequence indicator (see sequence math below)mac_first2bytes= the first two bytes of a 4-byte CMAC tag
-
Verification steps performed on receive:
- Compute
d = (msg[-3] - (self.seq // 2)) & 0xFF - Compute
seq = self.seq + 2*dand buildnonce = seq.to_bytes(5,'big') || self.nonce - Compute
c = CMAC-AES-4(key)overnonce.ljust(16, b'\0') || ciphertext - Reconstruct the full 4-byte tag by concatenating
msg[-2:](two bytes sent) withc.digest()[2:4](the computed last two bytes), then callcobj.verify(...). - If verification succeeds, set
self.seq = seq + 2and decryptciphertextwith AES-CTR usingnonce.
- Compute
Notes:
- The scheme transmits only the first two bytes of the 4-byte CMAC and reconstructs the trailing two bytes from the computation. This provides a 2-byte on-the-wire tag with a 4-byte internal check.
- Sequence numbering encodes the counter in the single
seq_bytefield as a delta relative to the receiver'sself.seq//2.
- After session keys are set, handshake messages 4 and 5 carry a 16-byte encrypted payload (after decryption):
- If the decrypted payload equals a device's
handshake_payload(from static keys), it's logged as a payload match. - Otherwise, the receiver attempts to decrypt payload with
permit_decrypt_key(AES-ECB) to obtain a 16-byteplainvalue with structure:plain[:12]= permit dataplain[12:16]= CMAC-AES-4 tag computed withpermit_auth_keyoverplain[:12]- The code checks
plain[0] == 0andplain[1] == prover_device_typeto match device type.
- If the decrypted payload equals a device's
auth8(...)builds a CMAC-AES object withmac_len=8and uses thehandshake_auth_keyto MAC the concatenationserver_key_material || client_key_material || derivation_key.- The session key derivation uses AES-ECB encryption with the 16-byte
derivation_keyof the concatenated 16 bytesserver_key_material||client_key_material(8+8). SeqCryptexpects an 8-byte static nonce (client_nonce||server_nonce) and uses a 5-byte sequence counter embedded into the CTR nonce.- Message trailers are only 3 bytes; the design trades authenticity bandwidth for message size by sending only 2 bytes of the MAC and reconstructing the rest on the verifier side.