Python CRC calculation for the NTAG 424 NFC chip

Viewed 444

I am currently working with the NXP NTAG 424 chips, which feature AES-128 encryption.

This chip requires the computation of a crc32 checkvalue whenever a new key is set (see the datasheet 11.6.1/Page 67). According to the data sheet, the crc is "computed according to IEEE Std802.3-2008". The application note (6.16.1/page 39) even gives an example for this :

new_key: F3847D627727ED3BC9C4CC050489B966
CRC32(new_key): 789DFADC

However when I try to replicate the results using python and the binascii crc32 library, the result is different:

>>> from binascii import unhexlify, crc32
>>> new_key = unhexlify('F3847D627727ED3BC9C4CC050489B966')
>>> print(hex(crc32(new_key)))
0x23056287     # Not the CRC I was looking for

This document often reverses byte order, however the command

>>> print(hex(crc32(new_key[::-1])))
0x9453faa7

also brings no joy.

So the question is: What am I doing wrong? I tried consulting the cited standard, but with my superficial knowledge I could not spot any difference between the standard crc32 and the algorithm cited in the IEEE standard.

1 Answers

The example's CRC is the COMPLEMENT of the 32-bit CRC that IEEE Std802.3 (or equivalently CCITT V.42) specifies, which I detail there with reference. This is another case of using a variant of a standard CRC in this tag: like all ISO/IEC 14443 type A tags, its error detection (up to 848 kbit/s) uses a variant of the 16-bit CRC that IEEE Std802.3 (or equivalently CCITT V.42) specifies, with different initial value and lacking the final complement.

Here is self-contained code that matches the question's test vector:

# compute the CRC32 for NTAG424
# Ethernet / CCITT V42 CRC32, less final complement
def NTAG424CRC(m):
    c = 0xFFFFFFFF
    for b in m:
         c ^= b;
         for n in range(8):
             c = (c>>1)^(0xEDB88320&-(c&1))
#   c ^= 0xFFFFFFFF  # required by Ethernet / CCITT V42
    return c.to_bytes(4,'little')

# demo, expected value 789dfadc
print(NTAG424CRC(bytearray.fromhex('F3847D627727ED3BC9C4CC050489B966')).hex())

Try it online!

What am I doing wrong?

  • Trusting that the manufacturer faithfully implemented its own specification.
  • Expecting the output of binascii.crc32 to be big-endian; it's little-endian, and must be so in telecom to preserve the burst-error detection property of CRC32. It is an oversight in the specification of binascii.crc32 to have an int as output, when using a bytearray like its input would make endianess error next to impossible.

We can and probably should use binascii.crc32, which is native and uses a pre-computed table, thus is much faster (but may be more susceptible to cache-related side-channel attacks).

import binascii

# compute the CRC32 for NTAG424 using binascii.crc32
# Ethernet / CCITT V42 CRC32, less final complement
def NTAG424CRC(m):
    return (binascii.crc32(m)&0xFFFFFFFF^0xFFFFFFFF).to_bytes(4,'little')
# &0xFFFFFFFF deals with negative output; it can be removed under Python 3
# ^0xFFFFFFFF undoes the complement rightly done by binascii.crc32

# demo, expected value 789dfadc
print(NTAG424CRC(bytearray.fromhex('F3847D627727ED3BC9C4CC050489B966')).hex())

Try it online!

Related