6 July 2022: Keycard bypass on Ledger HW.1
Summary
Keycard is a security mechanism present on Ledger HW.1 and Ledger Nano devices (not Nano S, S+ and X). It is used as a second authentication factor to validate transactions, using a challenge / response mechanism. As no physical validation mechanism is present on these devices (they have no screen nor button), its purpose is to thwart malware that could ask the device to sign a transaction on behalf of the user once it has authenticated with his PIN.
It is possible to replace the security keycard with a new one using the SET_USER_KEYCARD
message. This message was not fully protected against bruteforce. An attacker can replace the security keycard if he knows the PIN code and has physical access to the device. He can then sign transactions without knowledge of the previous keycard.
Note that malware or rogue applications cannot take advantage of this vulnerability, as bruteforce requires a physical disconnection of the device on each attempt.
This vulnerability affects only Ledger Nano and Ledger HW.1, which have been discontinued. Subsequent devices, namely Blue, Nano S, Nano S+, Nano X, are not affected.
Technical details
The keycard used by older Ledger wallets is an authentication mechanism between the user and the hardware wallet. It intervenes in addition to the PIN: the PIN unlocks the device, while the keycard ensures that sensitive operations have indeed been initiated by a user (and not by a malicious program).
The Nano and HW.1 hardware wallets were sold with a keycard, and a security card. This security card contained a QR code with 16 bytes to reset the keycard mechanism on the device.
The keycard is a card on which there is a mapping between a base 58 alphabet and an hexadecimal digit:
During each sensitive operation, such as signing a transaction, the device generates a challenge to which the user must respond using his keycard. This is a sequence of characters (up to 10) taken from the destination address.
Let’s take for example this address: 1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa.
The device randomly selects several characters from it (in bold): 1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa.
User consults his card and returns the associated characters. In our case, with the card above, 90F00D
. After 30 failed attempts, the device erases its secrets and must be reset.
How are keycards generated? The 16 bytes of the barcode present in the recovery sheet are actually a 3DES key. This key is shared with the hardware wallet during the initialization phase.
- For every challenge, the device encrypts a buffer 0, 1, 2, 3…, 78, 79 with this key, in CBC mode with a zero IV. Then, for each byte obtained, performs an exclusive or between each of the nibbles to obtain 80 4-bit values.
- We obtain a mapping that associates each value between 0 and 80 with a 4-bit value.
- The device randomly chooses a character from the destination address, subtracts 48 and requests the associated 4-bit value.
A complete implementation is available in btchip-js-api.
Keycard customization
A user may want to replace the original keycard with his own, if he fears that it has been compromised or that he has lost his recovery sheet. For this, it sends to the device, from a trusted machine, a “SET_USER_KEYCARD
” message with a new 3DES key. In order to validate that the user has the current keycard, the device sends 4 random characters, and the user consults his keycard and returns the 4 associated hexadecimal values.
For example, if the device sends the challenge “wH42”, the user returns “01AC” with the above keycard. The new key is used instead of the old one.
Anti-bruteforce mechanism
If the answer is correct, the device continues to operate. If not, the device stops: the user must then disconnect and reconnect it.
However, there is no test counter on this mechanism. An attacker can therefore call the SET_USER_KEYCARD
message as many times as he wishes: he must disconnect and reconnect the device, enter the PIN, and ask for a challenge again.
The answer to the challenge being 4 numbers of 4 bits, an attacker has one chance in 65536 of finding the right answer, which would take him between a few hours and several days.
This number of trials can be considerably reduced, because it is possible to determine whether the first digits of the keycard are correct or not by performing a timing attack on the verification thereof. Indeed, the verification is not performed in constant time, as can be seen in the code below:
if (N_btchip.keyCard.key.size || N_btchip.keyCardAlt.key.size) {
unsigned char keycardData[KEYCARD_SIZE];
btchip_keycard_create(keycardData, NULL);
for (i=0; i<4; i++) {
if (G_io_apdu_buffer[ISO_OFFSET_CDATA + i] != keycardData[btchip_context_D.keycardChallenge[i]]) {
validated = 0;
break;
}
}
}
// [...]
if (!validated) {
SB_SET(btchip_context_D.halted, 0x01);
return BTCHIP_SW_INCORRECT_DATA;
}
An attacker capable of precisely monitoring the execution time can determine, during each test, if the first character he entered is valid, and thus gradually reconstruct the original keycard.
Impact on Ledger devices
A user with physical access to a Ledger HW.1 or a Ledger Nano can replace the keycard key with his own, without prior knowledge of the key.
Only Ledger HW.1 and Ledger Nano (not Nano S, S+ or X) are affected by this attack.
Remember that this mechanism was not designed to counter an attacker with physical access to the device (its role being to verify that a human is indeed at the origin of the transaction). However, a try counter should have been added.
As these products are discontinued, and the attack scenario is outside of the security model of this feature, no fix will be provided.
Credits
We would like to thank the security researcher Niv Yehezkel from Hexagate who discovered the vulnerability and reported it through our bug bounty program.