最近在AI的帮助下学习了MicroChip的一款CryptoAuthentication系列的芯片,ATSHA204A。
想要学习CryptoAuthentication芯片的心思早就有了,很多年前就看好并且囤积了很多ATECC608B的芯片,这是个系列中最新的版本,功能也最全面。但是缺点也来了,学习曲线颇陡。 于是我打算从简单一点的版本开始下手。ATECC系列芯片,其实是早期Atmel在被微芯收购之前就有一些基础型号了。 这个ATSHA就是它的雏形。后面ATECC也还是向下兼容这个ATSHA的设计的,所以学到的东西以后也有用。 ATSHA功能较少,用法也比较简单,学起来更容易。
ATSHA204的基本功能,就是计算SHA-256,这也是名字里SHA的来由。但是这个计算不是简单的硬件加速,它的设计主要是用SHA-256、以及从这个算法搭出来的很多结构,以及它能秘密地存储密钥不被读出的特殊功能,实现认证。 认证可以是两个芯片之间,也可以是芯片和服务器主机之间,这样就可以为物联网等应用提供很好的辅助。
封装和型号
ATSHA204的芯片有很多封装,比如SOIC-8,TSSOP或者UDFN的迷你颗粒……而按照通信协议分,又有i2c和单线协议1-wire两种。 i2c协议适合用来和常见的单片机,包括树莓派一起使用,而单线协议因为线路较少,可以用在接触式手环(看起来像纽扣电池那样)之类的地方,更加灵活。
如果想要自己购买ATSHA204芯片,可以去 mouser.de 之类的地方下单,上面说芯片封装和协议这块要注意,比如ATSHA204A-SSHDA后面的这个-SSHDA,就是SOIC-8封装、i2c协议的版本。如果搞错了就需要重新想办法与电路连接。 后面一般还会有-B和-T的区别,主要是管状包装还是袋子包装,这个区别不大。
搭建基本的调试平台
我在调试的时候使用了树莓派2,看中了树莓派比较容易连接,也容易使用Python进行调试的特点。
i2c芯片和树莓派连接只需要四根线:Vcc,SDA,SCL,GND,就是电源、数据、时钟和接地。要注意的是,ATSHA芯片用的是3.3V的供电,在树莓派上有相应的电源,但是不要接到5V去了。 我买到了Mikroe的树莓派-MikroBUS接口板,和CryptoAuthentication系列芯片的调试插件,这样会更容易一些,只要把芯片卡进调试插件的弹簧插座里就很好,而且多余的另一个空白接口可以接逻辑分析仪。

在树莓派里,则要使用 sudo raspi-config 指令,配置一下系统,启用i2c接口。首先选择3 Interface Options

然后选择I5 I2C这个选项,

按下去之后会问是否启用i2c内核模块,选Yes即可。
最基本的代码:使用i2c通信
在python上使用i2c通信,可以使用smbus2这个库。 它的基本用法,在AI的辅助下我写了一个这样的类即可使用。但是ATSHA和ATECC系列芯片有一个特别的地方,需要单独说一下。
#!/usr/bin/env python3
from smbus2 import SMBus, i2c_msg
import time
class I2CBus:
def __init__(self, bus_num, addr, wake_method="i2c", twhi_ms=2.5):
self.bus = SMBus(bus_num)
self.addr = addr
self.wake_method = wake_method
self.twhi = twhi_ms / 1000.0 # 唤醒脉冲后到可通信前的等待时间
self._pi = None
if wake_method == "gpio":
import pigpio
self._pi = pigpio.pi()
if not self._pi.connected:
raise RuntimeError("pigpio 守护进程没运行: sudo pigpiod")
def write(self, data):
return self.bus.i2c_rdwr(i2c_msg.write(self.addr, list(data)))
def idle(self):
return self.write(b'\x02')
def read(self, n, rstrip=False):
msg = i2c_msg.read(self.addr, n)
self.bus.i2c_rdwr(msg)
msg = bytes(msg)
if rstrip:
msg = msg.rstrip(b'\xff')
return msg
def wake(self):
if self.wake_method == "gpio":
self._wake_gpio()
else:
self._wake_i2c()
time.sleep(self.twhi)
def _wake_i2c(self):
"""
通过向地址 0x00 写一个 0x00 字节, 让 SDA 在整个地址字节期间保持低电平,
从而产生唤醒脉冲 (tWLO, 最小约 60us)。
⚠ 这要求 I2C 时钟 <= ~125kHz。树莓派默认 100kHz 时一个字节约 80us, 正好够。
如果你把总线提到 400kHz, 这个方法会失败 —— 改用 --wake gpio。
设备(以及地址 0x00)不会 ACK, 抛 OSError 属正常, 忽略即可。
"""
try:
self.bus.i2c_rdwr(i2c_msg.write(0x00, [0x00]))
except OSError:
pass
time.sleep(0.01)
def _wake_gpio(self):
"""直接把 SDA(GPIO2) 拉低 ~0.1ms 再交还给 I2C。与总线时钟无关, 更稳。"""
import pigpio
SDA = 2
self._pi.set_mode(SDA, pigpio.OUTPUT)
self._pi.write(SDA, 0)
time.sleep(0.0001) # >=60us; 偏长也无害
self._pi.set_mode(SDA, pigpio.ALT0) # 交还给硬件 I2C, 上拉电阻把 SDA 拉高
def close(self):
try:
self.bus.close()
except Exception:
pass
if self._pi:
self._pi.stop()
def __enter__(self, *args, **argv):
return self
def __exit__(self, *args, **argv):
self.close()
唤醒
根据ATSHA204的手册,芯片有一个看门狗机制,在大概1.3秒没有动作之后,就会将芯片送入睡眠模式。 显然,一般来说芯片上电之后就会很快进入这个模式。它不会响应i2c总线上的读写操作,如果试图往i2c上它的地址发送信息,不会有响应。
需要使用手册上的办法,将芯片的SDA拉低大于60微秒,才能唤醒这个芯片。在i2c总线100kHz(树莓派2的默认性能)时,大概一个8比特的0x00发送过去就可以了。
这就是上面代码里面的__wake_i2c的设计。如果还不行,树莓派也可以把i2c的IO引脚暂时换成GPIO,强行输出0来做到这一点。但是一般用不到。
这个唤醒和睡眠的机制很重要。首先,睡眠机制会清除芯片里面的SRAM缓存,尤其是一个叫做tempkey的临时密钥存储器。这会干扰正在进行的密码操作。
另外,比如在进行SHA操作时送入大量数据,很可能1秒之内无法完成。一旦进入睡眠,芯片的SHA状态机就会重置,操作就失败了。
为了解决这个问题,手册上要求,在需要延长操作时间的时候,需要先用IDLE命令(这是一个芯片上的控制指令),将芯片暂停。这时芯片即使超时,也不会清除上面说的很多状态寄存器。 之后再使用一个唤醒,就可以继续操作了。在进行内容很多的批次操作时一定要注意这一点。