跳到主要内容
版本:Next

4.1.4.1.5 授权与签名

GIF 2024-5-28 11-32-14

授权与签名提供目标芯片签名功能,防止编程后的固件被直接拷贝使用,PowerWriter 提供两种签名算法,一种为ICWKEY签名授权、另一种为Matrix 签名,ICWKEY 为ECDSA 数字签名算法,为非对称签名算法,加密强度高,但资源占用较大,Matrix 为随机矩阵签名,算法简单,资源占用低。

4.1.4.1.5.1 在线签名

创芯工坊提供在线签名服务,详见创芯工坊授权中心帮助文档 帮助中心- (icworkshop.com)

提示

在线授权配置方法跟PowerWriter 客户端一致,使用方法需要使用创芯工坊客户端进行目标芯片编程,只能使用在线编程模式,每次编程一颗芯片,常用于开发送样。

4.1.4.1.5.2 Matrix

PowerWriter 内置离线授权

img

PowerWriter 内置了基于随机矩阵算法的 UID 离线授权方法,离线授权界面设置如图所示:

img

离线授权基础设置包含:

  • 密钥地址:密钥地址可以理解为存放授权信息的地址,他的默认地址设定为 芯片Flash 容量 - 12 的位置,上图是 STM32F071CB 的默认存储地址
  • 用户密码长度:这里可以填写用户设定密码长度,默认为12字节,可选4字节,8字节长度,未来这个功能将会升级为自动,匹配芯片UID长度。
  • 数据存储模式:数据存储模式分为小端模式和大端模式
  • 用户密码(3组用户密码):根据设定可以设定最多三种用户密码
  • Matrix 编码:Matrix 编码定义了用户可以编辑的离线授权的加密矩阵,如下图所示:

img

PowerWriter 离线授权随机矩阵主要包含三个区域,如下所示:

  • UID 加密算法编辑器代码编辑预览:此区域提供了代码预览和代码快速编辑的功能,通过随机生成代码或者手动修改算法矩阵,都可以实时显示在此区域,如需手动编辑算法矩阵,请参考自定义UID 修改说明的操作流程。
  • 随机生成:随机生成功能将会生成随机矩阵数组。
  • 检查代码:验证当前矩阵数组的强度。
  • 导出源码:设置好的信息导出为源码 ( MDK 示例)。

img

生成的项目文件见下图所示:

img

  • cortex_chipid_binding.c:芯片UID 签名代码源文件,以下说明 cortex_chipid_binding 中的代码组成结构:

花指令:通过指令开启花指令,花指令的作用是在原始的矩阵中混入一些无关的代码,让反编译看到的结果和预想中有出入,增加分析难度,此方法对于芯片保护级别只有0和1 两级的芯片尤其有用,用过可以通过以下开关进行开启或者关闭花指令

#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位数据大小端交换函数

/**
* @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]);
}
}

导出的变量:包括签名信息等。

/* 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: 用于存储芯片的UID,在启动的时候需要初始化一次,针对部分芯片UID 不连续的,在初始化的时候合并成连续的数据存储。

mChipAddr:用于存储混淆过的芯片UID 地址信息,混淆过后无法在二进制代码中找到芯片的ID地址信息。

mCalcKey:用于存储动态Matrix 编码的计算的结果,也就是存储正确密码。

mUserID:存储用户设定的密码信息。

mKey:存储PowerWriter 量产时写入的正确密码数据,在代码动态计算如果mCalcKey 和 mKey不匹配,说明此代码被非法拷贝,从而实现拒绝运行的目的。

void ChipUIDInitial():此函数用于初始化 UID 信息,为了隐藏芯片的真实UID ,PowerWriter 在导出源代码时,会将UID_CHIP_ADDR 与 随机数 UID_CHIP_ADDR_MIX 进行异或运算,使其在编译好的Bin 文件 hex 中不会显示真实的ID 地址,然后在 ChipUIDInitial 中在动态初始化,将正确的UID 存储在内存中,此函数分为以下几个功能:

  • 在代码运行时 计算UID 地址。
  • 将UID 地址安全转移到另外一个变量。
  • 检查UID 转移代码是否被非法篡改或者无效地址。
  • 对于连续UID 地址和 非连续UID 地址拷贝到同一个UID数组中。
/**
* @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: 用于计算UID Matrix 矩阵计算,这里并非直接调用 Matrix 矩阵算法:

/**
* @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:此函数用于验证动态计算的结果和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  //当前芯片的系列
#define STM32F071xB //当前芯片的型号
#define UID_CHIP_SIZE 12 //当前芯片ID的长度,默认都是12字节
#define UID_CHIP_ADDR_MIX 0xE4E23C7A //用于隐藏芯片ID 地址在代码中的位置
#define UID_CHIP_ADDR 0xFB1DCBD6 //芯片ID在内存中的地址
#define UID_ENABLE_TAG 0 //当为1时,将会对有占位符的授权地址填充**UID_KEY_TAG**
#define UID_KEY_TAG 0x5AA5A55A //有占位符标记
#define UID_KEY_LENGTH 12 //用户输入的密码长度
#define UID_KEYADDR_INNER ".ARM.__at_0x0801FFF4" //授权数据再内存中的地址
#define UID_KEYADDR_PLACEHOLDER_EN 0 //是否在代码中开启授权数据占位符
#define UID_KEYADDR_MIX 0xF4A77775 //授权地址混淆随机数
#define UID_KEYADDR_INNER_MIXED (0x0801FFF4 ^ UID_KEYADDR_MIX) //授权数据存储地址
#define UID_USERID_LENGTH UID_KEY_LENGTH //用户密码长度
#define UID_USERID_KEY1 0x097FA3EF //用户密码 1
#define UID_USERID_KEY2 0x438A3070 //用户密码 2
#define UID_USERID_KEY3 0xC3F2AB5E //用户密码 3
#define UID_DATAENDIAN littleEndian //数端序定义

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
}
}

  • 编译并保存:将设置好的PowerWriter 内置授权功能同步到PowerWriter 硬件。
注意

如修改密钥地址、长度、端序、或者密码信息、需要重新打开Matrix 编辑器,重新点击编译并保存,避免出现信息不同步。

生成的Demo中实际需要使用的只有 cortex_chipid_binding.c 和 cortex_chipid_binding.h,其他文件为辅助文件。

Matrix 的使用方法也可参考ICWKEY 的使用方法,详见 Matrix 配置方法 ,Matrix 的代码结构详解参考 Matrix demo 演示教程

4.1.4.1.5.3 Matrix锁定模式

Matrix 的锁定模式和 Matrix 的区别在于,锁定模式配置完成后,保存项目或者导出设置,无法再次配置或者修改配置,演示如下:

GIF 2024-5-28 11-50-23

4.1.4.1.5.4 ICWKEY

ICWKEY 的使用教程,详见 ICWKEY 用户手册

4.1.4.1.5.5 ICWKEY锁定模式

ICWKEY 的锁定模式,跟Matrix 的锁定模式效果相同,使用锁定模式之后,在重新打开项目或者导入配置时,无法再次编辑,见 Matrix锁定模式