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:
- 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)
- 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:
- Does not transmit the password/secret key in clear text
- Create a likely random (different for each access) session key for ensuring the reader and the tag are what they pretend
- 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:
- Ultralight C allow authentication, thus preventing MITM capture of password with a NFC probe (if used, obviously, else there's no advantage)
- 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.
- 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.
- 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.
- 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.