I really like the level of security provided by U2F, but along with security, you need to consider a recovery plan. Losing access to your most important accounts, if something happens with the main U2F token, is a serious problem. At the same time, I would like to avoid using a backup that compromises the security provided by U2F.
Popular backup methods
Today, it’s best practice to keep a second independent U2F token for backup; this token must be manually added to each service and stored in a "safe" place. Another common practice is to use the non-U2F method as a backup (OTP, recovery codes). Honestly, both of these methods are poor.
Independent U2F Token
This means that every time I register on some new service, I need to add both of my tokens. This fact raises a number of problems:
- The backup token should be fairly easily accessible. Despite the fact that I will not carry it with me on a keychain, I should be able to quickly get to it, so I can hardly come up with something better than keeping it at home. How real it is safe, even if a safe is used, you can talk for a long time;
- When I have to register for a service while away from home, I cannot add a backup token. So you need to try to remember that you need to add it later, and until this happens, there is no backup. In the worst case, I can completely forget about him;
- When I'm at home, both of my tokens are in the same place. This backup method is far from ideal: both tokens may not be available due to one incident (be destroyed or stolen);
- The fact that the backup token is stored at home is completely obvious. If someone really wants to get to my token, he already knows where to look for it;
- Non-universal method: not all services allow you to add more than one key to your account.
In my opinion, this “exemplary practice” is not very reliable, and rather burdensome. Let's look at another common practice.
Non-U2F method as backup
OTP:
- Using OTP as a backup is better than using it as the main 2FA method, but the fact of having OTP somehow opens up an additional attack vector;
- Phones break down, get lost and stolen, and if after its loss there is a chance that it will be in the hands of strangers, then you need to manually recall this backup on all accounts;
- I always carry a phone and a U2F token with me, so again, such a backup method is far from ideal: the probability of losing both of them immediately is much higher than if the backup were stored separately. But this item can be slightly compensated by using, for example, Authy, which stores the encrypted backup on its server;
- Non-universal method: unfortunately, there are a sufficient number of services that offer only custom applications and do not support standard TOTP.
Recovery Codes:
- Recovery codes must be stored in a safe place. Again, this “safe place” will most likely be my home, with almost the same problems as a separate U2F token;
- Again, a non-universal method: each service has its own approach to backup
So, to summarize, all of these methods are non-universal, burdensome and not too safe.
The best backup method
Now, after I have sufficiently criticized the current state of affairs, I will finally say what I really want. I really want to have two U2F tokens: primary and backup, but they must be configured in a certain way:
- When I register the main token on any device, the backup token automatically becomes operational for this service;
- As soon as I use a backup token on a service, the main token is invalid for this service.
Before we discuss the technical feasibility of this within the framework of U2F, I will explain why it is great and how I use it.
Why is it great
If we look at the criticism of the independent backup token described above, we can see that all the shortcomings of this method are eliminated:
- The backup token should no longer be easily accessible. Extreme examples can be: brick up a token inside a brick wall, or bury a meter and a half in a garden or elsewhere. No kidding, I'm quite ready to go for it;
- Regardless of where I am located, if I sign up for a service, I do not need to do anything to add a backup token to this service. I just use my main token, and I am in peace of mind, knowing that I have a backup;
- For outsiders, it is completely unclear where my backup token is located. Even knowing that it exists, trying to find it yourself hardly makes sense;
- It is safe enough. Even if something bad happens to my main token, it is highly unlikely that the same incident will affect the backup token;
- It is universal. This backup method will work on any service that supports U2F, regardless of what else this service supports.
And if something bad really happens with the main token, then I do the following:
- I dig out / unclear a backup token;
- Authenticate on all my services with U2F, thereby canceling the main token;
- I order a new pair of tokens, and upon receipt, add a new main token on all services, and revoke the old one.
At least for me personally, this strategy is a great compromise for a high level of security and an easy backup. It is safer and more reliable than any other method.
Implementation
U2F Protocol Overview
Before we can talk about implementation, we need to understand at a certain level how U2F works. Most manufacturers implement it as follows (not all of the following are present in the standard; some things are implementation details, but most existing implementations, as far as I know, work just that way):
device_secret
programmed in the U2F token, along with a 32-bit
counter
, which can only be incremented. When we register a U2F token on a service, the following happens:
- The browser sends the
AppID
to the U2F device (in fact, the domain name); - The device generates a random number (
nonce
), combines it with it with the AppID
, passes it all through HMAC-SHA256 using device_secret
as the key, and the resulting hash becomes the private key for this particular service: service_private_key
; - From
service_private_key
, the public key service_public_key
generated; - The device takes the
AppID
again, combines it with service_private_key
, and passes it through the HMAC-SHA256 again using device_secret
as the key. The result ( MAC
), along with the nonce
that was generated earlier, becomes key_handle
; - The device sends
key_handle
and service_public_key
back to the browser, and the browser passes to the service, which saves this data for future authentications.
Subsequent authentication proceeds as follows:
- The service generates a
challenge
(randomly generated data) and sends it to the browser along with key_handle
(which consists of nonce
and MAC
). The browser passes all this to the device, along with the AppID
(i.e. domain name); - The device, having
nonce
and AppID
, generates service_private_key
in the same way that it was generated during registration; - The device generates a
MAC
in the same manner as during registration, and comparing it with the MAC
received from the browser, it nonce
sure that nonce
not replaced, and therefore, service_private_key
reliable; - The device increments
counter
; - The device signs the
challenge
, AppID
and counter
using service_private_key
, and sends the resulting signature ( signature
) and counter
browser, which transfers this data further to the service; - The service checks the
signature
using the service_public_key
that it has after registration. Also, most services verify that counter
larger than the previous value (if this is not the first authentication). The purpose of this test is to make cloning of U2F devices inaccessible. As a result, if signature
matches and counter
greater than the previous value, authentication is considered to be successfully completed, and the service saves the new counter
value.
Now let's outline the details that are directly related to the discussion.
Details of interest
The first is that the device does not store
service_private_key
for each service: instead, it displays
service_private_key
every time using HMAC-SHA256. This is very important for us: obviously, if each device would store unique keys separately for each service, then only this device could authenticate subsequently.
This, by the way, is not a requirement of U2F: U2F does not indicate exactly how keys should be stored, and some early U2F implementations did, in fact, store keys for each service separately. This approach has the disadvantage that the number of services for which the device can be used is limited. The derivation of service_private_key
eliminates this drawback.And secondly, the device has
counter
to prevent cloning.
At first glance, it might seem that this
counter
does not allow us to implement the discussed backup strategy (at least, it seemed to me when I was trying to find a solution), but in fact, it only helps us! I'll explain now.
main idea
The idea is this: at the production stage, program two tokens in such a way that they both have the same
device_secret
, but the backup token needs some correction: instead of using
counter
in its pure form (as ordinary tokens do), it should add some big constant to
counter
. For example, half the 32-bit range, i.e. approximately
2 000 000 000
, it looks reasonable: I’m unlikely to exhaust so many authentications in my entire life.
In fact, that’s all. Simple and effective.
Having two tokens programmed in this way, I hide the backup token in some
really hard-to-reach place, and never touch it. If something terrible happens and I lose access to the main token, I still get to the backup token, and I can immediately use it on all services where I registered the main token, because The backup has the same
device_secret
, and its
counter
starts with a really large number, which I won’t get for the rest of my life.
Also, I draw attention to the fact that
I do not propose making tokens cloned . Two tokens, although they have the same
device_secret
, have different counters, and after programming
device_secret
there should be no way to get it back from the device or create a clone in any other way.
A Note About Counter
An attentive reader may notice that there is the following security problem: what if an attacker gains access to the main token and somehow initiates 2,000,000,000 authentications? Then he gains access to the service even after the backup token has been used on this service.
Fortunately, this problem has a simple solution. In any case, the counter must be implemented in hardware (presumably on some crypto processor), and for safe implementation this hardware counter must have a range of less than 32 bits. For example, on the
ATECC508A, counters can only count up to 2097151, so by setting the constant added to the counter to any value greater than the maximum counter value, we can be sure that the main token can never count to the counter in the backup token.
To clarify: let's say that our U2F token uses ATECC508A, and designate the counter inside the ATECC508A as
hw_counter
. Then:
- In the main token, we use for calculations:
hw_counter
; - In the backup token, we use for calculations:
hw_counter + 2000000000
.
Please note that we do not modify the real
hw_counter
inside the crypto processor; it will still count from 0 to 2097151. Instead, every time we need to get the counter value, we read
hw_counter
from ATECC508A, then we add our constant and return it (for further calculations for U2F).
Thus, the range of counter values in the main token will be [0, 2097151], while the range of counter values in the backup token will be [2000000000, 2002097151]. The fact that these ranges do not overlap ensures the cancellation of the main token when using a backup (if the service uses
counter
; the main services that I checked use it).
Actual implementation
None of the manufacturers of U2F tokens that I know about support the required customization today. But fortunately, there is an open-source implementation of the U2F token:
SoloKeys .
I wrote my original article (in English) a year ago, and this part is a bit outdated: then SoloKeys was at the prototyping stage, and I used the previous iteration of the project:
u2f-zero . Therefore, I will not translate this part now, since the only way to get a u2f-zero device is to solder it yourself, and it is hardly advisable to do this (although there are instructions on the github).
Nevertheless, all the details of the necessary modification of u2f-zero are given in the
original article .
When my hands reach the solokeys, I will write instructions for its modification.
One way or another, this is the only way I know today to get a working U2F token with a reliable backup. Checking several services (at least google and github) showed that it works: by registering the main token on the service, we can also use backup, and after the first use of the backup, the main token stops working. Awwwwwww. <3
A warning
Despite the fact that this backup strategy is cool, I am not so sure of its specific implementation through u2f-zero or solokey. This path is the only way to get what you want, so I went that way; but assuming that the attacker is gaining physical access to the U2F device, I’m not sure that hacking the device (i.e. getting
device_secret
from it) will be as difficult as it would be in the case of Yubikey or other major manufacturers. The authors of solokey claim that “the level of security is the same as in a modern car key,” but I did not conduct any examinations to confirm this.
However, to be honest, I'm not really worried about this. If an attacker simply steals a token without the intention of returning it, then the complexity of breaking it does not matter, because an attacker can simply use this token to access the account and, for example, simply revoke this token and add another. However, for this I must have other serious security problems as well. U2F token is only the second factor.
So, the only scenario where solokey may be less secure than something else is when an attacker tries to access the device for a short period of time, get
device_secret
from it, and return the device back, invisibly to me. To do this, he needs to read the contents of the flash microcontroller (or RAM at the right time), and this is not very trivial.
Taking into account all the factors, I believe that for me personally to have a reliable backup is much more important than having an ultra-secure hardware implementation of a U2F device. The likelihood of problems with such a safe implementation and the lack of a good backup is higher than the likelihood of problems with u2f-zero (solokey) and backup.
Conclusion
The considered backup strategy outperforms the alternatives in all dimensions: it is universal, safer and more reliable than any other methods.
I will be glad if at least one of the main manufacturers implements this in their products, but there is no certainty yet. One Yubico support guy, James A., even told me that the backup is as I need, “is not possible with the way U2F is designed,” and after I set out the implementation details, it just stopped responding.
Fortunately, this was not as impossible as Yubico believes.
My original article in English: Reliable, Secure and Universal Backup for U2F Token . Because the author of the original article is myself, then, with your permission, I did not put this article in the category of “translation” .