当前位置: 首页 > news >正文

“一机一码”安全加密方案

简介

在Easy-EAI生态的商业化落地中,核心算法模型(如.rknn权重文件)和业务代码的知识产权保护是诸多客户的刚需。本文将介绍一种基于设备唯一序列号(CPU Serial)的“一机一码”加密方案。这套机制能够有效防止软件和AI模型被恶意拷贝,为你的嵌入式产品穿上第一层防弹衣,但因加密算法种类非常繁多,本文只是适合该产品的其中一种方案。

一、理论分析

1. 核心加密逻辑:

从串码到密钥单纯在代码里做 if (serial == "f175...")的明文比对是非常脆弱的(防君子不防小人),极易被逆向破解。完整的安全方案:

读取:获取当前设备的 CPU Serial。

加盐(Salt)与哈希:将 Serial 与开发者自定义的一段隐秘字符串(Salt)拼接,通过 SHA-256 算法计算出不可逆的摘要,将此摘要作为对称加密的动态密钥(Key)

加解密执行:利用该动态密钥,结合 AES-256 等算法,在程序启动时动态解密核心授权文件(License)或模型文件。

2. 底层架构延伸:

从 Linux 系统层面读取 /proc/cpuinfo是兼顾效率与通用性的解法。但如果你对安全性有极致要求,Rockchip 芯片底层其实提供了更硬核的支持:

OTP (One-Time Programmable) 存储:芯片内部的一块物理熔断存储区。数据一旦写入便无法篡改,通常用于存放 Secure Boot 的公钥 Hash 或设备的“根密钥”。

硬件 Crypto 引擎:自带硬件加密模块,支持 AES/RSA 等算法的硬件级加速,不占用 CPU 算力。

工程取舍:OTP 烧写不可逆,对产线管理要求极高;而本文的“系统节点 Serial + 软件级混淆”方案,则是在开发部署效率安全防护之间最平衡的最佳实践。

二、功能实现

为了方便大家在开发板上直接复现,将创建一个名为 easy-eai-auth 的工程目录。请在你的板端的工作目录下执行以下操作。

1. 工程目录结构预览

首先,创建工程文件夹并进入:

mkdir /userdata/easy-eai-auth && cd /userdata/easy-eai-auth

我们最终的目录结构如下:

easy-eai-auth/ ├── hw_auth.h # 核心函数声明与宏定义 ├── hw_auth.c # 序列号读取与密钥生成的具体实现 ├── main.c # 业务层的调用演示 └── build.sh # 一键编译脚本

2. 核心代码编写

文件一:hw_auth.h (头文件配置)统一管理跨文件的函数声明和关键的“盐值”。

#ifndef __HW_AUTH_H__ #define __HW_AUTH_H__ #include <stddef.h> /* * 【核心配置】定义私有盐值 (Salt)。 * 警告:实际生产项目中,千万不要像这样将明文字符串直接写在代码里! * 建议:将字符串拆分成多个字符数组分布在不同文件中,或在运行时通过复杂逻辑动态拼接, * 以防止黑客通过 `strings` 命令或静态反汇编轻易提取出该盐值。 */ #define PRIVATE_SALT "Easy-EAI_Rockchip_RV1126B_Secret_2026" #ifdef __cplusplus extern "C" { // 确保在 C++ 编译器下依然使用 C 语言的符号命名规则,避免链接失败 #endif // 获取 CPU 序列号 int get_cpu_serial(char *serial_out, size_t max_len); // 根据序列号与盐值生成 32 字节的 AES 密钥 void generate_aes_key(const char *serial, unsigned char *aes_key); #ifdef __cplusplus } #endif #endif // __HW_AUTH_H__

文件二:hw_auth.c (核心逻辑实现)处理与 Linux 底层 /proc/cpuinfo 节点的交互以及 OpenSSL 哈希生成。

#include <stdio.h> #include <string.h> #include <openssl/sha.h> #include "hw_auth.h" /* * 函数说明:读取并解析 /proc/cpuinfo 获取硬件序列号 * 底层逻辑:/proc/cpuinfo 并不是真实硬盘上的文件,而是内核在内存中实时生成的硬件快照。 * 返回值:成功返回 0,并将序列号写入 serial_out;失败返回 -1。 */ int get_cpu_serial(char *serial_out, size_t max_len) { FILE *fp = fopen("/proc/cpuinfo", "r"); if (!fp) { perror("Failed to open /proc/cpuinfo"); return -1; } char line[256]; const char *target = "Serial"; // 使用 fgets 逐行读取,相比 fscanf 更安全,可有效防止缓冲区溢出 while (fgets(line, sizeof(line), fp)) { // 匹配以 "Serial" 开头的特征行 if (strncmp(line, target, strlen(target)) == 0) { char *colon = strchr(line, ':'); if (colon) { // 核心解析:提取冒号后的内容。 // " %s" 中的空格会让 sscanf 自动跳过冒号和串码之间的前导空格和制表符 sscanf(colon + 1, " %s", serial_out); fclose(fp); return 0; } } } fclose(fp); return -1; } /* * 函数说明:基于硬件序列号生成 AES 密钥 * 底层逻辑:利用 SHA-256 的不可逆性和雪崩效应,将“明文串码”+“内部盐值”彻底打碎, * 映射为一段 256-bit (32 Byte) 的无规律二进制流,作为最高强度的对称密钥。 */ void generate_aes_key(const char *serial, unsigned char *aes_key) { char combined_str[512]; // 步骤1:拼接明文 Serial 和 Salt snprintf(combined_str, sizeof(combined_str), "%s_%s", serial, PRIVATE_SALT); // 步骤2:初始化 SHA-256 上下文状态机 SHA256_CTX sha256; SHA256_Init(&sha256); // 步骤3:喂入数据进行运算 (Update 允许分多次喂入庞大数据,这里我们一次喂完) SHA256_Update(&sha256, combined_str, strlen(combined_str)); // 步骤4:提取最终的 32 字节哈希值到 aes_key 数组中 SHA256_Final(aes_key, &sha256); }

文件三:main.c (主函数)引入 OpenSSL 的EVP接口,在内存中完成数据的 AES-256-CBC 加密与解密闭环。

#include <stdio.h> #include <string.h> #include <openssl/sha.h> #include <openssl/evp.h> #include "hw_auth.h" /* * 初始向量 (Initialization Vector, IV): * 在 AES-CBC (密码分组链接) 模式下,相同的明文块加密出的密文会不同,这依赖于 IV。 * 规范做法:每次加密应随机生成 16 字节 IV,并将其与密文拼接存放在一起。解密时提取使用。 * 为降低 Demo 复杂度,此处采用硬编码的固定 IV。 */ unsigned char aes_iv[16] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10}; /* * AES-256-CBC 解密包装函数 * 相比于底层的 AES_xxx 接口,EVP 接口的优势在于它会自动处理数据尾部的 Padding(填充)。 */ int aes_decrypt(const unsigned char *ciphertext, int ciphertext_len, const unsigned char *key, unsigned char *plaintext) { EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); int len; int plaintext_len; // 初始化:指定使用 aes_256_cbc 算法,并载入动态生成的 key 和 IV EVP_DecryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, key, aes_iv); // 核心解密:处理绝大部分数据块 EVP_DecryptUpdate(ctx, plaintext, &len, ciphertext, ciphertext_len); plaintext_len = len; // 尾部处理:处理最后由于对齐要求而填充的废弃字节 EVP_DecryptFinal_ex(ctx, plaintext + len, &len); plaintext_len += len; EVP_CIPHER_CTX_free(ctx); return plaintext_len; } // AES-256-CBC 加密包装函数 (逻辑与解密对称) int aes_encrypt(const unsigned char *plaintext, int plaintext_len, const unsigned char *key, unsigned char *ciphertext) { EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); int len; int ciphertext_len; EVP_EncryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, key, aes_iv); EVP_EncryptUpdate(ctx, ciphertext, &len, plaintext, plaintext_len); ciphertext_len = len; EVP_EncryptFinal_ex(ctx, ciphertext + len, &len); ciphertext_len += len; EVP_CIPHER_CTX_free(ctx); return ciphertext_len; } int main() { char serial[64] = {0}; unsigned char aes_key[SHA256_DIGEST_LENGTH]; // SHA256 固定输出 32 字节 printf("--- Easy-EAI Hardware Binding & Crypto Demo ---\n"); // 1. 获取硬件序列号并生成专属密钥 if (get_cpu_serial(serial, sizeof(serial)) != 0) { printf("[-] Error: Could not read hardware serial. Exiting...\n"); return -1; } generate_aes_key(serial, aes_key); printf("[+] Hardware Key generated for Serial: %s\n", serial); // ================== 应用层实战演示 ================== // 模拟核心资产:在实际项目中,这通常是你读入内存的 .rknn 模型二进制流 unsigned char *secret_asset = (unsigned char *)"EASY-EAI-PREMIUM-LICENSE-VALID"; int asset_len = strlen((char *)secret_asset); // 分配密文和解密后明文的缓冲区 (比原资产大一些,用于容纳 AES 块对齐产生的填充字节) unsigned char ciphertext[128] = {0}; unsigned char decryptedtext[128] = {0}; // 2. 模拟发行阶段:用硬件密钥加密资产 // (实际产线中,这一步通常在独立的烧录工具上提前完成,下发到板子里的直接是密文) int ciphertext_len = aes_encrypt(secret_asset, asset_len, aes_key, ciphertext); printf("\n[*] Encrypting Core Asset...\n"); printf(" Ciphertext (Hex): "); for(int i = 0; i < ciphertext_len; i++) printf("%02x", ciphertext[i]); printf("\n"); // 3. 模拟设备运行阶段:程序启动时,利用当面板子的硬件密钥解密资产 printf("\n[*] Decrypting with Hardware Key...\n"); int decryptedtext_len = aes_decrypt(ciphertext, ciphertext_len, aes_key, decryptedtext); decryptedtext[decryptedtext_len] = '\0'; // 补充结束符,方便后续按字符串打印 // 4. 业务校验逻辑 // 这里的验证非常关键:如果在一台错误的设备上运行,aes_key 就错了,解密出来的就会是一堆乱码 if (strncmp((char *)decryptedtext, (char *)secret_asset, asset_len) == 0) { printf("[+] Decryption SUCCESS! Secret payload: %s\n", decryptedtext); printf("[+] Program can continue to load AI models...\n"); } else { printf("[-] Decryption FAILED! Data corruption or unauthorized hardware.\n"); return -1; } // 【极其重要的安全收尾】:离开作用域前,立刻将内存中的敏感明文和密钥擦除为 0 // 防止被黑客通过内存 Dump (如通过 /proc/kcore 或 GDB 附加) 将密钥捞走 memset(aes_key, 0, sizeof(aes_key)); memset(decryptedtext, 0, sizeof(decryptedtext)); return 0; }

备注:

文件四:build.sh (编译脚本)由于使用了 OpenSSL 库,编译时必须动态链接 -lcrypto。

#!/bin/bash # 注意:若为 Ubuntu 宿主机给 RV1126 交叉编译,请将 gcc 替换为实际的交叉编译器链 # 例如 CC=arm-linux-gnueabihf-gcc CC=gcc echo "Compiling Easy-EAI hardware auth demo..." $CC main.c hw_auth.c -o hw_bind_demo -lcrypto if [ $? -eq 0 ]; then echo "Build success! Executable generated: ./hw_bind_demo" else echo "Build failed. Please ensure libssl-dev is installed." fi

3. 编译与执行演示

在确保 4 个文件都在 /userdata/easy-eai-auth 目录下后,在终端依次执行以下命令:

(1)赋予编译脚本可执行权限

chmod +x build.sh

(2)执行编译

./build.sh

(3)运行生成的二进制可执行文件

./hw_bind_demo

总结

通过/proc/cpuinfo 获取Serial + 哈希加盐生成密钥 + AES 动态解密核心资产的流程,我们以极低的开发成本实现了一套可靠的硬件绑定机制。不要在代码中写 if (check_license() == true) 这样的返回布尔值的校验逻辑!黑客只需修改汇编代码中的一个跳转指令(如将 BNE 改为 BEQ)就能彻底绕过防线。让“数据强耦合”:必须让验证逻辑参与到核心数据的解密中去,一旦Serial不对,解密出来的.rknn模型就是一堆乱码,程序直接崩溃退出,这才是嵌入式加密设计的精髓。

【附录:核心密码学概念】

对于平时专注于底层驱动或 AI 算法的工程师,如果对上文提及的安全术语感到陌生,可以通过以下类比快速建立概念:

概念通俗解释核心特性
哈希 (Hash / 散列)数据界的“绞肉机”。无论你放入一本 10MB 的书,还是一个短句,它都会吐出一段固定长度(如 32 字节)的乱码。不可逆(你无法从肉馅还原出整块肉),并且具备“雪崩效应”(原文哪怕只改动一个标点符号,输出的哈希值也会面目全非)。
加盐 (Salt)为了防止黑客用预先计算好的“哈希对照表”(彩虹表)暴力撞库,我们在原文(如硬件 Serial)送入哈希函数前,强行拼接一段只有开发者知道的“私有字符串”(也就是这把盐)。即使两台设备碰巧序列号相似,因为“盐”的存在,它们生成的最终密钥也无法被轻易猜测。
SHA-256 (Secure Hash Algorithm 256-bit)由美国国家安全局 (NSA) 设计的一种极其强悍的哈希算法。不管输入多长,必定输出 256 位(32 字节)的结果。这是目前工业界最常用的摘要算法,在本文中被用来充当从“字符串”到“标准化二进制密钥”的转换器。
AES-256 (Advanced Encryption Standard)目前全球公认最安全、使用最广的“对称加密”算法。“对称”意味着你用什么钥匙锁门(加密),就得用相同的钥匙开门(解密)。256 代表它使用 256-bit(32 字节)的密钥进行加解密。破解它的难度目前只停留在理论层面(以人类现有的计算机算力,直到宇宙毁灭也无法靠暴力枚举破解 256 位密钥)。本文中,这段最关键的密钥正是由设备序列号通过 SHA-256 动态生成的。
http://www.cnnetsun.cn/news/2875815.html

相关文章:

  • 04、JAVAEE---多线程进阶、文件I/O、网络初识
  • OSPF综合实验(nat,汇总,特殊区域,加快收敛,安全认证)
  • 2026年AI人才市场火爆!这3个高薪岗位普通人也能入场?速收藏!
  • 哈希表冲突处理:开放寻址与拉链法的底层实现与工程选型
  • 深度解析AKShare Pro数据接口:从基础使用到高级配置
  • 企业微信自动化中验证环节的处理策略
  • 终极Project Sekai表情包制作指南:3分钟创建个性化Discord贴纸
  • pyarrow,一个列式数据处理的 Python 库!
  • Pentaho Data Integration 11.x架构演进与关键技术实现深度解析
  • 计算机毕设实战-基于 Java 的智能土地档案综合管理系统 土地信息与档案管控平台基于SpringBoot的油田土地档案管理系统【完整源码+LW+部署说明+演示视频,全bao一条龙等】
  • 深入解析汽车级LCD段码驱动芯片PCA8576D:从原理到实战应用
  • 企业知识产权管理痛点与解决方案系列解说十
  • Python通达信数据接口:三步掌握A股行情分析的免费神器
  • MPV懒人包终极指南:5分钟让Windows用户享受专业影院级播放体验
  • 3步释放华硕笔记本潜能:G-Helper轻量控制中心完全指南
  • 3分钟掌握:如何在Kodi中无缝播放115网盘视频
  • 【RT-DETR实战】RT-DETR实战手记(200):端侧实时目标检测,下一步往哪儿走?
  • 手把手教你用C#和BouncyCastle实现IC卡SM4国密算法(含密钥分散与MAC计算)
  • 贵港车棚供应商是什么?主要有哪几种类型?
  • 终极指南:如何高效使用PKSM进行跨世代宝可梦存档管理
  • Nintendo Switch游戏文件管理终极指南:NSC_BUILDER完全使用教程
  • 别再傻傻遍历二维数组了!用C语言三元组高效搞定稀疏矩阵加法(附PTA真题避坑指南)
  • Windows 11终极优化指南:Win11Debloat一键清理系统冗余与隐私保护
  • 华为MetaERP Oracle EBS(R12)用间接法编制现金流量表,从原理→前提→配置→FSG 搭建→公式设计→测试→月结操作→常见坑完整、一步一步讲清楚,你可以直接照着做实施。
  • 如何在老旧Mac上安装最新macOS:OpenCore Legacy Patcher完整4步指南
  • P87LPC778中断与I/O配置实战:从寄存器详解到避坑指南
  • Java毕业设计-基于jspm自行车个性化改装推荐系统基于springboot框架的自行车个性化改装推荐系统(源码+LW+部署文档+全bao+远程调试+代码讲解等)
  • 从方格游戏到动态规划:用Python手把手解‘踩方格’问题(附两种递推思路对比)
  • Windows 11优化指南:用Win11Debloat一键清理系统垃圾,提升电脑性能
  • 终极指南:Windows 11 LTSC系统完美添加微软商店完整方案