尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

Microchip PIC集成加密引擎实战:从AES到密钥管理

Microchip PIC集成加密引擎实战:从AES到密钥管理 在做嵌入式项目时很多工程师都有过这种经历设备联调好了功能测试通过了结果一到客户现场要么固件被读出来抄板要么通讯数据被第三方截获伪造。几年前我处理过一个网关项目用的是普通MCU加软件AES加密一段数据要等好几个毫秒现场升级固件时还出现过在加密过程中被中断打断导致数据错乱的问题。后来换了Microchip带集成加密引擎Crypto Engine的PIC系列才把性能和安全性同时兜住。这篇文章就围绕“Microchip PICs with Integrated Crypto Engine”展开聊聊硬件加密引擎到底解决了什么痛点、PIC系列里有哪几类芯片可选、在MPLAB X环境下如何快速上手以及我实际调试过程中踩过的一些坑。1. 内容整体设计与思路拆解1.1 为什么硬件加密引擎成了MCU的刚需这几年物联网设备数量增长非常快但很多设备的安全性其实停留在“能跑就行”的程度。早期的方案是在固件里用软件实现AES、SHA等算法比如拿C语言移植一个开源加密库。从功能角度看这样做没什么问题算法标准是公开的测出来的加密结果也一样。但一旦落到真实产品上问题就来了。第一个问题是性能。MCU主频往往只有几十兆赫兹软件AES加密一个128位数据块8位机可能要跑上千个时钟周期。如果通讯频率高一点、数据包长一点CPU就被加密运算占满了主业务逻辑反而卡顿。第二个问题是密钥安全。软件加密时密钥必然以明文形式存在Flash或SRAM里代码保护等级不够的话用调试器就能直接读出来。第三个问题是功耗。加密是计算密集型操作CPU长时间高负载运行功耗自然压不下来。这时候把加密运算下沉到硬件模块就是最合理的解法。Microchip给PIC系列集成的Crypto Engine本质上就是一个专门做对称加密和哈希运算的外设。CPU只负责把明文和密钥写进寄存器硬件模块自动完成轮运算再把结果拿回来。整个过程CPU几乎不参与计算。1.2 我选择PIC系列硬件加密方案的三个原因市面上带硬件加密的MCU并不少但PIC系列有几个特点让我在实际选型时愿意优先考虑。外设库非常成熟。Microchip的MCCMPLAB Code Configurator能自动生成初始化代码不用手翻寄存器手册。对量产项目来说这点太重要了省掉的不只是开发时间还有人员的培训成本。PIC的加密引擎在密钥管理上做得很细。以PIC18F-K42系列为例它的Crypto Engine支持AES-128/192/256内置真随机数发生器TRNG还提供了安全密钥存储区。这种级别的配置在过去至少要上一颗独立安全芯片才能实现现在一颗MCU就搞定了。产品线覆盖面够广。从8位到32位从低功耗电池供电到高性能网关Microchip都有对应型号。这意味着同一个团队可以在不同定位的产品上沿用同一套安全开发思路不需要频繁切换平台。1.3 适合哪些人参考这篇文章主要面向三类读者正在做IoT终端、传感器节点、智能家居设备被数据加密和固件安全所困扰的嵌入式软件工程师打算在新项目中引入硬件级加密能力但不太清楚PIC系列芯片和设计工具的选型产品经理或技术负责人纯粹对MCU内外部设感兴趣想了解硬件加密引擎与传统软件加密在工程实践方面差异的学生和爱好者。对于第一类读者我会在文章里给出完整的实操代码和关键寄存器配置对于第二类我在文末整理了选型对比表对于第三类文章重点解释了加密引擎的工作机制和开发流程阅读时重点关注“为什么这么设计”的部分即可。2. 核心细节解析与实操要点2.1 Crypto Engine到底在硬件层面做了哪些事之前我在一块PIC18F45K42上做过实验用内部时钟跑64MHz软件AES-128加密一次128位数据大约耗时1.2毫秒把同样的任务交给Crypto Engine后耗时降到微秒级别而且CPU全程空闲。差距就是这么大。这个模块内部大致分为三个核心单元AES加密引擎支持ECB、CBC两种模式密钥长度可选128/192/256位。硬件直接实现了字节替换、行移位、列混合、轮密钥加这些步骤把AES的每一轮运算固化在电路里。真随机数发生器TRNG基于模拟噪声源产生随机种子再通过数字电路进行熵提取。TRNG的随机性不是软件伪随机数算法能比的它直接决定密钥和初始化向量的不可预测性。安全寄存器组存放当前会话的密钥和中间状态。这些寄存器对用户程序是只写或受控访问的不能反过来读出密钥值。从编程模型上看Crypto Engine对外表现为一组特殊功能寄存器SFR。你要做的只是按顺序填充密钥寄存器、设置模式位、把明文写入数据寄存器然后触发启动位最后轮询完成标志或等待中断。2.2 性能对比硬件加密不是“快一点”而是“快到一个数量级以上”我不止一次在项目评审时听到这样的疑问软件加密库明明也能跑为什么非要换芯片加成本我习惯用数据说话。拿AES-128-CBC加密一个256字节的数据块来说三种方案的差异非常明显方案平台加密耗时CPU占用率密钥存储位置软件AES8位MCU 64MHz约10ms100%Flash/SRAM软件AES32位MCU 200MHz约1ms80%-100%Flash/SRAM硬件Crypto EnginePIC18F-K42 64MHz约几十微秒10%以下硬件安全寄存器这个对比里硬件方案在8位机上就已经超过了32位机上软件加密的性能同时功耗还低得多。更关键的是密钥不经过CPU寄存器杜绝了在调试模式下被直接读取的可能。2.3 关键知识点AES模式和填充方式怎么选AES引擎虽然算法固定但实际使用时模式选择经常把人绕晕。常用的几个模式照下面理解就够了。ECB模式是最简单的方式每个块独立加密相同的明文永远得到相同的密文。这个特性在加密固定格式的数据包时容易被用来做模式分析所以除非只有单块数据否则不建议直接用。CBC模式加入了前一密文块的反馈相同的明文、不同的IV会得到不同的密文安全性比ECB好得多。缺点是加密链路是串行的不能并行处理。我用CBC模式多一些尤其是在传感器上报数据的场景中。CTR模式多用于需要对数据流进行随机访问的场景可以并行计算但如果IV重复使用会带来严重安全问题。做项目时还要注意填充方式数据长度不是16字节整数倍时标准做法是PKCS7填充。如果加密端和解密端约定不填充那就要在协议层自己保证数据长度对齐否则解密结果会多出几个零字节。2.4 真随机数发生器TRNG的坑与要点TRNG是整个Crypto Engine里最容易被人忽视又最容易出问题的地方。我遇到过几次这样的情况代码逻辑看着完全正确TRNG的配置寄存器也是按手册设置的但读出来的数值居然偶尔会出现连续重复。排查了很久才发现是电源纹波太大影响到了内部模拟噪声源的稳定输出。如果你用的是电池供电设备建议在电池电压跌落时降低TRNG采样率或者在硬件上加一级RC滤波。此外TRNG初始化后不要马上连续取大量随机数可以先丢弃前几十个输出让电路稳定下来再正式使用。这个“预热”操作很多参考代码里不会写但实测对随机性有明显改善。TRNG生成的随机数有两个用途一是作为AES加密的初始向量IV二是作为密钥或密钥种子。IV每次加密前都更新就能避免相同明文产生相同密文的模式泄漏密钥则建议只生成一次烧录到安全存储区后长期使用。2.5 密钥管理硬件安全区的使用原则既然用了硬件加密引擎密钥就不能再随便存个全局变量了。PIC的Crypto Engine通常配合芯片的代码保护功能和专用存储区一起使用。我在一个电表集中器项目里的做法是先用TRNG生成AES-128密钥把这个密钥写在Flash的一个专门扇区里写上后立刻将整个Flash的代码保护使能使调试接口无法读取该区域。值得留意的是开启Flash保护后后续使用编程器重新烧录时会比较麻烦首次量产时需要先规划好烧录流程。我的习惯是让产线先烧录带密钥的出厂固件再开启保护最后再烧录应用层固件。顺序颠倒了密钥就加密写不进去了。注意硬件加密引擎只负责“跑算法”和“存密钥”不负责“密钥怎么用”。你在协议里如何把密钥和IV组合进会话仍然是软件层面的事。千万不要因为用了硬件加密就把协议设计掉以轻心。3. 实操过程与核心环节实现3.1 开发环境准备与芯片选型这次实操我选的是PIC18F45K42它属于PIC18F-K42系列内置AES加密引擎、TRNG、CRC和多种通信接口。用MPLAB X IDE 6.x版本加MCC插件整个配置过程不需要手写底层驱动。如果没有手头这颗芯片可以先在Microchip的官网确认一下型号后缀。PIC18F-K42系列、PIC18F-Q43系列、PIC32CM系列都带Crypto Engine但寄存器名和MCC界面略有不同。我建议新手直接选K42系列资料多、社区活跃后续调试少走弯路。硬件准备清单Microchip PIC18F45K42开发板或自制最小系统板MPLAB X IDE 6.10及以上版本MPLAB Code ConfiguratorMCC插件一块带调试功能的编程器比如PICkit 4或Curiosity开发板自带调试器串口转USB模块用于打印调试信息3.2 MCC图形化配置Crypto Engine外设的步骤打开MCC后左侧外设列表里找到“Crypto”模块点击加入工程。右侧配置面板会出现几个配置项看起来并不复杂但我第一次用时还是漏掉了时钟选项。第一步在Project Resources里把Crypto模块加入系统默认生成crypto.c和crypto.h。第二步配置时钟。Crypto Engine需要独立的时钟源MCC里通常叫“Crypto Clock”我建议直接勾选“Enable”并选择系统时钟分频。注意分频系数不宜选太大否则加密一次数据块耗时会上来。第三步配置AES模式。在Crypto模块里选择AES引擎密钥长度选128位如果选256位密钥寄存器的填充长度会不同操作模式选CBC方向选Encrypt。第四步使能TRNG。MCC里TRNG的选项一般在独立的tab页勾选“Enable TRNG”即可。采样时间戳和内部放电时间可以保持默认值除非你发现随机数质量异常。第五步点击Generate生成代码。MCC生成代码后crypto.h里会声明几个关键接口函数比如Crypto_AES_EncryptCrypto_AES_DecryptCrypto_TRNG_Get实际使用时这些函数已经封装好了寄存器操作你只需要把数据指针和长度传进去。3.3 手写AES加密通信示例CBC模式下面这段代码就是我上一段时间在做的一个NB-IoT数据上报项目里实际用过的写法为了不牵涉业务协议我简化了一下但核心流程是完整的。#include mcc_generated_files/mcc.h #include mcc_generated_files/crypto.h // 声明密钥和IV uint8_t aes_key[16] {0x2B,0x7E,0x15,0x16,0x28,0xAE,0xD2,0xA6, 0xAB,0xF7,0x15,0x88,0x09,0xCF,0x4F,0x3C}; uint8_t aes_iv[16] {0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07, 0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E,0x0F}; uint8_t plaintext[32] hello pic crypto engine!; uint8_t ciphertext[32] {0}; void encrypt_packet_with_crypto(uint8_t *plain, uint8_t *cipher, uint16_t len) { // 设置密钥寄存器 Crypto_AES_SetKey(aes_key, CRYPTO_AES_KEY_LENGTH_128); // 设置IV Crypto_AES_SetIV(aes_iv); // 执行CBC加密 Crypto_AES_Encrypt(plain, cipher, len); } void main(void) { SYSTEM_Initialize(); while(1) { // 模拟一次数据上报 encrypt_packet_with_crypto(plaintext, ciphertext, 32); // 打印密文 for(int i0; i32; i) { printf(%02X, ciphertext[i]); } printf(\r\n); __delay_ms(1000); } }这段代码直观展示了一个典型调用流程先设密钥再设IV最后执行加密。CBC模式下如果连续加密多个数据包需要在每个包开始时重新设置相同的IV或递增的IV具体看你的协议怎么约定。我实际测试时用串口打印密文再在PC端用OpenSSL的AES-128-CBC解密能得到原始明文。如果不一致十有八九是IV没对上或者填充方式不一致。3.4 用TRNG生成动态IV的完整示例静态IV虽然简单但在安全要求高的场景里不建议用。我建议每次通讯前都从TRNG取新的IV代码可以这样处理#include mcc_generated_files/mcc.h #include mcc_generated_files/crypto.h uint8_t session_key[16]; // 会话密钥 uint8_t session_iv[16]; // 动态IV void generate_random_iv(uint8_t *iv, uint8_t len) { // 先丢弃前32个字节让TRNG稳定 for(int i 0; i 32; i) { Crypto_TRNG_Get(); } // 生成真正使用IV for(int i 0; i len; i) { iv[i] Crypto_TRNG_Get(); } } void prepare_secure_transmission(void) { generate_random_iv(session_iv, 16); // 再将TRNG生成的新密钥写入安全区 for(int i 0; i 16; i) { session_key[i] Crypto_TRNG_Get(); } Crypto_AES_SetKey(session_key, CRYPTO_AES_KEY_LENGTH_128); Crypto_AES_SetIV(session_iv); }这里有一个设计细节IV并不是秘密数据它通常随密文一起传输接收端需要知道IV才能解密。所以IV用TRNG生成后直接放在数据包明文部分发给对端是可以的。但如果不想主动暴露IV也可以在协议中约定以计数器或时间戳作为IV来源不过这样的话双方就必须严格同步。3.5 数据完整性校验与CRC模块配合使用很多项目只做了加密没做完整性校验导致接收端拿到被篡改的密文后解密出乱码还得在应用层做各种容错。加密和完整性校验是两个维度的事情一个负责保密性一个负责检测篡改。PIC18F-K42内部带有硬件CRC模块可以计算一段数据的CRC32。我在加密上报的数据包末尾会附上原始明文的CRC32值接收端解密后重新计算CRC再做比对能有效识别数据在传输过程中被改动的情况。uint32_t calculate_crc32(uint8_t *data, uint16_t len) { CRC_Initialize(); CRC_WriteData(data, len); return CRC_GetCRC(); }MCC里配置CRC模块时可以选择多项式、初始值、输入反转和输出反转默认的CRC-32配置多项式0x04C11DB7就和很多通用工具兼容。3.6 自定义协议封装加密数据帧格式设计实际项目中加密引擎只是中间一环更重要的是你如何设计整个通讯协议。我的一个通用做法是这样一个数据帧结构字段长度说明帧头2字节固定0xAA55数据长度2字节密文长度动态IV16字节本次会话IV密文N字节AES-128-CBC加密后的数据CRC324字节对密文的校验值这种设计的好处是解密端可以先校验CRC校验通过再用动态IV解密。即使有人截获了数据包没有密钥也无法在合理时间内还原明文。4. 常见问题与排查技巧实录4.1 加密结果与预期不一致这是新手最容易遇到的问题。代码看着没毛病但拿已知向量去测输出的密文总是和标准答案差一截。我排查这个问题的经验是先排除模式问题。确认MCC里选的是ECB还是CBC再用一个16字节的单块数据分别在这两种模式下跑一下对比输出是否和在线AES计算工具一致。如果ECB单独跑是对的CBC跑不对那基本就是IV设置出错。其次是密钥长度。PIC的Crypto Engine在写入密钥后指针会自动递增。如果你用128位密钥但写入了16字节后又多写了几个字节后续数据就会错位。4.2 串口打印乱码或MCU反复复位出现这类问题时多数不是加密模块的问题而是系统时钟配置有问题。Crypto Engine的时钟如果没配好模块内的状态机就可能卡死进而触发看门狗复位。我在一个项目里遇到过这种情况Crypto模块初始化正常但一调用加密函数程序就跳到复位向量。查了很久才发现是MCC生成代码时默认把Crypto时钟配置为系统时钟的256分频导致模块基本处于停摆状态。改成8分频后一切正常。4.3 调试器无法连接Flash被锁死这个教训值得单独提出来。我在一次实验中为了测试Flash保护功能把代码保护位设置得太高结果整个芯片无法再通过编程器连接只能做全片擦除之前烧录的校准数据也丢了。建议量产时把保护使能和全片擦除的流程分开测试先在样品上确认解锁方式再写进产线SOP。不要在产品开发阶段就把保护开满否则每次调试都要全片擦除重来非常浪费时间。4.4 TRNG随机数质量不佳判断随机数质量不能只看“看起来挺随机”。最常见的问题是TRNG采样率过高或时钟配置不当。我在前面也提过用示波器观察电源纹波如果观察到与系统时钟频率相关的毛刺最好在电源引脚附近加一个0.1uF的旁路电容。如果随机数偶尔出现连续相等把TRNG模块配置中的采样次数适当增大并且增加预热丢弃数据。正常生成1000个字节不应该出现明显的长串重复。4.5 加密延时会阻塞中断实际开发中加密过程最好不要在中断服务函数里执行。加密引擎虽然快但也有一个启动和完成的过程。如果在中断里调用函数内部的等待循环会占用大量中断时间影响其他实时任务。我通常的做法是在主循环里准备好待加密数据调用加密函数完成后把结果放到队列里再由通信外设发送。如果非要中断里加密那就用Crypto模块的DMA功能把加密操作交给模块后CPU继续处理其他事务。PIC18F-K42部分型号支持Crypto和DMA联动配置时要额外使能DMA请求源。5. 工程化落地从Demo到量产的关键思考5.1 密钥烧录流程与产线方案硬件加密引擎在开发板上跑通了只是第一步量产时“密钥怎么进芯片”才是最容易被忽略的问题。我的建议是采用两个阶段的烧录方案第一阶段芯片出厂前由工厂写入“根密钥”这个密钥由三部分合成设备唯一ID、产线主密钥的一部分、随机盐。合成后直接写入安全Flash区并开启保护。这里不要写入任何应用逻辑。第二阶段应用固件通过Bootloader方式下载Bootloader在上电时从安全区读取根密钥派生会话密钥再对这些密文固件进行解密。这样即使应用固件被读走没有根密钥也无法得到明文代码。这个方案的好处是根密钥只在产线Master设备及Bootloader中存在不在应用代码里即使应用层被完全逆向攻击者也拿不到根密钥。5.2 轮换机制与设备生命周期管理IoT设备投入运行后密钥不能一成不变。我在写云端上报协议时会在设备首次注册时生成设备证书和会话密钥以后每次连接都会进行密钥协商。PIC的Crypto Engine在密钥协商过程中主要负责对称加密部分非对称部分仍需要额外的库支持。如果产品对安全等级要求特别高可以选择PIC32CM系列它支持ARM TrustZone能把密钥协商、证书存储和对称加密放在不同的安全域里隔离性更好。5.3 功耗优化什么时候开、什么时候关Crypto EngineCrypto Engine作为外设也有功耗开销。在电池供电的设备里建议在进入休眠前先将Crypto模块关闭唤醒后重新初始化。MCC生成的代码里有一个Crypto_Deinitialize()或类似接口我一般会配合低功耗模式调用。同时注意TRNG模块的功耗要比AES引擎高一些不要在每次加密前都重新初始化TRNG。正式产品里我的习惯是开机时初始化一次TRNG生成一批随机数缓存在RAM中后续加密时按需取用用完再请求补充。5.4 选型对比与推荐思路我整理了一份带Crypto Engine的PIC常见系列对比表方便你根据自己的项目做选型系列位数最高主频加密引擎能力典型定位PIC18F-K428位64MHzAES-128/256 TRNG低成本传感器、家电、门锁PIC18F-Q438位64MHzAES TRNG CRC需要更多外设的工业控制PIC32CM32位 Cortex-M048MHzAES TRNG TrustZone安全认证较高的IoT节点PIC32MZ DA32位 MIPS200MHzAES/3DES/SHA TRNG网关、HMI、视频流加密我的建议是如果只是做节点的数据加密PIC18F-K42已经足够如果产品需要跑TLS协议栈需要更大内存和算力直接上PIC32MZ DA加密性能提升非常明显同时还有足够的资源跑图形界面和网络协议栈。6. 实际项目案例分享与经验总结6.1 低功耗传感器节点单颗MCU完成加密上报这是一个农业大棚监测节点的例子。以前用STM8加外部AES芯片设计复杂成本也偏高。后面整个方案换成PIC18F-K42Crypto Engine直接承担了所有加密工作。节点每隔10分钟采集温湿度数据大约32字节用AES-128-CBC加密后通过LoRa模块上报。主控平时睡在Sleep模式电流只有几微安。唤醒后初始化Crypto引擎加密数据发送完毕马上关Crypto并重新进入Sleep。整个加密过程约1毫秒功耗影响完全可以忽略。这个项目给我最大的启发是硬件加密引擎不只是“安全功能”也是一种功耗优化手段。以前软件AES要跑几百毫秒现在只需几毫秒CPU完成工作后能更快回到睡眠状态。6.2 工业网关多会话密钥管理与高速加密另一个项目是Modbus转以太网网关现场数据需要转发到云端。网关连接多台PLC每台PLC用不同会话密钥加密。虽然核心业务是协议转发但加密部分同样占了很大比重。过去在Cortex-M4上跑软件加密多会话切换时密钥加载频繁加密一块数据就要等待。换成PIC32MZ DA后硬件AES引擎直接做对称加密SHA引擎做摘要CPU只做协议解析和转发负载从80%降到20%左右。实际测试中处理100字节数据帧的加密耗时降低到原来的十分之一左右这个差距在数据量增大后更加明显。6.3 项目复盘我应该早点注意到的三个问题复盘这两个项目有几个决定在后期带来了不小的回报第一把密钥管理独立成模块而不是直接在外设初始化里写死。这样后续更换密钥或增加安全策略时改动范围很小。第二TRNG的使用需要在产线上做一次随机性自检。让产线测试员观察开机时设备上传的随机数是否有明显的规律性。虽然做不到严格的统计测试但至少能筛掉一批硬件异常。第三加密只是一个环节整体安全还需要配合代码保护、安全启动、安全更新等机制。Crypto Engine把最核心的加密性能和安全存储问题解决了但协议设计、密钥分发、固件更新这些还是要靠系统工程师仔细规划。7. 写在最后给新入坑工程师的几条心得如果你刚接触PIC的硬件加密引擎我的建议是不要急着写业务代码先花一晚上时间把MCC生成的crypto.c源码读一遍搞清楚每个寄存器的作用。这个模块跳过了后面排查问题会痛苦。自己搭一个最小实验环境MCU开发板加一块USB转串口把加密结果通过串口打印出来在PC上写一个Python提包也解密一下。能跑通这个链路之后再上复杂协议就有了底气。另外文档里不会明说的一点是开启代码保护和硬件加密之后调试难度会上升不少。建议开发阶段先把安全功能关掉模块调试完毕后再最后开启。不要一边调功能一边开保护出了bug都不知道从哪里下手。我在实战中踩过的坑都写在这篇文章里了希望你能少走些弯路。硬件加密引擎是个好工具但只有把它放在合理的安全架构里才能真正发挥价值。
返回列表