xref: /btstack/tool/dump_keys.py (revision 6897da5c53aac5b1f90f41b5b15d0bd43d61dfff)
1#!/usr/bin/env python3
2# BlueKitchen GmbH (c) 2022
3
4# parse PacketLogger and dump encryption keys
5
6import sys
7import datetime
8import struct
9
10
11def as_hex(data):
12	str_list = []
13	for byte in data:
14		str_list.append("{0:02x} ".format(byte))
15	return ''.join(str_list)
16
17
18def as_key(data):
19	str_list = []
20	for byte in data:
21		str_list.append("{0:02x}".format(byte))
22	return ''.join(str_list)
23
24
25def as_bd_addr(data):
26	str_list = []
27	for byte in data:
28		str_list.append("{0:02x}".format(byte))
29	return ':'.join(str_list)
30
31
32def read_header(f):
33	bytes_read = f.read(13)
34	if bytes_read:
35		return struct.unpack(">IIIB", bytes_read)
36	else:
37		return (-1, 0, 0, 0)
38
39
40def handle_at_offset(data, offset):
41	return struct.unpack_from("<H", data, offset)[0];
42
43
44def bd_addr_at_offset(data, offset):
45	peer_addr = reversed(data[offset:offset + 6])
46	return as_bd_addr(peer_addr)
47
48
49class hci_connection:
50
51	def __init__(self, bd_addr, con_handle):
52		self.bd_addr = bd_addr
53		self.con_handle = con_handle;
54
55
56def connection_for_handle(con_handle):
57	if con_handle in connections:
58		return connections[con_handle]
59	else:
60		return None
61
62
63def handle_cmd(packet):
64	opcode = struct.unpack_from("<H", packet, 0)[0];
65	if opcode == 0x201a:
66		# LE Long Term Key Request Reply
67		con_handle = handle_at_offset(packet, 3)
68		conn = connection_for_handle(con_handle)
69		print("LE LTK for %s - %s" % (conn.bd_addr, as_key(reversed(packet[5:5+16]))))
70	elif opcode == 0x2019:
71		# LE Enable Encryption command
72		con_handle = handle_at_offset(packet, 3)
73		conn = connection_for_handle(con_handle)
74		print("LE LTK for %s - %s" % (conn.bd_addr, as_key(reversed(packet[15:15+16]))))
75	elif opcode == 0x040b:
76		# Link Key Request Reply
77		bd_addr = bd_addr_at_offset(packet, 3)
78		print("Link Key for %s - %s" % (bd_addr, as_key(reversed(packet[9:9+16]))))
79
80def handle_evt(event):
81	if event[0] == 0x05:
82		# Disconnection Complete
83		con_handle = handle_at_offset(event, 3)
84		print("Disconnection Complete: handle 0x%04x" % con_handle)
85		connections.pop(con_handle, None)
86	elif event[0] == 0x18:
87		# Link Key Notification
88		bd_addr = bd_addr_at_offset(packet, 2)
89		print("Link Key for %s - %s" % (bd_addr, as_key(reversed(packet[8:8+16]))))
90	elif event[0] == 0x3e:
91		if event[2] == 0x01:
92			# LE Connection Complete
93			con_handle = handle_at_offset(event, 4);
94			peer_addr = bd_addr_at_offset(event, 8)
95			connection = hci_connection(peer_addr, con_handle)
96			connections[con_handle] = connection
97			print("LE Connection Complete: %s handle 0x%04x" % (peer_addr, con_handle))
98
99
100# globals
101connections = {}
102
103if len(sys.argv) == 1:
104	print ('Dump encryptiong keys from PacketLogger trace file')
105	print ('Copyright 2023, BlueKitchen GmbH')
106	print ('')
107	print ('Usage: ', sys.argv[0], 'hci_dump.pklg')
108	exit(0)
109
110infile = sys.argv[1]
111
112with open (infile, 'rb') as fin:
113	pos = 0
114	try:
115		while True:
116			(entry_len, ts_sec, ts_usec, type) = read_header(fin)
117			if entry_len < 0:
118				break
119			packet_len = entry_len - 9
120			if packet_len > 66000:
121				print ("Error parsing pklg at offset %u (%x)." % (pos, pos))
122				break
123			packet = fin.read(packet_len)
124			pos = pos + 4 + entry_len
125			if type == 0x00:
126				handle_cmd(packet)
127			elif type == 0x01:
128				handle_evt(packet)
129			elif type == 0x02:
130				pass
131			elif type == 0x03:
132				pass
133
134	except TypeError as e:
135		print(e)
136		print ("Error parsing pklg at offset %u (%x)." % (pos, pos))
137