Skip to content

Instantly share code, notes, and snippets.

@atc1441
Last active March 29, 2024 15:47
Show Gist options
  • Star 69 You must be signed in to star a gist
  • Fork 11 You must be signed in to fork a gist
  • Save atc1441/41af75048e4c22af1f5f0d4c1d94bb56 to your computer and use it in GitHub Desktop.
Save atc1441/41af75048e4c22af1f5f0d4c1d94bb56 to your computer and use it in GitHub Desktop.
#include <stdio.h>
#include <stdint.h>
// Philips Sonicare NFC Head Password calculation by @atc1441 Video manual: https://www.youtube.com/watch?v=EPytrn8i8sc
uint16_t CRC16(uint16_t crc, uint8_t *buffer, int len) // Default CRC16 Algo
{
while(len--)
{
crc ^= *buffer++ << 8;
int bits = 0;
do
{
if ( (crc & 0x8000) != 0 )
crc = (2 * crc) ^ 0x1021;
else
crc *= 2;
}
while ( ++bits < 8 );
}
return crc;
}
uint8_t nfctag_uid[] = {0x04,0xEC,0xFC,0xA2,0x94,0x10,0x90}; // NTAG UID
uint8_t nfc_second[] = "221214 12K"; // Head MFG String, printed on Head and at memory location 0x23
int main()
{
uint32_t crc_calc = CRC16(0x49A3, nfctag_uid, 7); // Calculate the NTAG UID CRC
crc_calc = crc_calc | (CRC16(crc_calc, nfc_second, 10) << 16); // Calculate the MFG CRC
crc_calc = (crc_calc >> 8) & 0x00FF00FF | (crc_calc << 8) & 0xFF00FF00; // Rotate the uin16_t bytes
printf("by @ATC1441 NFC CRC : 0x%08X expected: 0x61F0A50F\r\n", crc_calc);// Print out the calucated password
return 0;
}
@JKamsker
Copy link

JKamsker commented Jun 13, 2023

Csharp version (LinqPad):

var crcExpected = "0x61F0A50F";
var crcActual = SonicarePasswordGenerator.ComputePassword(new byte[] { 0x04, 0xEC, 0xFC, 0xA2, 0x94, 0x10, 0x90 }, "221214 12K");

Console.WriteLine($"by @ATC1441 NFC CRC : {crcActual} expected: {crcExpected}.  Check: {crcActual == crcExpected}"); // Print out the calculated password

public class SonicarePasswordGenerator
{
	static ushort CRC16(ushort crc, byte[] buffer) // Default CRC16 Algo
	{
		for (int i = 0; i < buffer.Length; i++)
		{
			crc ^= (ushort)(buffer[i] << 8);
			for (int j = 0; j < 8; j++)
			{
				if ((crc & 0x8000) != 0)
					crc = (ushort)((2 * crc) ^ 0x1021);
				else
					crc *= 2;
			}
		}
		return crc;
	}

	public static string ComputePassword(byte[] uid, string nfc_second)
	{
		uint crc_calc = CRC16(0x49A3, uid); // Calculate the NTAG UID CRC

		byte[] nfc_second_bytes = System.Text.Encoding.UTF8.GetBytes(nfc_second); // Head MFG String, printed on Head and at memory location 0x23

		crc_calc = crc_calc | ((uint)CRC16((ushort)crc_calc, nfc_second_bytes) << 16); // Calculate the MFG CRC

		crc_calc = (crc_calc >> 8) & 0x00FF00FF | (crc_calc << 8) & 0xFF00FF00; // Rotate the uin16_t bytes

		return $"0x{crc_calc:X8}";
	}
}

@older
Copy link

older commented Jun 16, 2023

@JKamsker I'm getting the following output from your C# version:

by @atc1441 NFC CRC : 0x0000A50F expected: 0x61F0A50F

Not as expected?

@JKamsker
Copy link

JKamsker commented Jun 16, 2023

@older Oh! That might explain some things xD

Updated my answer

@Teraskull
Copy link

Python version (Tested on 3.9):

def crc16(crc: int, buffer: list[int]) -> int:
    """ Default CRC16 Algo. """
    for byte in buffer:
        crc ^= byte << 8
        for _ in range(8):
            if crc & 0x8000:
                crc = (crc << 1) ^ 0x1021
            else:
                crc <<= 1
        crc &= 0xFFFF
    return crc


def compute_password(uid: list[int], nfc_second: str) -> str:
    crc_calc = crc16(0x49A3, uid)  # Calculate the NTAG UID CRC.
    nfc_second_bytes = nfc_second.encode("utf-8")  # Convert nfc_second to bytes.
    crc_calc |= crc16(crc_calc, nfc_second_bytes) << 16  # Calculate the MFG CRC and combine with NTAG UID CRC.
    crc_calc = ((crc_calc >> 8) & 0x00FF00FF) | ((crc_calc << 8) & 0xFF00FF00)  # Rotate the bytes.
    return f"0x{crc_calc:08X}"


uid = [0x04, 0xEC, 0xFC, 0xA2, 0x94, 0x10, 0x90]  # NTAG UID.
nfc_second = "221214 12K"  # Head MFG String, printed on Head and at memory location 0x23.

crc_expected = "0x61F0A50F"
crc_actual = compute_password(uid, nfc_second)

print(f"by @ATC1441 NFC CRC : {crc_actual} expected: {crc_expected}. Check: {crc_actual == crc_expected}")  # Print out the calculated password.

Output:

by @ATC1441 NFC CRC : 0x61F0A50F expected: 0x61F0A50F. Check: True

@nicjes
Copy link

nicjes commented Jun 22, 2023

JavaScript version:

function crc16(crc, buffer) {
    for (let byte of buffer) {
        crc ^= BigInt(byte) << BigInt(8);
        for (let i = 0; i < 8; i++) {
            if (crc & BigInt(0x8000)) {
                crc = (crc << BigInt(1)) ^ BigInt(0x1021);
            } else {
                crc <<= BigInt(1);
            }
            crc &= BigInt(0xFFFF);
        }
    }
    return crc;
}

function generatePassword(uid, mfg) {
    let crc = crc16(BigInt(0x49A3), uid); // Calculate the UID CRC
    crc = crc | crc16(crc, mfg) << BigInt(16); // Calculate the MFG CRC
    crc = ((crc >> BigInt(8)) & BigInt(0x00FF00FF)) | ((crc << BigInt(8)) & BigInt(0xFF00FF00)); // Rotate the bytes

    let password = crc.toString(16).toUpperCase().replace(/(..)(..)(..)(..)/g, '$1:$2:$3:$4'); // Format the password
    return password;
}

I've implemented it in a little web app.

@filipsworks
Copy link

More beefy python version with built in command generator and ability to set # of sessions left:
https://github.com/filipsworks/SoniKraker

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment