poniedziałek, 10 marca 2025

STM32 - Funkcje bezpieczeństwa - Device electonic signature

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. 


Unique ID:


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:


Rejestr identyfikacji linii układu:

Rejestr IDCODE:


W celu odczytania danych z tych rejestrów można posłużyć się funkcjami wbudowanymi.

Odczyt UID:

Poniżej funkcje pozwalające na odczyt danych z biblitek HAL oraz LL. 

  1. /**
  2.   * @brief  Return the first word of the unique device identifier (UID based on 96 bits)
  3.   * @retval Device identifier
  4.   */
  5. uint32_t HAL_GetUIDw0(void)
  6. {
  7.   return(READ_REG(*((uint32_t *)UID_BASE)));
  8. }
  9.  
  10. /**
  11.   * @brief  Return the second word of the unique device identifier (UID based on 96 bits)
  12.   * @retval Device identifier
  13.   */
  14. uint32_t HAL_GetUIDw1(void)
  15. {
  16.   return(READ_REG(*((uint32_t *)(UID_BASE + 4U))));
  17. }
  18.  
  19. /**
  20.   * @brief  Return the third word of the unique device identifier (UID based on 96 bits)
  21.   * @retval Device identifier
  22.   */
  23. uint32_t HAL_GetUIDw2(void)
  24. {
  25.   return(READ_REG(*((uint32_t *)(UID_BASE + 8U))));
  26. }

Odczyt device ID:

  1. uint32_t HAL_GetDEVID(void)
  2. {
  3.    return((DBGMCU->IDCODE) & IDCODE_DEVID_MASK);
  4. }

Odczyt revision ID:

  1. uint32_t HAL_GetREVID(void)
  2. {
  3.    return((DBGMCU->IDCODE) >> 16);
  4. }

Pobranie rozmiaru flash:

  1. __STATIC_INLINE uint32_t LL_GetFlashSize(void)
  2. {
  3.   return (uint16_t)(READ_REG(*((uint32_t *)FLASHSIZE_BASE_ADDRESS)));
  4. }

Pobranie package type:

  1. __STATIC_INLINE uint32_t LL_GetPackageType(void)
  2. {
  3. #if defined(SYSCFG_PKGR_PKG)
  4.  
  5.   return LL_SYSCFG_GetPackage();
  6. #else
  7.    return (uint16_t)(READ_REG(*((uint32_t *)PACKAGE_BASE_ADDRESS)));
  8.  
  9. #endif  /* SYSCFG_PKGR_PKG */
  10. }

Zwracane dane dla układu STM32H725:

  1. #elif (STM32H7_DEV_ID == 0x483UL)
  2.  
  3. #define LL_SYSCFG_VFQFPN68_INDUS_PACKAGE       0U
  4. #define LL_SYSCFG_TFBGA100_LQFP100_PACKAGE    1U
  5. #define LL_SYSCFG_LQFP100_INDUS_PACKAGE        2U
  6. #define LL_SYSCFG_TFBGA100_INDUS_PACKAGE       3U
  7. #define LL_SYSCFG_WLCSP115_INDUS_PACKAGE       4U
  8. #define LL_SYSCFG_LQFP144_PACKAGE             5U
  9. #define LL_SYSCFG_UFBGA144_PACKAGE            6U
  10. #define LL_SYSCFG_LQFP144_INDUS_PACKAGE        7U
  11. #define LL_SYSCFG_UFBGA169_INDUS_PACKAGE       8U
  12. #define LL_SYSCFG_UFBGA176PLUS25_INDUS_PACKAGE 9U
  13. #define LL_SYSCFG_LQFP176_INDUS_PACKAGE        10U
  14.  
  15. #define LL_UTILS_PACKAGETYPE_VFQFPN68_INDUS         LL_SYSCFG_VFQFPN68_INDUS_PACKAGE         /*!< VFQFPN68 Industrial package type */
  16. #define LL_UTILS_PACKAGETYPE_TFBGA100_LQFP100       LL_SYSCFG_TFBGA100_LQFP100_PACKAGE       /*!< TFBGA100 or LQFP100 Legacy package type */
  17. #define LL_UTILS_PACKAGETYPE_LQFP100_INDUS          LL_SYSCFG_LQFP100_INDUS_PACKAGE          /*!< LQFP100 Industrial package type  */
  18. #define LL_UTILS_PACKAGETYPE_TFBGA100_INDUS         LL_SYSCFG_TFBGA100_INDUS_PACKAGE         /*!< TFBGA100 Industrial package type */
  19. #define LL_UTILS_PACKAGETYPE_WLCSP115_INDUS         LL_SYSCFG_WLCSP115_INDUS_PACKAGE         /*!< WLCSP115 Industrial package type */
  20. #define LL_UTILS_PACKAGETYPE_LQFP144                LL_SYSCFG_LQFP144_PACKAGE                /*!< LQFP144 Legacy package type      */
  21. #define LL_UTILS_PACKAGETYPE_UFBGA144               LL_SYSCFG_UFBGA144_PACKAGE               /*!< UFBGA144 Legacy package type     */
  22. #define LL_UTILS_PACKAGETYPE_LQFP144_INDUS          LL_SYSCFG_LQFP144_INDUS_PACKAGE          /*!< LQFP144 Industrial package type  */
  23. #define LL_UTILS_PACKAGETYPE_UFBGA169_INDUS         LL_SYSCFG_UFBGA169_INDUS_PACKAGE         /*!< UFBGA169 Industrial package type */
  24. #define LL_UTILS_PACKAGETYPE_UFBGA176PLUS25_INDUS   LL_SYSCFG_UFBGA176PLUS25_INDUS_PACKAGE   /*!< UFBGA176+25 Industrial package type   */
  25. #define LL_UTILS_PACKAGETYPE_LQFP176_INDUS          LL_SYSCFG_LQFP176_INDUS_PACKAGE          /*!< LQFP176 Industrial package type  */
  26. #endif /* STM32H7_DEV_ID == 0x450UL */

Sprawdzę układ STM32H723. Jakie dane zostaną z niego odczytane:


REVID można sprawdzić w Errata (link):


Jak widać układ zamontowany na mojej płytce Nucleo jest z wersji Z. 

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.

  1. uint32_t serial_xor(void) {
  2.     return UIDw0 ^ UIDw1 ^ UIDw2;
  3. }

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:

  1. volatile uint32_t UIDw0 = HAL_GetUIDw0();
  2. volatile uint32_t UIDw1 = HAL_GetUIDw1();
  3. volatile uint32_t UIDw2 = HAL_GetUIDw2();
  4.  
  5. unsigned char input[30] = {0x00};
  6. size_t len = snprintf((char*)input, sizeof(input), "%" PRIu32 "%" PRIu32 "%" PRIu32, UIDw0, UIDw1, UIDw2);
  7. unsigned char output1[32] = {0x00};
  8. mbedtls_sha256(input, len, output1, 0);
  9.  
  10. //output
  11. //221, 0, 29, 175, 93, 160, 245, 172, 187, 231, 53, 124, 181, 9, 94, 86, 31, 77, 122
  12. //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:

  1. uint32_t crc32val = xcrc32(&output1[0], 32, 0xffffffff);
  2. uint32_t uid[3] = { UIDw0, UIDw1, UIDw2 };
  3. uint32_t crc32val2 = xcrc32((unsigned char*)uid, sizeof(uid), 0xFFFFFFFF);
  4.  
  5. //crc32val - 0x50c9f202
  6. //crc32val2 - 0x96132cf4

Jak widać w obu przypadkach uda się uzyskać zadowalający efekt.