One Time Passwords are everywhere these days. We have great standards, like HOTP and TOTP, which means that all our implementations can be interoperable. We also have open source implementations of these standards, such as FreeOTP. Tons of server applications and web services use OTPs every day for millions of users.
But, currently, one big user experience issue comes to the fore: typing OTP codes is error-prone and time-consuming. Can’t we do better?
On and off for the last few years, I have been working to improve this situation. The result I have come up with is a companion app to FreeOTP called Jelling that allows FreeOTP to send OTP codes directly to paired Bluetooth Low Energy devices. I hope to explain in this post how Jelling works and outline the challenges that still remain. Hopefully you, my most esteemed reader, can help us close some of the gaps that remain.
Bluetooth LE GATT
Bluetooth Low Energy (BLE) has a mode of operation called the Generic Attribute Profile (GATT). Applications define collections of services that contain characteristics which are manipulated by remote entities.1 This means we can define our own protocol using GATT and use this protocol to send a token code from a Sender to the intended Receiver. The Sender will be an Android or iOS version of the FreeOTP application. The Receiver will usually be a Windows, macOS or Linux computer.
First, we have to decide whether the Sender or the Receiver will be the Peripheral or the Central. A Peripheral sends advertisement packets which tell other devices that the specified service is available for use. The Central scans for these advertisements and, when found, can connect to the Peripheral and use its Services. Two principles are important. First, a Peripheral can only have one Central, but a Central can have many Peripherals. Second, scanning uses more power than advertising, so it should be used sparingly.
Second, we need to consider the user experience. Attempting to initialize the token sharing transaction from the Receiver might seem to make the most sense, since the user is already typically doing something on the Receiver when he wants a token code. However, this means we would need to negotiate over BLE about which token code to receive. This negotiation would use a lot of power. Further, because the tokens are authentication credentials, we must confirm on the Sender before sending them to protect from token theft. On the other hand, if the Sender selects the token first and initiates the share we don’t require negotiation or confirmation at all. Therefore, we can reduce the number of GATT requests we need to send to one.2
Third, we need to consider security. We can’t just share a token without being sure about who will receive it. This means that we must use both BLE authentication and encryption. Further, if we implemented the Sender as a Peripheral where it sent characteristic notifications including the token code, we often can’t see who is subscribed to those notifications due to platform API limitations. This doesn’t work for our case since we might care that some OTP codes are only sent to some paired devices.
All of this leads to a simple implementation. The Sender (FreeOTP) operates in
Central mode and the Receiver (computer) is a Peripheral that exposes a single
B670003C-0079-465C-9BA7-6C0539CCD67F) which, in turn, exposes a
single, write-only characteristic (
protect this characteristic using both BLE encryption and authentication. The
Sender initiates the connection and transfers the OTP by sending a single write
to the characteristic containing the OTP code it wishes to share. This means
that the Receiver needs to advertise whenever it wants to receive token codes
(default: always3). The Sender scans and connects only when it wants to
share; this preserves battery life. The Sender chooses which token to share and
which Receiver to send it to. Once the Receiver has the token, it emulates a
keyboard and types wherever the cursor is. This user experience is simple and
The Good News
I’ve implemented Receivers for Windows, macOS and Linux. Each is implemented using its platform’s native development tooling and languages (C#, Swift and C, respectively). The Receivers advertise themselves as a BLE Peripheral which FreeOTP can connect to via pairing. Once paired, FreeOTP can send tokens to the selected Receiver.
I’ve also implemented this behavior in FreeOTP. For iOS, this is already merged to master. For Android, this lives in the bt branch which I hope to merge soon. Like always, you can click a token to show the code directly. However, now you can choose to share the token. This pops up a secondary menu which shows the available Receivers within range. Selecting a Receiver shares the token if already paired. Otherwise, it begins the pairing procedure.
The Bad News
Unfortunately, the platform support for this functionality appears to be
frustratingly inconsistent. The following chart documents my success and
failure. This test reflects my tests with both FreeOTP and LightBlue (a BLE
GATT testing application on iOS and Android). This proves (to me) that the
problem isn’t my code but rather platform incompatibilities. For more details,
see the charts in the
README.md of each repository.
|Pixel||Nexus 5x||iPhone 6+|
Sharing works with my iPhone 6+. It does not work from either my Google Pixel or Nexus 5x. I suspect my Pixel has problems because I wasn’t able to get it to work anywhere. However, on my Nexus 5x the simple, single GATT write never causes the callback to fire in my Windows code. It does work if I disable authentication. This leads me to believe that there is a compatibility issue between Windows and Android during pairing.
The macOS BLE implementation seems to be the most mature of all the platforms. Sharing works on both my iPhone 6 and Nexus 5x. Like I mentioned above, my Pixel seems horribly broken. I even updated to 8.1 beta to see if it has been fixed. No dice. I will try on another Pixel that has been reset to factory defaults soon.
Unfortunately, Linux appears to be the most broken of all the platforms. With my iPhone 6+ I am able to successfully see the advertisement and perform service discovery. But when I perform a GATT write, pairing never begins. My Nexus 5x can see the advertisement and connect, but it fails performing service discovery. My Pixel connects only in non-BLE mode (it does seem to connect in BR/ATT mode; but that is useless).
The Call for Help!
I’d really like to bring this functionality to a FreeOTP release in the near future. But we need your help! “How?” you ask.
Test Jelling on your systems. We need tests with all different kinds of phones and laptops. Test instructions are available in the
README.mdof each repository (Windows, macOS and Linux).
Assist debugging combinations where things are broken. In particular, we need help from Bluez. If you are a Bluez developer, please contact me!
Review the code. I’m not a Windows or macOS developer. If I’m doing something wrong, it is probably my fault. Bug reports are welcome. Patches are even better.
- Search for “Bluetooth GATT” to learn more. [return]
- “Here is the token code.” [return]
- There is a trade-off here between usability, privacy and battery usage. The later is much less of a concern in the Receiver since it has a large battery and is often plugged in. Privacy is a tough one. If you’re advertising you can be used by FreeOTP, you can also be tracked by someone physically nearby. We consider this low-risk. [return]