I have an (university) project where I basically write and read text out of NFC Tags with Android devices in order to store one's balance in the card (wich can be used on the cafeteria, for example).

Right now, I'm using Ntag213 doing the below code:

NdefRecord mimeRecord = NdefRecord.createMime("text/plain", messageEncrypted.getBytes(Charset.forName("US-ASCII")));
ndef.writeNdefMessage(new NdefMessage(mimeRecord));

As you can notice, I'm using application level encryption to encrypt the message (messageEncrypted) before writing it to the tag (AES-256 encrypt with 'com.scottyab:aescrypt:0.0.1' library - with a very big password key which is also combined with each tag UUID as part of it).

So far so good - Only I can understand the data on the tags.

On my research, I've found that when it comes to security Ultralight C > Ntag213.

**I know both tags have limited space but it is more than enough for me.

Question 1) When using application level encryption, why (is it?) is Ultralight C safer then Ntag213?

Question 2) I'm pretty sure I can guarantee security using AES encryption on application layer, but I don't want people (besides me) messing with the stored data (formatting tag or writing info there - even if I can dettect it). I see that the only way of preventing that (please, correct me if I'm wrong) is to set a password for the tag. However, both Ntag213 and Ultralight C have only an 32 bit password. Is it good enough? Is there another way of preventing someone (besides me) writing data?

Question 3) Which other security measures can I use on such tags to enforce security (tag and application layer)? I see that Ultralight C has 3DES Authentication, but I haven't find an example for Android so far.

Question 4) When you compare tag security (Mifare Desfire > Ultralight > Ntag213 > Mifare Classic), what is really being compared? The ease of one cracking the (native tag's) encryption or the ease of one store (anything) on the tag without permission ?

Question 5) I see a bunch of other techs (Mifare Desfire, ICODE SLIX, Infineon Cipurse) that are more secure, which makes me wonder if the tech I'm using (Ntag213 or Ultralight C) is good enough for storing someones balance. Would you (and that's a personal opinion) say that Ntag213 with application level encryption and 32 bit password good enough for this type of application? And how long would it take someone to actually break its security?

  • NTAG213 with 32-bit password combined with an AUTH_LIM not enough?
    – daparic
    Apr 12, 2021 at 17:51

1 Answer 1


With your code, an attacker will not be able to forge a valid message for your system. Yet she can still copy a (valid) message you've created and paste it on any other tag or restore to a previous state.

This means that this is possible:

  1. Restore the account balance (if it's what stored on the tag) to what was before the purchase (simply by copy the tag before purchase and restore after purchase)
  2. Share with someone else account (if you leave your tag unattended, some attacker can copy it and paste it on another tag, impersonnating you for further purchase).

Please notice that even if you are checking the balance online with a database (per-tag) to limit case 1, you can't prevent case 2 with your scheme.

The 32bits password protection only prevents from modifying the content of the tag unless you know the password (since I doubt you'll use the password to set a "no-access" zone), but anyone with a simple $100 tool can capture the password when operating a valid tag since it's transmitted in clear (MITM here), so it's useless as I understand your need.

To prevent the kind of abuse above, you need a way that:

  1. Does not transmit the password/secret key in clear text
  2. Create a likely random (different for each access) session key for ensuring the reader and the tag are what they pretend
  3. Use that session key to access / modify data on the tag

This is only possible with Mifare Ultralight C (or DESFire) tags (don't use Mifare Classic as their crypto is broken).

Typically, with such tag, you'll use the tag unique identifier to create a per-tag, per-sytem secret key. With this key, you'll authenticate with a block on the tag and store the balance/message encrypted with another key you're deriving from the tag UID. You should also rotate the keys, if possible after each authentication.

If authentication is valid, you're sure that the tag isn't cloned, and wasn't restored from a previous state.

So to make a clear diagram of the process:

  let M = message to store on tag
  let transaction = the current transaction counter you need to store in a database
  let Secret1 = some secret random value for your system (16 bytes for 3DES key). Depending on your security need, you might need to derive this secret from the reader's unique identifier.
  let Secret2 = some another secret random value for your system (16 bytes for AES key)

  When a tag is enrolled:
    let UID = tag UID
    transaction = 0
    let authKey = Hash(UID, Secret1, transaction)
    let msgKey = AES_CTR(M, Hash(UID, Secret2, transaction))
    change KeyA from default to authKey
    authenticate with authKey on some sector
    write msgKey
    store (UID, transaction) in your database

  When the tag is used:
    let UID = tag UID
    let authKey = Hash(UID, Secret1, transaction)
    authenticate with authKey on same sector
    if (authencation failed) reject tag or kill it, exit
    read msgKey from the sector's data
    extract M = AES_CTR(msgKey, Hash(UID, Secret2, transaction))
    if (M isn't good) reject tag or kill it, exit
    Perform your system's intended action, this gives M'
    increment transaction
    compute msgKey = AES_CTR(M', Hash(UID, Secret2, transaction))
    write msgKey to sector
    compute authKey = Hash(UID, Secret1, transaction)
    change authentication's key with new authKey
    update database's transaction count.

If you don't have a database (on the reader, or if the system is not online), you can't prevent tag copy and restore since the attacker could use a NFC tool to emulate a Ultralight C card and capture the content of the block when enrolling (even through they can't understand what's inside). This is high tech so depending on the value of the assets you can to protect, it might not make sense.

There is some feature with Mifare where you can store a value in a block and you can instruct the tag to increment or decrement it if you have the correct key. It's trivial to implement this with the above pseudo-code, but this only work if the tag is storing a balance (understand: not for a message).

Now that you've the overview, let's answer your questions:

  1. Ultralight C allow authentication, thus preventing MITM capture of password with a NFC probe (if used, obviously, else there's no advantage)
  2. Password is moot. Unless you can control all your readers, that is, place the tag in a sliding tray, and slide it so it's read inside the machine with no space around for electronic gizmo, it's useless.
  3. Authentication is required. Without a way to derive a secret session key, there is no protection from a MITM. For android, I don't know.
  4. It's like comparing a truck with a transparent plastic bag. Both can store apples. Yet, the former is more expensive but you can do more things with it, like putting more stuff in it, locking the doors, etc... If you don't care about people seeing the apples and adding/removing them, use the plastic bag, it's cheaper.
  5. There is no real advantage for your system to use DESFire or ICode here. You actually don't need additional storage space, and 3DES is secure enough to protect a cafeteria balance here. There is no economical sense to pay hundred if not thousand of dollars of computing power to try to break the $20 balance on the student cafeteria's account.

From a usability point of view, you can use an Android phone as a ISO14443-4 NFC tag (with random UID, so my scheme above needs to be modified). DESFire is also ISO14443-4 compliant so if you go the Android's way of doing NFC, you can still sell and use DESFire tag for those without such phone & function.

Again, this requires a lot of development to get right, and I can't tell if it makes sense for you compared to a relatively simple Ultralight C system or a web based application for paying.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .