4.1.4.1.5 Signatures
Authorization and Signature provides the target chip signature function to prevent the programmed firmware from being directly copied and used. PowerWriter provides two signature algorithms, one is ICWKEY signature authorization and the other is Matrix signature. ICWKEY is ECDSA digital signature algorithm, an asymmetric signature algorithm, with high encryption strength but large resource consumption, and Matrix is random matrix signature, a simple algorithm with low resource consumption. Matrix is a random matrix signature with simple algorithm and low resource consumption.
4.1.4.1.5.1 Online Signature
Semiconductor Manufacturing provides online signature service, please refer to Semiconductor Manufacturing Licensing Centre help file for details. Help Centre- (icworkshop.com)。
The online authorization configuration method is the same as the PowerWriter client, but the method of use requires the use of the ChipWorks client for programming the target chip, which can only use the online programming mode, programming one chip at a time, and is commonly used for development and delivery of samples.
4.1.4.1.5.2 Matrix
PowerWriter incorporates an offline UID authorization mechanism based on a random matrix algorithm. The configuration of the offline authorization interface is illustrated in the figure below.
Standard configuration for offline authorization includes the following:
- Key Address: The key address refers to the memory location where the authorization information is stored. By default, it is set to the position at Flash capacity of the chip minus 12 bytes. The figure above shows the default storage address for the STM32F071CB.
- User Password Length: This field allows you to specify the length of the user-defined password. The default is 12 bytes, with optional lengths of 4 bytes or 8 bytes. In future updates, this feature will be upgraded to automatically match the UID length of the chip.
- Data Storage Mode: The data storage mode can be configured as either Little Endian or Big Endian.
- User Password (3 User Passwords): Up to three different user passwords can be configured based on the settings.
- Matrix Encoding: Matrix encoding defines the encryption matrix that users can edit for offline authorization. It is illustrated in the figure below:
PowerWriter Offline Authorization Random Matrix Overview.
UID Encryption Algorithm Editor / Code Preview Area:
This area provides functions for code preview and quick code editing. Whether generating code randomly or manually modifying the algorithm matrix, the result will be displayed in real time in this area. For manual editing of the algorithm matrix, please refer to the operation guide in the Custom UID Modification Instructions.
Random Generation:
This function generates a random matrix array automatically.
Code Check:
This feature validates the strength and integrity of the current matrix array.
Export Source Code:
The configured settings can be exported as source code (e.g., MDK example project).
Generated Project Files (as shown in the figure):
- cortex_chipid_binding.c:Chip UID Signature Source File.The following section describes the code structure within cortex_chipid_binding.
Junk Instructions (Obfuscation) :By enabling junk instructions, additional unrelated code is inserted into the original matrix. This makes reverse-engineered output differ from the expected result, increasing analysis difficulty. This method is especially effective for chips with only two protection levels (Level 0 and Level 1). You can enable or disable this feature using the provided switch.
#define ENABLE_JUNK_CODE 1 //Enable/Disable junk code
#define ENABLE_JUNK_CODE 1 //Enable/Disable junk code
#if ENABLE_JUNK_CODE
static char mJunkCodeVar[UID_KEY_LENGTH]; //junkCode for mix
//The following code may warn in KEIL(MDK), ignore it
static void ChipUIDAlgo(char pUserID[], char pChipID[], char pKey[])
{
pKey[0] = pUserID[5] + pChipID[7] & pUserID[3] | pChipID[6] ;
mJunkCodeVar[2] = pChipID[4] ^ pChipID[9] - pKey[11] ^ pKey[4] ;//junk code,Your can remove or add your's junk code
mJunkCodeVar[10] = mJunkCodeVar[11] + pUserID[0] * pUserID[1] & pChipID[8] ;//junk code,Your can remove or add your's junk code
pKey[1] = pChipID[5] * pChipID[9] - pChipID[2] ^ pChipID[0] ;
pKey[2] = pUserID[4] ^ pChipID[4] - pUserID[1] * pUserID[8] ;
mJunkCodeVar[11] = pKey[3] | pChipID[10] | mJunkCodeVar[2] + pKey[2] ;//junk code,Your can remove or add your's junk code
mJunkCodeVar[2] = pUserID[7] & pChipID[1] | mJunkCodeVar[9] + pKey[8] ;//junk code,Your can remove or add your's junk code
mJunkCodeVar[10] = pUserID[9] & pUserID[7] * mJunkCodeVar[5] | pUserID[11] ;//junk code,Your can remove or add your's junk code
pKey[3] = pUserID[10] & pUserID[7] + pUserID[2] | pChipID[1] ;
mJunkCodeVar[3] = pChipID[1] | mJunkCodeVar[3] + pChipID[0] & pUserID[3] ;//junk code,Your can remove or add your's junk code
mJunkCodeVar[2] = mJunkCodeVar[5] & pKey[8] | mJunkCodeVar[7] & pUserID[3] ;//junk code,Your can remove or add your's junk code
mJunkCodeVar[8] = pKey[4] ^ pKey[8] * pChipID[8] * mJunkCodeVar[8] ;//junk code,Your can remove or add your's junk code
pKey[4] = pChipID[11] & pUserID[0] + pChipID[8] - pUserID[9] ;
mJunkCodeVar[11] = pChipID[7] ^ mJunkCodeVar[6] + mJunkCodeVar[10] | pUserID[6] ;//junk code,Your can remove or add your's junk code
mJunkCodeVar[3] = pUserID[5] + mJunkCodeVar[4] - mJunkCodeVar[2] ^ pChipID[11] ;//junk code,Your can remove or add your's junk code
mJunkCodeVar[2] = mJunkCodeVar[2] | pUserID[4] ^ mJunkCodeVar[9] + pKey[1] ;//junk code,Your can remove or add your's junk code
pKey[5] = pUserID[6] * pChipID[10] ^ pChipID[3] | pUserID[11] ;
mJunkCodeVar[5] = pKey[4] * pKey[4] + pChipID[3] ^ pUserID[1] ;//junk code,Your can remove or add your's junk code
pKey[6] = pUserID[0] - pChipID[1] + pUserID[10] | pUserID[2] ;
mJunkCodeVar[10] = pChipID[11] * pKey[11] * pUserID[1] + mJunkCodeVar[6] ;//junk code,Your can remove or add your's junk code
pKey[7] = pUserID[11] * pUserID[1] ^ pUserID[9] & pChipID[4] ;
mJunkCodeVar[10] = pKey[4] - pKey[8] & pUserID[5] | pUserID[2] ;//junk code,Your can remove or add your's junk code
mJunkCodeVar[0] = pChipID[7] ^ pKey[3] - pChipID[4] ^ pChipID[0] ;//junk code,Your can remove or add your's junk code
mJunkCodeVar[1] = pChipID[5] ^ mJunkCodeVar[7] | pKey[7] - pKey[8] ;//junk code,Your can remove or add your's junk code
pKey[8] = pChipID[9] & pUserID[6] | pChipID[2] + pChipID[7] ;
mJunkCodeVar[4] = mJunkCodeVar[5] | mJunkCodeVar[0] | pKey[4] + pChipID[7] ;//junk code,Your can remove or add your's junk code
mJunkCodeVar[8] = pUserID[1] + pUserID[2] + pKey[10] - mJunkCodeVar[7] ;//junk code,Your can remove or add your's junk code
pKey[9] = pUserID[7] - pUserID[4] * pChipID[6] ^ pChipID[11] ;
pKey[10] = pChipID[3] ^ pUserID[3] * pChipID[8] - pUserID[8] ;
mJunkCodeVar[0] = pUserID[6] | pUserID[4] - mJunkCodeVar[2] + pUserID[8] ;//junk code,Your can remove or add your's junk code
mJunkCodeVar[11] = mJunkCodeVar[5] + pKey[0] ^ mJunkCodeVar[7] | pChipID[11] ;//junk code,Your can remove or add your's junk code
mJunkCodeVar[9] = pChipID[11] | pUserID[1] & pChipID[2] + pChipID[8] ;//junk code,Your can remove or add your's junk code
pKey[11] = pUserID[5] & pChipID[10] | pChipID[5] + pChipID[0] ;
mJunkCodeVar[0] = pKey[8] + pKey[0] ^ pUserID[9] + pKey[0] ;//junk code,Your can remove or add your's junk code
mJunkCodeVar[9] = mJunkCodeVar[0] & pUserID[11] + pChipID[8] + pKey[7] ;//junk code,Your can remove or add your's junk code
}
#else
//The following code may warn in KEIL(MDK), ignore it
static void ChipUIDAlgo(char pUserID[], char pChipID[], char pKey[])
{
pKey[0] = pUserID[5] + pChipID[7] & pUserID[3] | pChipID[6] ;
pKey[1] = pChipID[5] * pChipID[9] - pChipID[2] ^ pChipID[0] ;
pKey[2] = pUserID[4] ^ pChipID[4] - pUserID[1] * pUserID[8] ;
pKey[3] = pUserID[10] & pUserID[7] + pUserID[2] | pChipID[1] ;
pKey[4] = pChipID[11] & pUserID[0] + pChipID[8] - pUserID[9] ;
pKey[5] = pUserID[6] * pChipID[10] ^ pChipID[3] | pUserID[11] ;
pKey[6] = pUserID[0] - pChipID[1] + pUserID[10] | pUserID[2] ;
pKey[7] = pUserID[11] * pUserID[1] ^ pUserID[9] & pChipID[4] ;
pKey[8] = pChipID[9] & pUserID[6] | pChipID[2] + pChipID[7] ;
pKey[9] = pUserID[7] - pUserID[4] * pChipID[6] ^ pChipID[11] ;
pKey[10] = pChipID[3] ^ pUserID[3] * pChipID[8] - pUserID[8] ;
pKey[11] = pUserID[5] & pChipID[10] | pChipID[5] + pChipID[0] ;
}
#endif
U32DataEndiaSwap:32-bit Data Endianness Conversion Function
/**
* @brief Data Endian Swap in uint32
* @note Please keep the burner and project consistent, do not modify it
* @retval None
*/
void U32DataEndiaSwap(uint32_t * pBuffer, uint32_t size)
{
int i;
for(i = 0; i < size; i ++){
pBuffer[i] = BigLittleSwap32(pBuffer[i]);
}
}
Exported Variables:Including signature information, etc.
/* store full chipID */
static char mChipID[UID_CHIP_SIZE];
/* initial chip id */
static uint32_t mChipAddr; //global
/* calc key compare with mKey */
static char mCalcKey[UID_KEY_LENGTH];
/* store full userID */
static char mUserID[] = {
#if (UID_USERID_LENGTH == 4)
(UID_USERID_KEY1 & 0xff),((UID_USERID_KEY1>>8) & 0xff),((UID_USERID_KEY1>>16) & 0xff),((UID_USERID_KEY1>>24) & 0xff),
#elif (UID_USERID_LENGTH == 8)
(UID_USERID_KEY1 & 0xff),((UID_USERID_KEY1>>8) & 0xff),((UID_USERID_KEY1>>16) & 0xff),((UID_USERID_KEY1>>24) & 0xff),
(UID_USERID_KEY2 & 0xff),((UID_USERID_KEY2>>8) & 0xff),((UID_USERID_KEY2>>16) & 0xff),((UID_USERID_KEY2>>24) & 0xff)
#else
(UID_USERID_KEY1 & 0xff),((UID_USERID_KEY1>>8) & 0xff),((UID_USERID_KEY1>>16) & 0xff),((UID_USERID_KEY1>>24) & 0xff),
(UID_USERID_KEY2 & 0xff),((UID_USERID_KEY2>>8) & 0xff),((UID_USERID_KEY2>>16) & 0xff),((UID_USERID_KEY2>>24) & 0xff),
(UID_USERID_KEY3 & 0xff),((UID_USERID_KEY3>>8) & 0xff),((UID_USERID_KEY3>>16) & 0xff),((UID_USERID_KEY3>>24) & 0xff)
#endif
};
/* Store the key calculated by the burner */
#if UID_KEYADDR_PLACEHOLDER_EN
const static char mKey[UID_KEY_LENGTH] __attribute__((section(UID_KEYADDR_INNER)))
#if UID_ENABLE_TAG
=
{
#if (UID_KEY_LENGTH == 4)
UID_KEY_TAG & 0xff,(UID_KEY_TAG >> 8) & 0xff,(UID_KEY_TAG >> 16) & 0xff,(UID_KEY_TAG >> 24) & 0xff
#elif (UID_KEY_LENGTH == 8)
UID_KEY_TAG & 0xff,(UID_KEY_TAG >> 8) & 0xff,(UID_KEY_TAG >> 16) & 0xff,(UID_KEY_TAG >> 24) & 0xff,
UID_KEY_TAG & 0xff,(UID_KEY_TAG >> 8) & 0xff,(UID_KEY_TAG >> 16) & 0xff,(UID_KEY_TAG >> 24) & 0xff
#else
UID_KEY_TAG & 0xff,(UID_KEY_TAG >> 8) & 0xff,(UID_KEY_TAG >> 16) & 0xff,(UID_KEY_TAG >> 24) & 0xff,
UID_KEY_TAG & 0xff,(UID_KEY_TAG >> 8) & 0xff,(UID_KEY_TAG >> 16) & 0xff,(UID_KEY_TAG >> 24) & 0xff,
UID_KEY_TAG & 0xff,(UID_KEY_TAG >> 8) & 0xff,(UID_KEY_TAG >> 16) & 0xff,(UID_KEY_TAG >> 24) & 0xff
#endif
}
#endif
;
#else
//no license placeholder
#endif
mChipID: Used to store the chip's UID (Unique Identifier). It needs to be initialized once at startup. For some chips where the UID is not stored in a continuous memory region, this function merges the UID into a contiguous data block during initialization.
mChipAddr:Stores the obfuscated address information of the chip UID. After obfuscation, the real UID address cannot be found in the binary code.
mCalcKey:Stores the result of the dynamic Matrix encoding calculation — essentially the computed correct password.
mUserID:Stores the user-defined password(s) set by the user.
mKey:Stores the correct password data written during PowerWriter mass production. If mCalcKey does not match mKey during runtime calculation, it indicates that the code has been illegally copied, and the system will refuse to run.
void ChipUIDInitial():This function is used to initialize the UID information. To hide the real UID of the chip, PowerWriter performs an XOR operation between the actual UID address (UID_CHIP_ADDR) and a random value (UID_CHIP_ADDR_MIX) when exporting the source code. This prevents the real UID address from appearing directly in the compiled binary or HEX file. The correct UID is then dynamically restored and stored in memory during runtime in the ChipUIDInitial() function.
This function includes the following key operations:
- Calculate the UID address at runtime
- Safely transfer the UID address to another variable
- Verify whether the UID transfer code has been tampered with or points to an invalid address
- Copy both continuous and non-continuous UID address segments into a single contiguous UID array
/**
* @brief Initialization chip ID. Called at initialization time
* Note: Please do not place ChipUIDAlgo_Calc in the same place as ChipUIDAlgo_Check
* @retval void
*/
void ChipUIDInitial()
{
//restore real id Addr
int i = 0;
uint32_t chipIDAddr = 0;
uint32_t mask = 0x0000000f;
mChipAddr = UID_CHIP_ADDR;
mChipAddr ^= UID_CHIP_ADDR_MIX;
//Do not read directly from the address,you could use your's style
for ( i = 0; i < sizeof(uint32_t) * 2; i++)
{
chipIDAddr |= mChipAddr & (mask << i * 4);
}
//If it has been modified by decompiling, exit
if (chipIDAddr == 0 || chipIDAddr == 0xffffffff)
return;
//copy to array
#if ((defined STM32L1_Series) || (defined STM32L0_Series)) //offset 0x00,0x04,0x14
for (i = 0; i < UID_CHIP_SIZE; i++)
{
mChipID[i] = *(uint8_t *)chipIDAddr;
if (i != 7)
{
chipIDAddr++;
}
else
{
chipIDAddr += 0x0D;
}
}
#else
for (i = 0; i < UID_CHIP_SIZE; i++)
{
mChipID[i] = *(uint8_t *)chipIDAddr;
chipIDAddr++;
}
#endif
}
ChipUIDAlgo_Calc: The UID Matrix calculation is used for the matrix computation of UID, but it does not directly call the Matrix algorithm.
/**
* @brief Calculate the key
* Note: Before calling. Must have called ChipUIDInitial
* @retval pKey
*/
char * ChipUIDAlgo_Calc()
{
ChipUIDAlgo_Typedef func = ChipUIDAlgo;
(*func)(mUserID,mChipID,mCalcKey);
if(UID_DATAENDIAN == bigEndian){
U32DataEndiaSwap((uint32_t *)mCalcKey,sizeof(uint32_t));
}
return mCalcKey;
}
ChipUIDAlgo_Check:This function is used to verify whether the result of the dynamic calculation is exactly consistent with the result from PowerWriter.
/**
* @brief Check the key
* Note: Before calling. Must have called ChipUIDInitial
* @retval if key is same then retun true,Otherwise, return false
*/
bool __inline ChipUIDAlgo_Check()
{
const char *mLicAddr = 0;
int mLicAddrI32 = 0;
ChipUIDAlgo_Calc();
mLicAddrI32 = UID_KEYADDR_INNER_MIXED;
mLicAddrI32 ^= UID_KEYADDR_MIX;
mLicAddr = (char *)(mLicAddrI32);
return 0 == memcmp(mCalcKey, mLicAddr, UID_KEY_LENGTH);
}
cortex_chipid_binding.h
#define STM32F0_Series //MCU Series
#define STM32F071xB //MCU
#define UID_CHIP_SIZE 12 //The ID length of the chip is by default 12 bytes.
#define UID_CHIP_ADDR_MIX 0xE4E23C7A //Used to hide the location of the chip ID address in the code
#define UID_CHIP_ADDR 0xFB1DCBD6 //The address of the chip ID in the memory
#define UID_ENABLE_TAG 0 //When the value is 1, the authorization address with placeholders will be filled with **UID_KEY_TAG**.
#define UID_KEY_TAG 0x5AA5A55A //Markings with placeholders
#define UID_KEY_LENGTH 12 //The length of the password entered by the user
#define UID_KEYADDR_INNER ".ARM.__at_0x0801FFF4" //The address of the authorized data in the memory
#define UID_KEYADDR_PLACEHOLDER_EN 0 //Whether to enable the authorization data placeholder in the code
#define UID_KEYADDR_MIX 0xF4A77775 //Authorization address confusion random number
#define UID_KEYADDR_INNER_MIXED (0x0801FFF4 ^ UID_KEYADDR_MIX) //The storage address of the authorized data
#define UID_USERID_LENGTH UID_KEY_LENGTH //The length of the user password
#define UID_USERID_KEY1 0x097FA3EF //User password 1
#define UID_USERID_KEY2 0x438A3070 //User password 2
#define UID_USERID_KEY3 0xC3F2AB5E //User password 3
#define UID_DATAENDIAN littleEndian //Definition of Number Sequence
main.c:
#include "cortex_chipid_binding.h"
void SystemInit()
{
//don't call ChipUIDInitial() at here
//...
}
int main()
{
//Initial Chip
ChipUIDInitial();
//user code
//......
//Check in your code
bool ret = ChipUIDAlgo_Check();
if(ret){
//ok
}else{
//false
}
}
- Compile and Save:This operation synchronizes the configured PowerWriter built-in authorization functionality to the PowerWriter hardware.
If you modify settings such as the key address, length, endianness, or password information, you need to reopen the Matrix Editor and click "Compile and Save" again. This ensures that all configuration data remains synchronized and avoids inconsistencies.
In the generated Demo project, only the following two files are required for actual use:
- cortex_chipid_binding.c
- cortex_chipid_binding.h
Other files are auxiliary files and not required for final deployment.
The use of Matrix is the same as the use of ICWKEY Matrix configuration ,the code structure of Matrix is a detailed reference. Matrix demo tutorial.
4.1.4.1.5.3 Matrix Lock Mode
The difference between Matrix's Locked Mode and Matrix is that after the configuration of Locked Mode is completed, you can't configure or modify the configuration again by saving the project or exporting the settings, as demonstrated below:
4.1.4.1.5.4 ICWKEY
For a tutorial on how to use ICWKEY, see ICWKEY user manual。
4.1.4.1.5.5 ICWKEY lock mode
ICWKEY's locking mode has the same effect as Matrix's locking mode. After using the locking mode, you can't edit the project again when you reopen it or import the configuration, see Matrix Lock Mode.