W tym poście chciałbym opisać działanie i odczyt danych UID zastosowaną w układach STM32.
Na poniższej tabelce widać, jakie funkcje bezpieczeństwa są udostępniane na poszczególnych rodzinach mikrokontrolerów.
Jest to numer, który jest nadawany każdemu układowi STM32. Składa się on z 96 bitów. Nadawany podczas produkcji.
Zastosowania to np. nadawanie numeru seryjnego, czy generowanie klucza.
W skład numeru UID wchodzą takie informacje jak:
- koordynaty x,y na waflu krzemowym w formacie BCD,
- numer wafla krzemowego,
- numer serii produkcyjnej,
Wartości umieszczone w UID mogą się powtarzać. Natomiast cała kombinacja, czyli 96 bitów, jest unikatowa dla każdego układu.
Nie wszystkie bity w UID mają zmienne wartości. Niektóre mają na stałe przypisane wartości '0'. Wynika to z faktu np. zakresu koordynatów X oraz Y wafla krzemowego. Nie udało im się jednak znaleźć konkretnej informacji które bity mają zawsze wartość zerową. Prawdopodobnie najprościej jest wybrać kilka sztuk procesorów i sprawdzić które wartości pozostają stałe, i je odrzucać w razie konieczności generowania danych na podstawie numerów ID.
Z dokumentu dla układów STM32H723/25 (link) można wyciągnąć następujące informacje:
Adres odczytu rozpoczyna się od 0x1FF1E800. Wszystkie bity pozwalają jedynie na odczyt. Niestety nie ma tu zbyt wielu przydatnych informacji dotyczących samej struktury tego numeru.
Dodatkowo są jeszcze trzy rejestry pozwalające na identyfikacje układu.
Rejestr z rozmiarem pamięci flash:
Odczyt UID:
Poniżej funkcje pozwalające na odczyt danych z biblitek HAL oraz LL.
- /**
- * @brief Return the first word of the unique device identifier (UID based on 96 bits)
- * @retval Device identifier
- */
- uint32_t HAL_GetUIDw0(void)
- {
- return(READ_REG(*((uint32_t *)UID_BASE)));
- }
- /**
- * @brief Return the second word of the unique device identifier (UID based on 96 bits)
- * @retval Device identifier
- */
- uint32_t HAL_GetUIDw1(void)
- {
- return(READ_REG(*((uint32_t *)(UID_BASE + 4U))));
- }
- /**
- * @brief Return the third word of the unique device identifier (UID based on 96 bits)
- * @retval Device identifier
- */
- uint32_t HAL_GetUIDw2(void)
- {
- return(READ_REG(*((uint32_t *)(UID_BASE + 8U))));
- }
Odczyt device ID:
- uint32_t HAL_GetDEVID(void)
- {
- return((DBGMCU->IDCODE) & IDCODE_DEVID_MASK);
- }
Odczyt revision ID:
- uint32_t HAL_GetREVID(void)
- {
- return((DBGMCU->IDCODE) >> 16);
- }
Pobranie rozmiaru flash:
- __STATIC_INLINE uint32_t LL_GetFlashSize(void)
- {
- return (uint16_t)(READ_REG(*((uint32_t *)FLASHSIZE_BASE_ADDRESS)));
- }
Pobranie package type:
- __STATIC_INLINE uint32_t LL_GetPackageType(void)
- {
- #if defined(SYSCFG_PKGR_PKG)
- return LL_SYSCFG_GetPackage();
- #else
- return (uint16_t)(READ_REG(*((uint32_t *)PACKAGE_BASE_ADDRESS)));
- #endif /* SYSCFG_PKGR_PKG */
- }
Zwracane dane dla układu STM32H725:
- #elif (STM32H7_DEV_ID == 0x483UL)
- #define LL_SYSCFG_VFQFPN68_INDUS_PACKAGE 0U
- #define LL_SYSCFG_TFBGA100_LQFP100_PACKAGE 1U
- #define LL_SYSCFG_LQFP100_INDUS_PACKAGE 2U
- #define LL_SYSCFG_TFBGA100_INDUS_PACKAGE 3U
- #define LL_SYSCFG_WLCSP115_INDUS_PACKAGE 4U
- #define LL_SYSCFG_LQFP144_PACKAGE 5U
- #define LL_SYSCFG_UFBGA144_PACKAGE 6U
- #define LL_SYSCFG_LQFP144_INDUS_PACKAGE 7U
- #define LL_SYSCFG_UFBGA169_INDUS_PACKAGE 8U
- #define LL_SYSCFG_UFBGA176PLUS25_INDUS_PACKAGE 9U
- #define LL_SYSCFG_LQFP176_INDUS_PACKAGE 10U
- #define LL_UTILS_PACKAGETYPE_VFQFPN68_INDUS LL_SYSCFG_VFQFPN68_INDUS_PACKAGE /*!< VFQFPN68 Industrial package type */
- #define LL_UTILS_PACKAGETYPE_TFBGA100_LQFP100 LL_SYSCFG_TFBGA100_LQFP100_PACKAGE /*!< TFBGA100 or LQFP100 Legacy package type */
- #define LL_UTILS_PACKAGETYPE_LQFP100_INDUS LL_SYSCFG_LQFP100_INDUS_PACKAGE /*!< LQFP100 Industrial package type */
- #define LL_UTILS_PACKAGETYPE_TFBGA100_INDUS LL_SYSCFG_TFBGA100_INDUS_PACKAGE /*!< TFBGA100 Industrial package type */
- #define LL_UTILS_PACKAGETYPE_WLCSP115_INDUS LL_SYSCFG_WLCSP115_INDUS_PACKAGE /*!< WLCSP115 Industrial package type */
- #define LL_UTILS_PACKAGETYPE_LQFP144 LL_SYSCFG_LQFP144_PACKAGE /*!< LQFP144 Legacy package type */
- #define LL_UTILS_PACKAGETYPE_UFBGA144 LL_SYSCFG_UFBGA144_PACKAGE /*!< UFBGA144 Legacy package type */
- #define LL_UTILS_PACKAGETYPE_LQFP144_INDUS LL_SYSCFG_LQFP144_INDUS_PACKAGE /*!< LQFP144 Industrial package type */
- #define LL_UTILS_PACKAGETYPE_UFBGA169_INDUS LL_SYSCFG_UFBGA169_INDUS_PACKAGE /*!< UFBGA169 Industrial package type */
- #define LL_UTILS_PACKAGETYPE_UFBGA176PLUS25_INDUS LL_SYSCFG_UFBGA176PLUS25_INDUS_PACKAGE /*!< UFBGA176+25 Industrial package type */
- #define LL_UTILS_PACKAGETYPE_LQFP176_INDUS LL_SYSCFG_LQFP176_INDUS_PACKAGE /*!< LQFP176 Industrial package type */
- #endif /* STM32H7_DEV_ID == 0x450UL */
Sprawdzę układ STM32H723. Jakie dane zostaną z niego odczytane:
Z informacji o rejestrze DBGMCU, można też określić że wartość DEVID odpowiada tej opisanej w dokumentacji czyli 0x483.
Po podłączeniu płytki do STMCubeProgrammer można odczytać te same dane:
REVID oraz DEVID:
Poniżej przedstawię jeszcze jak przygotować numer seryjny w oparciu o UID. Najprościej użyć UID w pełnej formie, czyli 96 bitów. Wykorzystanie go w takiej formie zapewni unikatowość numeru za każdym razem. Natomiast załóżmy, że potrzebujemy krótszego numeru, przyjmę, że numer seryjny będzie składał się z czterech bajtów.
Na początek XOR.
- uint32_t serial_xor(void) {
- return UIDw0 ^ UIDw1 ^ UIDw2;
- }
Powyższy przykład nie gwarantuje 100% pewności, że numer będzie unikatowy. Różne kombinacje danych na UID mogą spowodować wygenerowanie tego samego wyniku, po zredukowaniu do 32 bitów.
Drugim sposobem jest wykorzystanie CRC32 i/lub SHA256. Pierwszy jest szybszy do wdrożenia, natomiast drugi zapewnia większą pewność unikatowego generowania numeru. Natomiast gdy wybierzemy SHA256 to dostajemy 32 bajty danych, które należy następnie skrócić. W tym przypadku oba sposoby powinny jednak poprawnie spełnić swoje zadanie.
Wykorzystanie biblioteki mbedTLS do hashowania SHA256:
- volatile uint32_t UIDw0 = HAL_GetUIDw0();
- volatile uint32_t UIDw1 = HAL_GetUIDw1();
- volatile uint32_t UIDw2 = HAL_GetUIDw2();
- unsigned char input[30] = {0x00};
- size_t len = snprintf((char*)input, sizeof(input), "%" PRIu32 "%" PRIu32 "%" PRIu32, UIDw0, UIDw1, UIDw2);
- unsigned char output1[32] = {0x00};
- mbedtls_sha256(input, len, output1, 0);
- //output
- //221, 0, 29, 175, 93, 160, 245, 172, 187, 231, 53, 124, 181, 9, 94, 86, 31, 77, 122
- //133, 150, 112, 36, 113, 125, 72, 118, 203, 31, 148, 203, 241
Teraz wykonam crc32 na danych UID oraz na danych wyjściowych po SHA256:
- uint32_t crc32val = xcrc32(&output1[0], 32, 0xffffffff);
- uint32_t uid[3] = { UIDw0, UIDw1, UIDw2 };
- uint32_t crc32val2 = xcrc32((unsigned char*)uid, sizeof(uid), 0xFFFFFFFF);
- //crc32val - 0x50c9f202
- //crc32val2 - 0x96132cf4
Jak widać w obu przypadkach uda się uzyskać zadowalający efekt.