4.1.4.1.5 授权与签名
授权与签名提供目标芯片签名功能,防止编程后的固件被直接拷贝使用,PowerWriter 提供两种签名算法,一种为ICWKEY签名授权、另一种为Matrix 签名,ICWKEY 为ECDSA 数字签名算法,为非对称签名算法,加密强度高,但资源占用较大,Matrix 为随机矩阵签名,算法简单,资源占用低。
4.1.4.1.5.1 在线签名
创芯工坊提供在线签名服务,详见创芯工坊授权中心帮助文档 帮助中心- (icworkshop.com)。
在线授权配置方法跟PowerWriter 客户端一致,使用方法需要使用创芯工坊客户端进行目标芯片编程,只能使用在线编程模式,每次编程一颗芯片,常用于开发送样。
4.1.4.1.5.2 Matrix
PowerWriter 内置离线授权
PowerWriter 内置了基于随机矩阵算法的 UID 离线授权方法,离线授权界面设置如图所示:
离线授权基础设置包含:
- 密钥地址:密钥地址可以理解为存放授权信息的地址,他的默认地址设定为 芯片Flash 容量 - 12 的位置,上图是 STM32F071CB 的默认存储地址
- 用户密码长度:这里可以填写用户设定密码长度,默认为12字节,可选4字节,8字节长度,未来这个功能将会升级为自动,匹配芯片UID长度。
- 数据存储模式:数据存储模式分为小端模式和大端模式
- 用户密码(3组用户密码):根据设定可以设定最多三种用户密码
- Matrix 编码:Matrix 编码定义了用户可以编辑的离线授权的加密矩阵,如下图所示:
PowerWriter 离线授权随机矩阵主要包含三个区域,如下所示:
- UID 加密算法编辑器代码编辑预览:此区域提供了代码预览和代码快速编辑的功能,通过随机生成代码或者手动修改算法矩阵,都可以实时显示在此区域,如需手动编辑算法矩阵,请参考自定义UID 修改说明的操作流程。
- 随机生成:随机生成功能将会生成随机矩阵数组。
- 检查代码:验证当前矩阵数组的强度。
- 导出源码:设置好的信息导出为源码 ( MDK 示例)。
生成的项目文件见下图所示:
- 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 的区别在于,锁定模式配置完成后,保存项目或者导出设置,无法再次配置或者修改配置,演示如下:
4.1.4.1.5.4 ICWKEY
ICWKEY 的使用教程,详见 ICWKEY 用户手册。
4.1.4.1.5.5 ICWKEY锁定模式
ICWKEY 的锁定模式,跟Matrix 的锁定模式效果相同,使用锁定模式之后,在重新打开项目或者导入配置时,无法再次编辑,见 Matrix锁定模式。