單片機裡面的UID,他是幹什麼的你知道嗎?

沃愛單片機 發佈 2024-03-26T06:05:25.975311+00:00

今天跟大家帶來的知識不算難,現在非常多MCU都有全球唯一標識碼這個東西,可能大家都了解過,不過具體怎麼用並沒有實際設計過!

1、聊一聊

今天跟大家帶來的知識不算難,現在非常多MCU都有全球唯一標識碼這個東西,可能大家都了解過,不過具體怎麼用並沒有實際設計過!下面重點對其加密方面的應用跟大家理一理。

2、stm32的標識碼UID

對於目前大部分MCU都會存在一個唯一標識碼供用戶使用,同樣stm32也是一樣,通過查找對應的數據手冊便可以得到該唯一標識碼的具體信息。

這裡以stm32F103為例,其他型號的stm32性能也可能不存在該唯一標識,具體需要根據對應的數據手冊進行查閱,如果存在可能基地址稍有不同。如下圖所示:


分析一下:

  • 1 ) stm32的標識碼放在了唯一設備ID寄存器裡面,一共96個bit也就是12個字節且只能讀取。
  • 2 ) 通過手冊上的說明可以大致了解到該唯一標識碼的應用場景。
  • 3)一般的量產產品都會有一個設備的條碼,那麼這個唯一的標識碼便可以作為條碼的一部分來供查找。
  • 4 ) 在通信協議中該唯一標識碼可以作為一種標識序列號來進行設備的加載和區分。
  • 5 ) 當然最後就是把其作為一個安全密鑰,然後與軟體加密算法結合起來以降低固件被惡意複製的風險。

2、讀取UID

對於該唯一標識ID,bug菌這裡談兩點注意的:

1、唯一標識ID只是stm32裡面一種ID,其實一款晶片內部還有很多其他ID,比如設備ID和其他內部組件的ID等;

2、UID一共是96位具有唯一性,而截取中間的幾位不一定具有唯一性。

3、對於UID的讀取非常簡單,上面的手冊截圖也說明了,可以通過字節、半字和字來進行讀取,也就是說可以用8位、16位、32位來讀取。

參考Demo:

uint32_t  Unique[3] = {0};
uint8_t   Unique[12] = {0};

 int main(void)
 {        
     uint8_t i;

     Unique[0] = *(uint32_t*)(0x1FFFF7E8);
     Unique[1] = *(uint32_t*)(0x1FFFF7E8 + 4);
     Unique[2] = *(uint32_t*)(0x1FFFF7E8 + 8);
     printf("以uint32_t讀:\r\n");//插入換行
     printf("ID  0-31 :%x\r\n",Unique[0]);//插入換行
     printf("ID 32-63 :%x\r\n",Unique[1]);//插入換行
     printf("ID 64-95 :%x\r\n",Unique[2]);//插入換行

    for(i = 0 ;i < 12;i++)
     {
        unique[i] = *(uint8_t*)(0x1FFFF7E8 + i);
     }

     printf("以uint8_t讀:\r\n");//插入換行
    for(i = 0;i<12;i++)
    { 
       printf("ID byte%d :%x   ",i,unique[i]);//插入換行
       if(i%4 == 3) printf("\r\n");//插入換行 
    }
printf("\r\n公眾號:最後一個bug\r\n");//插入換行

輸出結果:

3、UID加密簡易版

之前bug菌整理過一篇單片機解密的文章<【整理】一文帶你了解"單片機解密"技術>,對於解密的辦法可以說是無比的殘忍,其實晶片的加密與解密跟網絡安全的攻防是一樣的。

所謂:"道高一尺魔高一丈",只有不斷的更新加密技術以增加解密成本或許在一定程度上能夠遏制不正規解密行為在,下面就先介紹一下UID的一種簡易加密方案,為什麼說簡易呢?可以說修改部分固件實現一個跳轉功能就解密了,不過對於一般的小型產品還是能夠在一定程度上起到保密效果的。

解釋兩句:

  • 1 )左邊是在晶片外部實現的,可以通過編寫一個上位機來自動生成密鑰並保存到晶片中的存儲介質中。
  • 2 )上面所說的UID與密鑰的隱式獲取是指 : 其讀取過程中的地址等信息不要顯式的暴露在固件中,比如上面提到的UID的地址或許是密鑰的地址,可以通過數據變換、運算等等進行隱藏。
  • 3 )最後一步如果密鑰不一致進行自動清除固件,該思想有點類似於我們平時密碼輸入,如果輸入次數較多就會鎖定無法解鎖,這樣對解密過程造成阻礙。

4、加強版本

對於上面的加密方法其關卡點就一個位置,如果在固件空白區域安插一些跳轉指令跳轉到正常運行的位置,你的固件就解密了。

所以目前比較常用的是對整個固件進行完整性標識序列與UID組合進行加密的辦法。

所以對於未使用的存儲區域最好是都填充完,避免被解密者利用。

4、另類版本

出於關卡點過於單一的問題考慮,我們需要進行多處的關卡點處理,同樣各個關卡點的複雜度也會給解密者帶來困難,每一處關卡點都帶有解密信息,相當於每次都會需要判斷機密,從而讓跳轉這種辦法失效。

最簡單的處理辦法就是定義一些宏處理,比如:

#define Sect_TURE  (UIDCODE - SAVECODE  + 1)
#define Sect_FALSE (UIDCODE - SAVECODE)
#define Sect_NUM_1 (UIDCODE - SAVECODE  + 1)
#define Sect_NUM_2 (UIDCODE - SAVECODE  + 2)


當然不僅僅只有上面的處理,我們還可以通過替換為其他變量來隱藏一些處理辦法,從而達到迷惑解密著的目的。這樣我們就可以把加密分布到程序的各個角落,加強了固件的安全性。

不過這樣的辦法如果放在訪問較為頻繁的位置,勢必會影響系統的性能,如果所使用的晶片性能一般,可以選擇部分關鍵關卡點處理。

好吧,所以一切安全的前提是唯一標識碼UID無法被修改,否則也是徒勞,不過既然晶片都是人造的,那肯定就有辦法進行解密,只是成本問題。

5、最後小結

本文到這裡就結束了,對於MCU的加密和解密是一個永恆的話題,同樣對於一個成熟的產品加密也是必須要考慮的技術問題,看看大家還有什麼好的MCU加密辦法,歡迎大家分享留言討論!

關鍵字: