最近在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

树莓派启用i2c步骤1

然后选择I5 I2C这个选项,

树莓派启用i2c步骤2

按下去之后会问是否启用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命令(这是一个芯片上的控制指令),将芯片暂停。这时芯片即使超时,也不会清除上面说的很多状态寄存器。 之后再使用一个唤醒,就可以继续操作了。在进行内容很多的批次操作时一定要注意这一点。