You may remember that over three and a half years ago, I started looking in Abbott’s FreeStyle Libre 2 reader protocol, which turned out to be encrypted between the device and their software, as a clear attempt at messing with reverse engineers — I say that because the protocol does not require you to confirm the connection on the device, which means that it’s clearly not done to protect the private data on one’s glucometer from malware or other attempts to “slurp” it up unwittingly.
Well, finally I added support for the Libre 2 to glucometerutils earlier this month. The commits on the driver itself are quite underwhelming, since the Libre and Libre 2 readers use effectively the same underlying protocol except for the encryption part (which I knew after spending a lot of time on Ghidra), so instead most of the changes are “hidden away” in the release of freestyle-hid and the new package freestyle-keys — before I get to talk more about how this is set up, I do want to thank MPM1107 who reached out with the full implementation of the handshake, and instructions on extracting the required keys.
The first thing to make clear up front is that, as always is for glucometerutils, the implementation is a complete clean room reverse engineering effort. While there was no wall between reading through the code and writing it, at least for myself, nobody has lifted any code from the original implementation. This is important because we already know that Abbott knows how to use the DMCA when their intellectual property is being misappropriate.
Indeed, in a interesting turn of events, the algorithms, both for encryption and MAC, used by the protocol are completely standard: they are Speck and CMAC. The handshake though, is definitely a custom job, as it relies on a few fixed and a few randomized values that are provided both by the reader device and the host. By the time you read this blog post, it should all be nicely documented — although my original write up is actually quite close, with the exception of not knowing the algorithms used, and a few of the nuances.
In my original write up, I thought that the seed for the encryption was related just to four strings (AuthrEnc
, AuthrMac
, SessEnc
, SessMac
), the serial number, and two randomized strings — turns out I was incorrect. For each of the four operations, you also need another shared key which is known to both the software and the reader. These four “master keys” if we want to call them like that, are what the freestyle-keys package contains.
To limit the blast radius of possible cease and desist, or takedown, requests, I preferred making a separate package to host these four constants. Not that I expect any such requests to come through for this, since these keys are not, to the best of my understanding, affected by either DMCA or European equivalent legislation (though, obviously, I am not a lawyer.) DMCA and equivalent legislation would apply to a similar key (and indeed, both CSS, AACS, and HDMI have had similar troubled past), if it was used to protect copyrighted material — your own glucose reading are not copyrighted material though, and the key is only a mean to frustrate reverse engineers (and in that, it succeeded! unlike nearly every other meter I got my hand on, this one took over three years to become available to third party software to read!)
I already noted above that, even looking at it as a completely opaque system, there’s no value in encrypting the (local) communication between a host and a device — it’s not like anyone that hasn’t access to the host in the first place can sniff on your USB cable. If you wanted to protect the user’s data from malware or other unauthorized access, you would require confirmation on the reader, before software could actually download it, but there’s no option for that anywhere, at least on the Libre 2. In the USA, a different version called Libre Pro was released years ago, and, as I understand it, it’s actually locked down so that only medical professionals can download the data; that might actually have a on-device confirmation, but since I have no access to it I can say.
While there is always the option that the encryption is pretty much just a face-saving move to make a regulator happy (by experience medical regulators can be that silly), the other thing that makes me think that it was done only to frustrate reverse engineering efforts is that the handshake appears to be designed so that, given a full USB traffic dump, a reader would be able to reconstruct the whole traffic, but the host wouldn’t be able to decode anything. That’s because the reader’s random bytes are provided in cleartext, but the host random bytes are only exchanged encrypted.
Moving on from the handshake, it does look like the encrypted packets restrict the maximum size of commands to 56 bytes — which means either there’s something not quite right in the way I’ve been looking at the binary packed commands on the Libre, or they are flexible enough to have dynamic sizing between them. I’ll investigate further as time permits, but since this does not affect glucometerutils, it’s very low in priority. Since the commands are only ever initialized by the host, when encrypting them, in addition to the session key generated as part of the handshake, the packet contains a cleartext counter. This allows different otherwise identical packets to be encrypted to different ciphertext. Thankfully, the reader does not seem to care if you just leave the seed to zero for every single command.
You may now be wondering, if you went to look at the code, why the library is carrying around a whole Speck and CMAC implementation, particularly as I said they are standard. Well, the reason is annoying but only so much: there’s no good Python implementation of both. There’s a Simon and Speck implementation, but it does not include CMAC (which makes sense, they are not strictly related), and while pycryptodome provides a CMAC implementation, it doesn’t look like you can pass your custom algorithm implementation — and pycryptodome does not implement Speck.
So for the time being, it will be included in the library itself. Longer term I’ll consider depending on libraries that implement it, at least when requesting encryption support.
Bonus: Multilanguage Readers
In addition to updating the code to support this new device, I also implemented support for reporting the correct native glucose unit for the device. As suspected, the $uom?
command is the “unit of measure”, and indeed it has the same semantics of other FreeStyle devices.
But probably the best thing of finally having the ability to talk to the device is that I could use my console tool to send arbitrary commands, which lead me to find that the $lang
command not only allows you to query for the language that is configured on the device, but also to set it… and to set it to values that are not available as part of the on-device UI!
The device I’m using here (marked as “TEST” to distinguish it from the one I actually used for a long time as my primary glucometer) is the one I started this whole journey with, close to four years ago. It’s a continental European model (not the British model I used as daily meter) and it supports only four languages according to its UI: English, French, German, and Dutch. But by using the $lang
command through my console, I manage to cycle through 19 languages plus 4 variants.
I can imagine how much of a lifesaver this would be, particularly for the British model that allows no other language out of the box, particularly given that the reader devices nowadays are used, I expect, mostly by either young or elderly diabetics, who wouldn’t have, or wouldn’t be able to reliably use, a smartphone to scan instead.
As I said before you don’t call your work complete once you discovered these settings, of course, you need to make something useful out of it, and I don’t know how to make this widely useful. But at the very least, I can say that having this documented and available is a first step. If you do happen to have such a device and want to change the language for yourself or a family member, you can do so with the freestyle-hid library:
$ pip install 'freestyle-hid[tools,encryption]>=1.1.1'
$ freestyle-hid-console --product-id 14672 - '$lang,17'
Code language: plaintext (plaintext)
This example, should work just as well on Windows, macOS or Linux. I’m assuming the target is a FreeStyle Libre 2 reader. I need to find my Libre 1 to see if this works on it as well or not, but given the similarity of the firmware I’d be extremely surprised if it didn’t – but you would have to change the product ID. 14672 is 0x3950 which is the Libre2, please don’t ask why I had to put it in decimal.
The language I’m setting the device to in the example is Polish. You can find the full list in the repository if you need a different language instead.
Hopefully, this will help someone in need, some day.