ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Arduino实战:ATECC608A硬件加密芯片配置与AES-CBC加密

Arduino实战:ATECC608A硬件加密芯片配置与AES-CBC加密 上个月有个做智能门锁的客户拿着一块ESP32开发板找我说他的设备被人拆开后直接用烧录器把Flash里的固件整个读了出来然后在源码里找到了AES密钥所有通信数据全被解出来了。他问我有没有办法让密钥不放在Flash里我告诉他上ATECC608A这个问题基本能根治。这篇文章就从这个场景讲起记录我这几天在Arduino上配置ATECC608A、跑通AES-CBC加密的完整过程包括接线、选库、配置锁定、代码示例和踩坑排查希望能帮你少走弯路。1. 密钥锁进保险柜为什么IoT项目要上硬件加密芯片先说清楚一个问题我们平时在Arduino里写AES128 aes(keys);然后就把密钥keys[]直接塞在源码里这个密钥到底是安全的吗答案是一点都不安全。Arduino的Flash是可以通过ISP接口、JTAG调试口或者直接拆下芯片用编程器读出来的尤其很多廉价开发板连读保护都没开固件被扒出来只是时间问题。哪怕你做了加固、做了混淆密钥迟早会被逆向出来。这就是软件加密的死穴**加密算法本身是公开的安全完全依赖密钥保密而密钥又必须存在MCU里。芯片一旦被攻破密钥随之失守。**硬件安全芯片解决的就是这个“密钥放在哪才稳”的问题。ATECC608A把密钥和加解密运算都封装在一颗独立芯片里外部只能向它“提交数据”让它返回计算结果谁也没办法直接把它存着的密钥导出来。ATECC608A是Microchip微芯推出的一款加密协处理器支持AES-128、SHA-256、HMAC、ECC P256椭圆曲线签名/密钥交换自带真随机数发生器TRNG还内置了防篡改检测。最核心的一点是它有16个密钥槽Slot每个槽都可以被配置成“只能用来算、不能读出来”的安全状态。把密钥交给这颗芯片之后即便整个Arduino固件被脱走攻击者也拿不到密钥本身只能得到一个算不出有用结果的黑盒。在IoT场景里这枚芯片真的算是入门级硬件加密的标配了。云厂商的IoT硬件认证、各种智能门锁、加密通信网关、版权保护外设很多方案都内置了类似的安全芯片。而对Arduino玩家来说ATECC608A有现成的Breakout小板几十块钱一颗I2C接口直接接上就能用学习成本比折腾安全MCU低得多。这篇文章适合谁看如果你在做需要联网的传感器设备、智能家居控制器、或者任何要把“身份认证”和“数据加密”落地到实物的项目同时又受够了把密钥裸奔在Flash里的方案那这篇文章就是给你写的。读完你至少能搞清楚ATECC608A内部是怎么组织的、怎么把它从出厂状态配置成可用的安全品、以及怎么用AES-CBC模式真实地加密一组数据。2. 存储地图与锁定位ATECC608A的三种Zone到底管什么你拿到一颗ATECC608A光看外观就是个8脚芯片跟普通EEPROM差不多。它真正的玄机都在内部存储结构里所以配置之前至少要明白内部几个区域是干嘛的。2.1 Config Zone配置区——芯片的总闸门Config Zone是芯片最核心的一小块存储区芯片的I2C地址、运行模式、每个Slot的读写权限、锁定位的状态全部写在这里。芯片出厂时Config Zone是默认可写的我们可以通过I2C修改它。一旦把配置区锁定往后谁也别想再改。这块区域决定了“这枚芯片允许谁碰、允许碰什么”所以配置之前一定要做好备份锁死之后是没有后悔药的。2.2 Data Zone数据区——16个Slot存放密钥和业务数据Data Zone里面有16个Slot槽每个槽可以存放AES密钥、ECC私钥、SHA摘要或者普通数据。不同Slot还有各自的SlotConfig和KeyConfig用来决定这个槽能不能被外部读、能不能被外部写、能不能参与加密运算。比如我们后面要用的AES密钥槽就可以配置成“禁止外部读取但允许内部AES命令调用”。这种情况下就算芯片落到别人手里他们也没办法抄走这个密钥。2.3 OTP Zone一次性编程区——写一次就不许反悔OTP Zone跟名字一样写进去之后就不能再改适合存放设备序列号、校准参数这类需要永久固定的内容。实际项目里这个区域用得相对少但它在概念上要清楚因为有的配置工具会把它和Data Zone一起涉及。2.4 锁定位是安全的关键分水岭ATECC608A有三类锁定位这是新手最容易忽略的部分锁定位作用锁定后效果ConfigLock锁定Config Zone配置区不可再写入所有配置永久固定DataLock锁定Data ZoneSlot配置部分Slot的行为配置不可再改SlotLocked锁定单独某个Slot该Slot的写入权限被禁止密钥无法再被覆盖芯片出厂的时候这些锁全是开着的方便开发调试。但我们做成成品的时候必须把该锁的都锁上。否则攻击者可以自己写一段代码往你的密钥槽里覆盖新密钥或者改Slot配置把密钥读出来。锁定位全部置好后这枚芯片才真正从“开发样片”变成“安全品”。这里补充一个我实际操作中的体会很多人一上来就调用AES加密函数发现返回错误或者结果不对原因往往是没把槽配置成AES类型。ATECC608A的AES命令不是说你往任意槽写16字节就能加密的它要检查这个槽的KeyConfig是否允许AES运算。所以配置芯片这个前置工作无论如何也绕不过去。3. I2C接线与库选型动手之前先避开三个大坑ATECC608A本身支持I2C和单线SWI两种接口在Arduino生态里绝大多数场景都是用I2C。接线其实很简单但电压问题、地址问题和库的问题一旦踩中会让你怀疑是不是芯片坏了。3.1 接线用3.3V别用5V市面上常见的ATECC608A Breakout模块管脚不多通常就是VCC、GND、SDA、SCL四个脚有的还引出了A0/A1地址选择脚。参考接线如下芯片模块引脚接到Arduino Uno接到ESP32VCC3.3V3.3VGNDGNDGNDSDAA4GPIO21SCLA5GPIO22第一个大坑就是供电电压。**ATECC608A是3.3V器件绝对不要直接接5V。**如果你用的Uno的5V是干净的也许只烧一次模块更常见的是I2C通信极其不稳定扫描地址时有时无毫无头绪。Arduino Uno的I2C引脚本身是5V电平虽然很多3.3V模块的引脚有钳位保护实际能工作但从可靠性来说我还是建议用ESP32、MKR系列这类3.3V主控或者加一颗电平转换芯片。如果你就要用Uno那至少不要把VCC接5V。第二个坑是上拉电阻。ATECC608A模块的SDA/SCL大多自带2.2kΩ上拉如果你的模块没有需要在两条线上各加一个4.7kΩ上拉到3.3V。I2C在缺少上拉时扫描不到任何设备这是最常见的“芯片不响应”原因。3.2 I2C地址0x60还是0xC0ATECC608A的默认7位I2C地址是0x60对应8位写地址0xC0、8位读地址0xC1。很多库里的slave_address字段填的是0xC0这是8位写地址的值别看到0xC0就觉得和你扫出来的0x60对不上。如果你用I2C Scanner扫总线正常应该看到地址0x60。当然部分模块通过A0/A1跳线可以改成其他地址配置区里也有I2C_Address字段可以改但这些调整需要在锁配置之前完成。多设备共用总线时如果出现地址冲突最简单的办法是换一个不同地址的模块或者在配置区里改地址后重新锁定。3.3 库选型从Adafruit到cryptoauthlibArduino上操作ATECC608A的库主要有三个方向我先说结论再解释为什么库优点缺点适合谁Adafruit ATECC608A上手快API友好I2C封装好了AES等底层命令暴露有限侧重ECC快速测试、读取芯片信息ArduinoECCX08Arduino官方库支持多种安全芯片对ATECC608A的AES命令支持仍有限想用官方方案签名认证场景Microchip cryptoauthlib功能最全AES/ECC/随机数/配置命令全覆盖配置较复杂依赖较多做实际产品、深入配置、需要AES我的建议是**如果是做产品或者要完整用AES-CBC直接用Microchip cryptoauthlib。**它虽然配置起来稍微啰嗦但它把ATECC608A的全部能力都暴露出来了从读配置区到写密钥槽到AES加解密都有标准接口踩坑的资料也最多。Adafruit的库适合快速验证“芯片是不是好的、能不能读到序列号”但别指望它帮你完成所有AES操作。还有一个需要提醒的点你想在Wokwi这类在线仿真平台上跑通这个项目是做不到的因为ATECC608A不是逻辑仿真的普通传感器没有现成的仿真模型。这个项目必须拿到实体芯片来调。听起来麻烦但硬件加密本来就不可能靠仿真验证安全性。4. 首次配置与安全锁定从出厂裸片到可用AES密钥这一章是整个项目的核心。目标很明确让芯片从出厂默认状态变成一个“AES密钥已经放进去且读不出来”的安全品。4.1 出厂状态检查先确认芯片能用拿到芯片接好线第一件事是写一个最基础的I2C扫描程序确认总线上能找到0x60这个设备。找不到的话先别怀疑芯片检查上拉电阻、供电电压和接线顺序。找到设备之后用cryptoauthlib的atcab_read_serial_number()读取芯片序列号能正确读出来就说明通信链路没问题。你也可以用配置区读取命令把整个128字节的Config Zone读出来保存一份这就是你的原厂备份。这里强烈建议把配置区内容原样保存到一个文件里。我自己就因为没备份后面手贱改错了一个字节导致整颗芯片作废。ATECC608A不像STM32配置锁死之后没有“擦除重来”这种选项。4.2 写入AES槽的配置重点处理SlotConfig和KeyConfig出厂默认配置不一定适合AES。你需要把某个Slot通常用Slot 0配置成AES密钥槽。在ATECC608A的存储地图里每个Slot有两组重要配置SlotConfig决定外部能不能读写该槽KeyConfig决定该槽存放什么类型的密钥、允不允许AES操作。手动改这些字节很容易出错因为ATECC608A的配置项之间有关联。比如KeyConfig里写错密钥类型后面AES命令就会返回执行失败SlotConfig里写错权限可能写入密钥失败或者读保护没生效。我建议不要手撸16进制的配置数组直接用Microchip官方配置工具生成一个配置模板再通过cryptoauthlib的atcab_write_config_zone()写入。配置写入之后的验证也很重要。你可以先不锁配置区用atcab_read_config_zone()把配置再读出来对比一下和你写进去的是不是一致。如果发现某些字节被忽略或者报错先检查这个字节是不是保留位或者被芯片保护的区域。4.3 生成AES密钥并写入Slot密钥是怎么进到芯片里的有几种方式可以用伪随机数在主控端生成16字节也可以通过芯片自带真随机数发生器生成。我习惯用芯片自己的随机数发生器这样密钥没有在I2C总线上以明文形式出现过实际上写入时还是会以明文传到芯片但不会落到Flash里。写入时需要注意Slot当前是否允许写入。如果你严格按照配置模板把Slot配置成“不可写”那第一次写入密钥就会失败。正确顺序是先用工厂配置或临时配置把Slot设置成可写写入密钥后再改配置并锁定。更稳妥的做法是把“写密钥”和“锁配置”放在同一个烧录流程里中间不保存密钥明文。4.4 安全锁定与最终验证密钥写进去之后接下来就是上锁。顺序很重要先锁Config Zone再锁Data Zone。atcab_lock_config_zone()和atcab_lock_data_zone()两条命令执行完芯片的安全模式才真正生效。锁定之后可以做一个验证尝试用atcab_read_zone()直接读密钥槽正常情况应该返回“操作被拒绝”的错误。如果你能读到密钥内容说明SlotConfig没配好赶紧回去重新检查。我后来在量产流程里总结了一个比较靠谱的“配置检查清单”分享给你供电电压是3.3V吗I2C地址扫描到了0x60吗配置区原厂备份保存在安全位置了吗Slot的KeyConfig已经改成AES类型了吗密钥写入后尝试读取被拒绝了吗ConfigLock和DataLock已经置位了吗全部对勾打满芯片才算是可以放心交给用户的状态。5. AES-CBC实战用芯片加密传感器数据并送出串口讲完配置终于到了标题里说的AES-CBC应用。我会用一个实际例子DHT22温度传感器采集温度经过ATECC608A加密后通过串口发送PC端用Python解密验证。5.1 加密流程CBC模式的实现思路首先必须明确一点ATECC608A的AES引擎一次只能处理一个16字节分组。AES-CBC模式里的“链接”逻辑前一个密文块与当前明文块异或是在主控端实现的芯片本身不帮你串起来。这就好比芯片是一个算力极强的计算器但组合运算还得你自己编排。CBC加密的过程可以用这个公式表达C0 AES_Encrypt(key, P0 ⊕ IV)Ci AES_Encrypt(key, Pi ⊕ C(i-1))解密过程对称反过来P0 AES_Decrypt(key, C0) ⊕ IVPi AES_Decrypt(key, Ci) ⊕ C(i-1)数据长度如果不是16字节整数倍需要填充。标准做法是PKCS#7缺几个字节就补几个字节补的数值等于缺的字节数。5.2 Arduino端调用芯片完成单块加密下面我用cryptoauthlib风格的API写一个示例主要展示结构具体函数名以你使用的库为准。#include cryptoauthlib.h ATCAIfaceCfg cfg { .iface_type ATCA_I2C_IFACE, .devtype ATECC608A, .atcai2c.slave_address 0xC0, .atcai2c.bus 0, .atcai2c.baud 100000, }; #define AES_SLOT 0 void setup() { Serial.begin(115200); if (atcab_init(cfg) ! ATCA_SUCCESS) { Serial.println(init failed); while (1); } } void encryptCBC(uint8_t *plaintext, uint16_t len, uint8_t *iv, uint8_t *ciphertext) { // 计算填充后的长度 uint16_t pad 16 - (len % 16); uint16_t paddedLen len pad; uint8_t prev[16]; memcpy(prev, iv, 16); for (uint16_t i 0; i paddedLen / 16; i) { uint8_t block[16]; if (i * 16 16 len) { // 最后一个块可能是填充块全部填充字节值pad memset(block, pad, 16); memcpy(block, plaintext i * 16, len - i * 16); } else { memcpy(block, plaintext i * 16, 16); } // CBC链接明文块与前一块密文异或 for (int j 0; j 16; j) { block[j] ^ prev[j]; } // 调用芯片加密单块 atcab_aes_encrypt(AES_SLOT, 0, block, ciphertext i * 16); memcpy(prev, ciphertext i * 16, 16); } } void loop() { // 模拟从DHT22读到的16字节传感器数据 uint8_t sensorData[16]; snprintf((char *)sensorData, sizeof(sensorData), TEMP:%02d.%d, 26, 5); // 生成随机IV uint8_t iv[16]; atcab_random(iv); // 加密 uint8_t ciphertext[32]; encryptCBC(sensorData, 16, iv, ciphertext); // 通过串口发送 Serial.print(IV); for (int i 0; i 16; i) { Serial.printf(%02X, iv[i]); } Serial.print( CT); for (int i 0; i 32; i) { Serial.printf(%02X, ciphertext[i]); } Serial.println(); delay(5000); }很多朋友刚开始会写错填充比如数据正好是16字节倍数时就忘了补充一整块结果解密端最后多出一块垃圾数据。我这里为了演示数据长度固定为16字节但代码里依然保留PKCS#7的逻辑这样你扩展到任意长度都不会出错。另外要提醒的是ATECC608A做一次AES加密需要几毫秒到十几毫秒取决于I2C总线和芯片状态。对于传感器数据这种低频传输完全够用但如果你要加密音视频流这颗芯片的定位就不合适应该选带AES引擎的MCU或者更高级别的安全芯片。5.3 PC端解密用Python验证链路串口发出的数据格式是IVxxxx CTxxxxPC端收到后需要重新拼出IV和密文。下面的Python脚本用pycryptodome解密from Crypto.Cipher import AES from Crypto.Util.Padding import unpad # 注意纯演示用真实项目密钥不应放在PC端明文脚本里 key bytes([0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F]) line input(paste serial line here: ) iv_hex line.split( IV)[1].split( CT)[0] ct_hex line.split( CT)[1] iv bytes.fromhex(iv_hex) ciphertext bytes.fromhex(ct_hex) cipher AES.new(key, AES.MODE_CBC, iv) plaintext unpad(cipher.decrypt(ciphertext), AES.block_size) print(Decrypted:, plaintext.decode())跑通之后你会看到串口发过来的加密数据成功还原成TEMP:26.5。这时候你再回头想开头那个门锁客户的场景即便有人扒了固件他不知道密钥也拿不到IV对应的明文通信数据在他眼里就是一堆花里胡哨的乱码。有一点必须强调我这个脚本为了演示方便把密钥直接写在了PC端。真实项目里密钥如果出现在PC脚本里那整个链路的安全性就回到了原点。解密端也要有对应的安全存储方案比如把密钥放在另一颗ATECC608A里通过芯片的ECC或AES能力解密或者放在硬件加密机上。6. 两天踩坑记录从I2C扫描失败到配置锁死的完整排查写这篇博文的时候我特意把踩过的坑整理成了一份排查链路因为这些问题每一个都让我在工位上白坐了半天。记录下来希望你遇到时能直接定位。6.1 坑一I2C扫描不到0x60模块像死了一样现象I2C Scanner跑完整个地址段什么都没扫到。排查过程第一步量VCC和GND之间的电压发现模块VCC有4.98V。问题就在这我把杜邦线插错位置接到了Uno的5V。ATECC608A对5V非常敏感轻则工作异常重则永久损坏。第二步换到3.3V依然扫描不到。这时候我开始怀疑芯片烧了但冷静下来先检查SDA/SCL是否接反结果发现两个线确实搞反了。第三步换回来之后还是没反应最后检查模块背面才发现SDA和SCL引脚旁边有一组焊盘需要短路焊锡选择I2C模式有些模块默认是SWI单线模式。把跳线改过来设备立刻出现在0x60。排查链路供电电压 → 引脚顺序 → 板载跳线模式。这个顺序很重要最贵的芯片往往是被最后这个低级问题害的。6.2 坑二配置区写进去读出来对不上现象用atcab_write_config_zone()写入配置数组之后再读出来发现有几个字节变了。排查过程我最初以为芯片坏了后来查数据手册发现ATECC608A的Config Zone里有几个字节是芯片出厂固定值比如序列号、修订号这几块区域是写不进去的你读出它们时永远是原值。所以写入之前最好做一次“读原厂配置→在你需要的地方打补丁→写回”而不是拿一个网上找的模板直接覆盖。直接覆盖模板会把出厂序列号之类的信息弄乱后面想用序列号做设备认证就会出现不一致。6.3 坑三密钥写完读出来是FF或者读取直接被拒绝现象我从Slot 0读密钥返回全是0xFF换一个Slot读直接被拒绝。排查过程这里其实分两种情况。读出来是0xFF表示这个槽现在是空槽或者已经写保护了读取被拒绝才说明SlotConfig里的读保护生效了。我这个坑恰恰是因为第一次写入密钥时Slot配置还允许读写写入后我又改了配置锁上了导致后面读不到。一开始我以为流程有问题后来才意识到这正是芯片在正常履行它的安全职责。“读不出来”不是bug是特性。芯片锁定后应该读不出来密钥如果还能读出来说明你的安全配置是失败的。6.4 坑四调用AES加密返回失败但明明已经写入密钥现象atcab_aes_encrypt()返回错误码换一个密钥槽也一样。排查过程我花了很长时间去检查I2C时序、供电、库版本最后定位到KeyConfig里的密钥类型没配对。ATECC608A的AES命令只会对KeyConfig中标记为AES类型的槽生效而我的配置模板是官方ECC签名示例那个槽是ECC私钥类型。把该槽的KeyConfig改成AES密钥类型重新生成密钥并锁定AES加解密一次通过。这个坑尤其隐蔽因为芯片不会告诉你“这个槽不是AES类型”它只给你一个模棱两可的命令失败码。6.5 坑五配置锁死之后想改地址整颗芯片报废现象我把ConfigLock锁上之后突然想改I2C地址结果发现改不了。咨询了朋友才知道这属于芯片的正常安全行为而且配置锁只能锁一次没有解锁通道。排查过程这个算不上排查纯粹是我自己的操作失误。我本来计划用两颗芯片分别连在同一个I2C总线上图省事先上了锁等要改地址时才意识到必须在锁定之前完成。最后只能换一颗新芯片重新走配置流程。这件事给我的教训是**配置ATECC608A之前务必将所有可能需要调整的项列一个清单逐项确认后再锁。**宁可多花十分钟检查备份也不要锁完再后悔。7. 这枚芯片还能怎么玩认证、OTA与安全启动的进阶思路AES-CBC只是ATECC608A能力的冰山一角。当你把配置流程吃透之后这枚芯片能在更多场景里成为项目的“信任根”。最常见的进阶用法就是挑战-应答认证Challenge-Response Authentication。芯片里存了一个私钥主机端存对应的公钥。设备上电时主机发一段随机数给设备设备用ATECC608A里的私钥对随机数做ECDSA签名主机验签通过后设备才被信任。这种方案不需要把私钥传给设备设备端也不用保存任何机密信息非常适合防固件克隆和配件认证。另一个实用方向是安全OTA。固件包先用AES密钥加密密钥存在ATECC608A里。MCU联网下载到的是密文固件需要ATECC608A解密后才能写入Flash。即使攻击者截获了整个OTA包没有芯片里的密钥也解不开。这种方式配合安全启动还能进一步防止固件被篡改。在考虑合规项目时还需要留意不同地区对密码算法有不同要求。国密领域的SM2/SM3/SM4算法在很多物联网设备中也有应用ATECC608A本身主打国际算法体系如果项目有国密合规要求需要另外评估硬件平台。这里不展开讲政策只提醒大家选型时把算法兼容性放进评估清单。最后分享一点个人心得做硬件加密这件事最大的敌人不是攻击者多聪明而是你自己觉得“差不多了吧”。我见过很多所谓加了密的设备密钥就存在同一条I2C总线上的一颗普通EEPROM里攻击者把两颗芯片都焊下来一分钟就能读出来。ATECC608A这种独立安全芯片至少给你划了一条清晰的物理边界密钥在里面谁也别想碰。把配置流程跑通、把该锁的锁死、把密钥管理好你的设备才算真正有了一扇能关上的门。以后再做IoT项目我都会默认加一颗这个芯片成本不高但省心很多。
返回列表