4

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.

U_flow
  • 475
  • 5
  • 18

1 Answers1

4

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!

fgrieu
  • 2,724
  • 1
  • 23
  • 53
  • 1
    I just tried it with the NTAG 424, it works as expected, but only if the authentication happens directly before you try to change the key (at least for me, that may or may not be either an issue with my command counter increment (although it works good for all my other commands) or another undocumented "feature" of the NTAG 424). But thanks for the solution including the binascii.crc32 call, that one works beautifully! – U_flow Mar 17 '20 at 11:35
  • "What am I doing wrong? Trusting that the manufacturer faithfully implemented its own specification." - I had a good laugh=) quite true. also, great answer, thx! – oberstet May 12 '21 at 11:28