NFC Hacking: Part 1 – Authentication Systems Security

Check out the project on GitHub.

Recently, I became interested in the security of various NFC/RFID contactless access control systems. You have probably seen some in action, for example smart door locks which you can unlock using a small key fob. Even credit cards and recent smartphones use the same technology to pay by touching the register terminal. I am by no means an expert in these technologies, but I wanted to document what I have learned.

Maybe you have seen some form of two-factor authentication hardware, like a YubiKey. I like these small USB/NFC keys, and use them to secure my passwords and online accounts. During my work on KeePassXC (stay tuned for a post about this in the future), I learned quite a bit about the inner workings of the Yubikey and how its two-factor challenge-response functionality works. But to understand why the system is as it is, we first have to consider what constraints and security considerations apply.

Case Study: A Smart Door Lock

Let’s suppose we want to build a smart door lock. Some kind of electronic device that unlocks a door, if and only if the correct keycard is presented to it. This serves as a easy to understand system, but all these methods can be use for more abstract things like computer authorization systems as well.

Alright, we purchase a microcontroller and a NFC reader module along with some compatible NFC cards. For the purpose of this post, I went with an Arduino Uno and a PN532 breakout module. The module offers a fully programmable NFC interface and is connected to the Arduino via the SPI port. No actual lock motor or anything is connected, the serial interface will have to be enough.

For this example, we will look at keys which are single-factor (no additional PIN entry or anything), due to convenience.

Next, what do we require from our door lock in terms of security?

  • Any source is freely available (the system is known)
  • Only the correct physical key unlocks the door
  • Key cannot be cloned / copied
  • Key cannot be derived by disassembling the lock
  • Keys cannot be derived by eavesdropping on the wireless communication
  • (The lock is resistant to physical attacks)

Most bullet points are kind of obvious, assertions you would expect from a quality mechanical door lock as well. The limitation to open-source software is a conscious decision. There are commercial solutions that advertise a high degree of encryption and security, but I chose to implement my own. I believe in Kerckhoffs’s principle (a cryptographic system should be secure if everything about it, except the key, is known), and I also want to learn about the technology and underlying mechanisms instead of deploying a readymade solution.

Eavesdropping on the wireless communication is a legitimate concern when using NFC, although the distances are pretty short secret data can still be leaked, when using the right equipment. A cheap solution is to use a software defined radio (eg. an Airspy Mini), and a suitable decoder (eg. NFC Laboratory or GNURadio).

The last point is a bit more subjective – at some point, you can just break the door using a tank. There has to be a conceptual line which we consider the limit of physical attack on the lock. For this setup, let’s assume the electronics of the lock are on the outside or at least accessible without issue. This means that a potential attacker is able to extract and modify code on the microcontroller, but not without ripping the lock from the door and bringing it into a lab – no shorting the door in this scenario, that is beyond scope. Keep in mind that this is a virtual door which serves as an analogue to some abstract IT system.

The test setup. Blue: Arduino Uno board, Red: NFC module, Center: Yubikey 5 NFC, Right: Airspy Mini SDR connected to an antenna.

Method 1: Comparing UIDs

The keycards we chose are cheap popular RFID tags. More precisely, one of the most popular and used tag type on the planet, ISO/IEC 14443 Type A (Mifare) NFC tags. These tags carry essentially a static, non-changeable, globally unique identification number. This ID is usually made up from 4 or 7 bytes. In addition, these tags have space for some amount of freely writeable data (usually 1 kilobyte). This second data block my be used to store anything, eg. a NDEF container which contains a URL to your blog. Once a user scans the tag using a smartphone, the website will be opened.

A selection of common Mifare NFC / NTAG data tags.

It is important to note that both the ID and the user data are free to be read by anyone that has the keycard and a compatible reader, i.e. they offer no means of restricting read access. Obviously, we do not want our keys to be cloneable, so we do not use the user data section to hold the key information, which could be easily copied to a second card.

Instead, our smart door lock just has to read the unique ID of a keycard, and compare it to the one it was configured with beforehand. If the IDs match, the user presented the correct keycard and the door may open. Pretty easy to implement.

Now, you might think, wow, non-changeable and even globally unique? That are quite strong assertions! And you are correct, neither of them hold true in the real world. First of all, there are cards that exhibit a rather interesting behavior concerning their IDs – due to a “undocumented bug” the ID can be changed as often as you want. These cards are commonly referred to as “first generation magic” and are available online (eg. at KSEC or AliExpress) for a few bucks. Later on, the “second generation magic” cards hit the market, where a changeable ID data block is openly advertised and supported. The first generation cards require special tools like a Proxmark in order to exploit the “bug”, but the second generation magic cards can be configured using even common smartphone apps. In conclusion, the IDs of the tags can be changed and copied to other cards.

In addition, once you know the ID, you can emulate a virtual keycard. A rooted Android phone or a Proxmark can act in a way such that the lock thinks it is presented a keycard with the emulated ID. For more info, see this presentation.

As for the global uniqueness – think about how many potential IDs even exist. In the case of the still commonly used four byte variant, that’s 2^32 potential IDs, or just a bit fewer than 4.3 billion. Sounds a lot, but it is really not. Consider how many humans and systems use keycards, and factor in that enough card manufactures are complaining that they are running out of 4-byte IDs – and they are not necessarily coordinating with each other. The next best thing, GUIDs (which are not even guaranteed to have no collisions) use 16 bytes.

So, how did we do in terms of the requirements we defined above?

Attack 1

  • Only the correct physical key unlocks the door
  • Keys cannot be cloned / copied

Protection: Failed, pretty hard. These types of keycards can be cloned, and even emulated without problems.

Skill level: Easy. Pretty much plug and play.

Equipment: Cheap. Magic cards, NFC reader, rooted Android or Proxmark

Feasibility: High. Cards are easily cloned, even without the person carrying them noticing.

Attack 2

  • Keys cannot be derived by disassembling the lock

Protection: Moderate. As the key UID is stored in clear text inside the nonvolatile memory of the chip, it can be usually extracted once an attacker gets their fingers on the chip. There are ways to make it harder to extract data from a chip, for example disabling the programming and data interface, deleting all the memory once tampering or reprogramming is detected, or by providing special inaccessible memory (“secure enclaves”).

The chip we chose (Arduino Uno / Atmega328p) offers a moderate amount of protection: It can be configured in read-back protection mode. This mode requires a total erase of the chips content, which clears the protection but also deletes any stored secret keys.

These methods are only an arms race – at some point, you can cut open the chip and inspect its memory contents using a scanning electron microscope. Sure, that is expensive and complex, but not entirely impossible.

On other platforms, like PCs or smartphones, you don’t even have to got that far, since malware or even the operating system could just read the memory sections used by our program and steal the UID.

Skill level: Depends. Either open access, or requires expert tools and training.

Equipment: Depends. Cheap ISP programmer or expensive electron microscope

Feasibility: Depends. If no protection is used: High. With protection: Low.

Attack 3

  • Keys cannot be derived by eavesdropping on the wireless communication

Protection: Failed. The ID is transmitted in clear text, and anyone listening to the NFC wireless communication can receive it. However, since the ID can just be read by any reader anyway, there is little need to actually eavesdrop instead of just reading the ID.

Skill level: Advanced. Requires some RF experience.

Equipment: Moderate. AirSpy Mini SDR, Antenna, PC

Feasibility: Moderate. Depends on the arrangement of the reader and the range of the antenna.

Method 2: Challenge-Response

In our next iteration, we want to protect the system against cloning attacks, as they are the most commonly used easy attacks.All we need to do is not expose the ID / the secret key in any way to the outside world. But how do we know which keycard is the correct one if we cant compare the key? Enter: challenge-response based authentication.

Essentially, we need a way to communicate data that can be used to verify which card is the correct one, without an attacker being able to derive the secret key (which would be needed for cloning the card) from the data. The HMAC-SHA1 algorithm (a hash-based message authentication code using the SHA1 hashing function) can be used to perform this kind of exchange.

Now you might think, what, SHA1? Isn’t SHA1 insecure, in that it is possible to find collisions? For this I redirect you to this StackOverflow answer, which explains the topic way better that I ever could. Even though HMAC-SHA1 is still secure, you could just as well implement HMAC-SHA256 if you would want to.

Now, this kind of computation requires a more complex card than the simple data carrying ones used above. I prefer JavaCards, for example the Fidesmo card or any generic NXP card. These cards are able to be programmed, implementing whatever logic you want. They usually privide a contact interface (gold pads), and sometimes a NFC interface (internal antenna in contactless cards).

The Yubikey implements HMAC-SHA1 as well, which makes it ideal for this kind of application, even though its firmware is not public.

A selection of JavaCard-compatible cards and devices.

Both peers, the lock and the keycard, store the same secret key in their respective memory. When the keycard wants to authenticate with the lock, the lock sends a randomly generated challenge to the keycard, which then uses its stores secret key to derive a response from this challenge. This response can only be generated if the secret key is used, and the secret key cannot be derived from neither the challenge nor the response.

HMAC-SHA1 generation. From Wikipedia, (By Gdrooid – Own work, CC0, Attribution)

The lock performs the same operation using its copy of the secret key, and compares the received response with the one is computed. If the responses match, the secret keys must too and thus the keycard is validated.

Each time a new challenge is generated and used, which means that even if an attacker would listen to the communication, no useful information could be extracted. However, this requires a god random number generator. If the attacker knows which challenges are generated after e.g. a power outage or reboot, they could record responses and perform a replay attack without ever knowing the actual key.

I adapted code for JavaCards to implement this kind of functionality, you can find it at GitHub. It implements the Yubikey-style HMAC-SHA1 protocol via NFC.

Attack 1

  • Only the correct physical key unlocks the door
  • Keys cannot be cloned / copied

Protection: Good. Without knowing the secret key, the keycard cannot be cloned. This assumes that the keycard itself cannot be attacked in a similar manner as the reader chip above. Although the protocol has no option to read the secret key, a determined attacker could still somehow try to extract the key. However, the manufactures of secure tokes like YubiKeys or the NXP cards claim a very high degree of data security. Excluding maybe the electron microscope or some obscure power analysis attacks, there should be pretty much no way of extracting the key directly from the hardware.

Attack 2

  • Keys cannot be derived by disassembling the lock

Protection: Moderate. For the same reasons, see above. The key is still stored in clear text and can be extracted by malware or experts.

Attack 3

  • Keys cannot be derived by eavesdropping on the wireless communication

Protection: Perfect. The HMAC-SHA1 algorithm prevents deriving the key from public information (as of now). A good random number generator is required to prevent replay attacks.

Method 3: Challenge-Response, Without Storing the Key

The reader still has to store the secret key somewhere in its memory, making it vulnerable to data extraction (in theory). This is especially a concern when the system is implemented on a PC, where other programs might steal data. However, there is a way to authenticate the keycard using HMAC-SHA1 without ever knowing the secret key.

When a new key is enrolled, a random challenge is generated, and the response of the key is stored in runtime writable memory, together with this initial challenge. At the next authentication, the same challenge is sent again and the response is compared. Using the same challenge over and over again would make the system very vulnerable to replay attacks, where an attacker would just capture and send the same response again.

To prevent these replay attacks a new challenge is generated after each successful authentication. This new challenge is then sent to the keycard and the response is stored for the next time a keycard wants to authenticate.

You might have noticed that each challenge is used twice then, once when it is generated to receive the correct response, and again when the next authentication takes place. So while it reduces the frame in which a replay attack can be executed, it does not eliminate all possibilities.

This method is used by the PAM module provided by Yubico.

Attack 1

  • Only the correct physical key unlocks the door
  • Keys cannot be cloned / copied

Protection: Good, same as above.

Attack 2

  • Keys cannot be derived by disassembling the lock

Protection: Perfect. The key does not exist in the memory of the lock.

Attack 3

  • Keys cannot be derived by eavesdropping on the wireless communication

Protection: Moderate. Although the key cannot be derived, replay attacks are possible because each challenge is used twice.

Skill level: Advanced. Requires some RF and development experience.

Equipment: High. AirSpy Mini SDR, Antenna, PC, SmartCard development platform or SmartCard emulator, modified firmware

Feasibility: Moderate. Depends on the arrangement of the reader and the range of the antenna. In addition, modified firmware has to be provided, which sends a pre-defined response. However, remember, the source code is open-source and not that hard to modify. A combination of vsmartcard and jcardsim can be used to emulate a SmartCard, or a real SmartCard can be programmed.

A capture of a challenge-response exchange of a Yubikey 5 NFC. Captured using the Airspy Mini and antenna shown above.
Waiting for token...
Tag number: 1
Found token
Select OK
Challenge: 42:13:37:ca:fe:
Response: 23:b3:85:df:a3:10:6a:a4:46:14:3d:6d:1a:04:aa:c1:47:5e:46:ee:

As you can see, the challenge and response appear in clear text in the captured RF data.

// Example of just challenge-response with predefined slot
void simple_chalresp()
{
    uint8_t challenge[] = { 0x42, 0x13, 0x37, 0xCA, 0xFE };
    uint8_t response[RESP_BUF_SIZE] = { 0 };
    
    if(ykhmac_compute_hmac(SLOT_1, challenge, 5, response))
    {
        Serial.print("Challenge: ");
        print_array(challenge, 5);
        Serial.println();

        Serial.print("Response: ");
        print_array(response, RESP_BUF_SIZE);
        Serial.println();
    }
    else Serial.println("Challenge-response error");
}

For the rest of the code, see the GitHub project.

Method 4: Challenge-Response, Without Reusing Challenges but with Encrypted Keys

Unfortunately, in order to use each challenge only once, we have to store the secret key inside the lock. Otherwise, there would be no way of knowing if a given response is the correct one for a generated challenge. So, are we back at method 2, where we have to store the secret key in clear text?

It is possible to encrypt the stored secret key in a way such that is continues being usable without requiring a second encryption key. The PAM module by Eugene Crosser implements an algorithm I chose to adapt.

The secret key is encrypted using AES-128-CBC and stored next to a randomly generated challenge. However, the encryption key used is the response to this challenge using the secret key. Setting this kind of conundrum up is easier than it sounds, it requires the user to input the secret key, which is then encrypted using the computed response to a random challenge.

At the next keycard authentication, the stored challenge is used to receive a response from the keycard. This response is then used to decrypt the stored secret key. Whatever the result of this decryption is, is is used to compute a response to the used challenge. If the responses match, the key was decrypted correctly, which means that the response was correct, which means that the keycard carries the correct secret key.

The new response is then used to re-encrypt the secret key, and the new challenge is stored alongside it.

Attack 1

  • Only the correct physical key unlocks the door
  • Keys cannot be cloned / copied

Protection: Good, same as above.

Attack 2

  • Keys cannot be derived by disassembling the lock

Protection: Good. The key does exist on the lock, but only encrypted. The unencrypted key does exist in the RAM of the lock, but only for a very short time. On PCs and smartphones, RAM protection mechanisms can be used.

It is possible to generate the secret key when having access to the stored encrypted key, the stored challenge, and the keycard to compute the response. So an attacker would have to physically steal instead of copy the keycard as well – in addition to somehow extracting the memory of the lock.

If the keycard is e.g. implanted into a human, or otherwise very hard to steal without the use of considerable effort and force, the secret key is very securely stored.

Attack 3

  • Keys cannot be derived by eavesdropping on the wireless communication

Protection: Perfect. See above, but replay attacks are no longer possible.

Implementation

We arrive at the conclusion that HMAC-SHA1 challenge response using a AES-128 encrypted secret key with rolling codes is the way to go.

I implemented an Arduino project and an agnostic library, which is able to interface with Yubikeys and my vk-ykhmac applet. The library also handles the authentication flow discussed in method 4. Check out the code if you are interested. The library is written independent of frameworks or used hardware, and available on PlatformIO here.

All of the challenge-response methods here require some amount of persistent storage. While you could store everything in RAM, then the keycard would have to be re-enrolled every time the lock looses power. On the Arduino Uno, the persistent EEPROM is used for demonstration purposes.

Using a second factor, such as a PIN and a keypad would be desired. This factor can be used to further protect and encrypt the key.

In the next post, I will document how I built custom NFC hardware tokens.

Disclaimer

I am not a crypto expert, so do your own math and development if you plan to use any of this in a production environment. I made this purely as a learning exercise.

Sources and Further Reading

Banner image by TheDigitalWay from Pixabay

Leave a Reply

Your email address will not be published. Required fields are marked *