RIOT 中 PSA Crypto EdDSA(Ed25519)配置与测试实战:从模块裁剪到源码级验证
【免费下载链接】RIOTRIOT - The friendly OS for IoT项目地址: https://gitcode.com/GitHub_Trending/riot/RIOT
本篇技术指南以 RIOT 仓库中的 tests/sys/psa_crypto_eddsa 配置测试为核心,讲解如何仅启用 PSA Crypto 模块中的 EdDSA(Ed25519 曲线)功能,完成密钥生成、签名、验签的完整流程,并介绍后端自动选择机制、内存配置要点与自动化测试方法。读完本文,你将能够在自己的 RIOT 应用中按需裁剪 PSA Crypto 模块、理解psa_asymmetric_ecc_ed25519的后端解析逻辑,并掌握用testrunner验证签名结果正确性的实践方案。
测试定位:为什么单独为 EdDSA 建一个测试
tests/sys/psa_crypto_eddsa/README.md 明确指出,这是一个仅针对 PSA Crypto 模块中 eddsa 功能的配置测试(configuration test),其内容基于 examples/advanced/psa_crypto 示例演化而来。
RIOT 的 PSA Crypto 实现支持 AES、HMAC、ECDSA、EdDSA 等多种运算,而本例刻意把范围收敛到 EdDSA 一项,其目的是:
- 验证模块级裁剪是否可行:PSA Crypto 采用"基础模块 + 按操作拆分"的架构,本测试证明只选
psa_asymmetric与psa_asymmetric_ecc_ed25519两个模块即可独立完成 Ed25519 签名运算,无需引入对称加密、哈希等其他功能模块; - 验证后端自动解析:Ed25519 在支持硬件加速的平台上自动走
periph_ecc_ed25519后端,否则回退到软件实现(monocypher),这一依赖关系在 sys/psa_crypto/Makefile.dep 中有明确体现; - 作为配置正确性的回归保障:通过测试框架自动断言程序输出 "All Done",确保模块配置组合在 CI 环境中持续可用。
最小化模块配置:Makefile 剖析
测试的构建配置位于 tests/sys/psa_crypto_eddsa/Makefile,内容非常精简,是理解 PSA Crypto 模块依赖关系的绝佳样例:
include ../Makefile.sys_common USEMODULE += ztimer USEMODULE += ztimer_usec USEMODULE += psa_crypto USEMODULE += psa_asymmetric USEMODULE += psa_asymmetric_ecc_ed25519 CFLAGS += -DCONFIG_PSA_ASYMMETRIC_KEYPAIR_COUNT=1 CFLAGS += -DCONFIG_PSA_SINGLE_KEY_COUNT=1 CFLAGS += -DTHREAD_STACKSIZE_MAIN=4096 include $(RIOTBASE)/Makefile.include基础模块与依赖链
psa_crypto:PSA Crypto 基础模块,是任何 PSA 运算的前提。根据 sys/psa_crypto/Makefile.dep 的依赖规则,它会自动附带random与prng_sha256prng(密钥生成需要随机数):ifneq (,$(filter psa_crypto,$(USEMODULE))) USEMODULE += random USEMODULE += prng_sha256prng endifpsa_asymmetric:非对称运算基模块,依赖规则为USEMODULE += psa_key_management,进而再依赖psa_key_slot_mgmt(见 sys/psa_crypto/Makefile.dep 与 L498-L501),即密钥槽管理是签名运算的隐含前置。psa_asymmetric_ecc_ed25519:Ed25519 运算模块,是本测试的核心。
后端自动解析:硬件优先,软件兜底
这是整个测试最有价值的部分。查看 sys/psa_crypto/Makefile.dep 的 Ed25519 分支:
## ECC_ED25519 backend ifneq (,$(filter psa_asymmetric_ecc_ed25519,$(USEMODULE))) ifeq (,$(filter psa_asymmetric_ecc_ed25519_custom_backend,$(USEMODULE))) FEATURES_OPTIONAL += periph_ecc_ed25519 include $(RIOTMAKE)/features_check.inc.mk ifneq (,$(filter periph_ecc_ed25519,$(FEATURES_USED))) USEMODULE += psa_asymmetric_ecc_ed25519_backend_periph else USEMODULE += psa_asymmetric_ecc_ed25519_backend_monocypher endif endif endif ifneq (,$(filter psa_asymmetric_ecc_ed25519_backend_c25519,$(USEMODULE))) USEPKG += c25519 USEMODULE += psa_c25519 USEMODULE += psa_c25519_edsign endif ifneq (,$(filter psa_asymmetric_ecc_ed25519_backend_monocypher,$(USEMODULE))) USEPKG += monocypher USEMODULE += psa_monocypher USEMODULE += psa_monocypher_ed25519 endif ifneq (,$(filter psa_asymmetric_ecc_ed25519_backend_periph,$(USEMODULE))) FEATURES_REQUIRED += periph_ecc_ed25519 endif逻辑解读:
- 只要用户选了
psa_asymmetric_ecc_ed25519且没有显式指定psa_asymmetric_ecc_ed25519_custom_backend,构建系统就声明一个可选特性periph_ecc_ed25519; - 若目标板提供该硬件加速特性(
FEATURES_USED命中),则启用psa_asymmetric_ecc_ed25519_backend_periph(外设硬件加速后端); - 若目标板不提供该特性,则自动回退到软件后端
psa_asymmetric_ecc_ed25519_backend_monocypher,该模块通过USEPKG += monocypher引入 monocypher 密码库,并启用psa_monocypher/psa_monocypher_ed25519; - 除默认路径外,还支持显式选择
psa_asymmetric_ecc_ed25519_backend_c25519(引入 c25519 包与psa_c25519_edsign)。
因此,同一份 Makefile 在native平台会编译 monocypher 软件实现,在具备硬件加速的板上则自动切换为 periph 后端,无需改动任何应用代码。这也印证了 examples/advanced/psa_crypto/README.md 中"不指定任何配置时,PSA Crypto 会依据目标平台自动选择默认后端"的描述。
所有可选模块名(伪模块)都登记在 sys/psa_crypto/Makefile.include,并且该文件还强制了"一个算法必须且只能选一个后端"的约束:
ifneq (,$(filter psa_asymmetric_ecc_ed25519,$(USEMODULE))) ifneq (1,$(call backends, psa_asymmetric_ecc_ed25519)) $(error "One (and only one) backend should be selected for psa_asymmetric_ecc_ed25519") endif endif关键内存配置项
Makefile 中的三个编译期宏对应 PSA Crypto 的资源配置:
| 宏 | 取值 | 作用 |
|---|---|---|
CONFIG_PSA_ASYMMETRIC_KEYPAIR_COUNT | 1 | 非对称密钥对槽位数量。EdDSA 测试中需要 1 个私钥(签名),故设为 1 |
CONFIG_PSA_SINGLE_KEY_COUNT | 1 | 单钥槽位数量。用于存放导入的公钥(PSA_KEY_TYPE_PUBLIC_KEY_OF_KEY_PAIR归入单钥槽),故设为 1 |
THREAD_STACKSIZE_MAIN | 4096 | 主线程栈大小。软件后端(如 monocypher)在栈上做运算时需要更大的栈空间 |
依据 sys/psa_crypto/doc.md 的说明,PSA 内部把密钥槽分为三类:Single Key Slot(单钥/非结构化数据)、Asymmetric Key Slot(本地非对称密钥对)、Protected Key Slot(安全元件或硬件保护内存中的密钥)。本例的example_eddsa.c同时生成了私钥对并导入了公钥,正好同时触及前两类槽位,因此两个计数都需要显式配置。
需要特别说明的是:槽位数量是 RAM 中缓存的密钥上限,不代表持久化存储的密钥总数上限(本实现暂不支持持久化,见 sys/psa_crypto/doc.md 的说明)。Makefile.ci中列出的nucleo-c031c6、nucleo-l011k4、samd10-xmini、stm32f030f4-demo等小内存板卡被标记为BOARD_INSUFFICIENT_MEMORY(见 Makefile.ci),正是因为这些板卡的 RAM 不足以承载完整的 PSA Crypto + 签名栈。
完整 EdDSA 流程:example_eddsa.c 逐段解析
测试的核心运算实现在 example_eddsa.c,文件头注明其内容复制自examples/advanced/psa_crypto。它演示了使用 PSA Crypto API 完成一次 Ed25519 签名的标准五步走。
算法与密钥参数定义
#define EDDSA_MESSAGE_SIZE (127) #define ECC_KEY_SIZE (255) #define ECC_KEY_TYPE (PSA_KEY_TYPE_ECC_KEY_PAIR(PSA_ECC_FAMILY_TWISTED_EDWARDS)) #define ECC_ALG (PSA_ALG_PURE_EDDSA)- 曲线族为Twisted Edwards(
PSA_ECC_FAMILY_TWISTED_EDWARDS),即 Edwards25519; - 算法标识为纯 EdDSA(
PSA_ALG_PURE_EDDSA),对应 RFC 8032 中的 PureEdDSA 模式(与先哈希再签名的 Ed25519ph 相对); - 密钥位长 255 bit,待签名消息固定为 127 字节(
msg数组初始化为0x0b)。
第一步:配置私钥属性并生成密钥对
psa_key_usage_t usage = PSA_KEY_USAGE_SIGN_MESSAGE | PSA_KEY_USAGE_VERIFY_MESSAGE; psa_set_key_algorithm(&privkey_attr, ECC_ALG); psa_set_key_usage_flags(&privkey_attr, usage); psa_set_key_type(&privkey_attr, ECC_KEY_TYPE); psa_set_key_bits(&privkey_attr, ECC_KEY_SIZE); status = psa_generate_key(&privkey_attr, &privkey_id);psa_key_attributes_t结构必须先用psa_key_attributes_init()清零初始化——依据 sys/psa_crypto/doc.md,未初始化的结构会被 PSA 视为active而无法用于新操作。psa_generate_key()由 PSA 在内部完成随机密钥生成,私钥材料不会暴露给应用层(这正是 PSA Crypto 密钥管理设计的关键点)。
第二步:导出公钥
uint8_t public_key[PSA_EXPORT_PUBLIC_KEY_OUTPUT_SIZE(ECC_KEY_TYPE, ECC_KEY_SIZE)] = { 0 }; size_t pubkey_length; status = psa_export_public_key(privkey_id, public_key, sizeof(public_key), &pubkey_length);缓冲区大小由宏PSA_EXPORT_PUBLIC_KEY_OUTPUT_SIZE(ECC_KEY_TYPE, ECC_KEY_SIZE)在编译期计算,避免手动估算出错。
第三步:以公钥属性导入公开密钥
psa_set_key_algorithm(&pubkey_attr, ECC_ALG); psa_set_key_usage_flags(&pubkey_attr, PSA_KEY_USAGE_VERIFY_MESSAGE); psa_set_key_bits(&pubkey_attr, PSA_BYTES_TO_BITS(pubkey_length)); psa_set_key_type(&pubkey_attr, PSA_KEY_TYPE_PUBLIC_KEY_OF_KEY_PAIR(ECC_KEY_TYPE)); status = psa_import_key(&pubkey_attr, public_key, pubkey_length, &pubkey_id);注意这里不能直接复用私钥属性:公钥的用途标记仅为PSA_KEY_USAGE_VERIFY_MESSAGE(不能签名),类型用PSA_KEY_TYPE_PUBLIC_KEY_OF_KEY_PAIR()派生,位数则根据导出的实际长度经PSA_BYTES_TO_BITS()换算。
第四、五步:签名与验签
status = psa_sign_message(privkey_id, ECC_ALG, msg, sizeof(msg), signature, sizeof(signature), &sig_length); return psa_verify_message(pubkey_id, ECC_ALG, msg, sizeof(msg), signature, sig_length);签名使用私钥privkey_id,验签使用导入的公钥pubkey_id,算法均为ECC_ALG。example_eddsa()的返回值即psa_verify_message()的结果,只有当签名通过验证时才返回PSA_SUCCESS,这使整个函数天然具备"自校验"能力。
运行入口与计时:main.c 与 ztimer
main.c 是标准的 RIOT 测试入口:
#include "psa/crypto.h" #include "ztimer.h" extern psa_status_t example_eddsa(void); int main(void) { bool failed = false; psa_status_t status; ztimer_acquire(ZTIMER_USEC); ztimer_now_t start = ztimer_now(ZTIMER_USEC); start = ztimer_now(ZTIMER_USEC); status = example_eddsa(); printf("EdDSA took %d us\n", (int)(ztimer_now(ZTIMER_USEC) - start)); if (status != PSA_SUCCESS) { failed = true; printf("EdDSA failed: %s\n", psa_status_to_humanly_readable(status)); } ztimer_release(ZTIMER_USEC); if (failed) { puts("Tests failed..."); } else { puts("All Done"); } return 0; }实现要点:
- 用
ztimer_acquire(ZTIMER_USEC)/ztimer_release(ZTIMER_USEC)获取微秒级计时器(对应 Makefile 中的ztimer与ztimer_usec模块),测量单次 EdDSA(生成密钥 + 导出/导入公钥 + 签名 + 验签)的整体耗时并打印; - 失败时通过
psa_status_to_humanly_readable(status)输出人类可读的错误状态,便于定位; - 无论成功失败,最终都会输出
All Done或Tests failed...,供测试框架断言。
从源码结构可以推断,计时输出同时起到了"后端性能对比"的辅助作用——examples/advanced/psa_crypto/README.md 中给出了不同后端的预期运行时间对比(例如硬件 CryptoCell 310 与软件实现、ATECC608A 安全元件之间的数量级差异),本例沿用这一模式,便于开发者在实际板卡上核对后端是否按预期生效。
运行与自动化验证
手工运行
在任意支持 PSA Crypto 的平台上编译并烧录:
make -C tests/sys/psa_crypto_eddsa BOARD=native flash term使用真实硬件时替换BOARD,例如BOARD=nrf52840dk(该板具备硬件加速后端)。程序启动后会打印类似EdDSA took xxx us的耗时信息,并以All Done结束表示签名-验签链路全部成功。
自动化测试
测试脚本 基于 RIOT 的testrunner框架:
#!/usr/bin/env python3 import sys from testrunner import run def testfunc(child): child.expect_exact('All Done') print("[TEST PASSED]") if __name__ == "__main__": sys.exit(run(testfunc))脚本的核心断言只有一行:期望串口输出精确匹配All Done。由于main.c只有在psa_generate_key、psa_export_public_key、psa_import_key、psa_sign_message、psa_verify_message全部返回PSA_SUCCESS时才会走到该分支,因此这一断言实质上覆盖了整条 EdDSA 链路。配合测试框架运行:
make -C tests/sys/psa_crypto_eddsa BOARD=native flash test脚本退出码为 0 表示通过,输出[TEST PASSED]。
移植到自己应用中的配置清单
综合以上分析,若要在自己的 RIOT 应用中只启用 PSA Crypto 的 EdDSA 功能,最小配置如下:
# 基础模块(必须) USEMODULE += psa_crypto USEMODULE += psa_asymmetric USEMODULE += psa_asymmetric_ecc_ed25519 # 后端:默认自动选择(硬件 periph 优先,否则 monocypher 软件实现) # 也可显式指定: # USEMODULE += psa_asymmetric_ecc_ed25519_custom_backend # USEMODULE += psa_asymmetric_ecc_ed25519_backend_c25519 # 或 # USEMODULE += psa_asymmetric_ecc_ed25519_backend_monocypher # 密钥槽配置(按需调整) CFLAGS += -DCONFIG_PSA_ASYMMETRIC_KEYPAIR_COUNT=1 CFLAGS += -DCONFIG_PSA_SINGLE_KEY_COUNT=1 # 软件后端运算需要更大的栈 CFLAGS += -DTHREAD_STACKSIZE_MAIN=4096若偏好 Kconfig 方式,可在应用目录的app.config.test中写成:
CONFIG_MODULE_PSA_CRYPTO=y CONFIG_MODULE_PSA_ASYMMETRIC=y CONFIG_MODULE_PSA_ASYMMETRIC_ECC_ED25519=y并在menuconfig的System -> PSA Crypto菜单下调整密钥槽数量(对应CONFIG_PSA_ASYMMETRIC_KEYPAIR_COUNT、CONFIG_PSA_SINGLE_KEY_COUNT)。更多可用模块与后端清单参见 sys/psa_crypto/doc.md 的 "Available Modules" 章节。
小结
tests/sys/psa_crypto_eddsa虽是一个规模很小的配置测试,却完整承载了 RIOT PSA Crypto 模块化设计的核心思想:通过psa_asymmetric+psa_asymmetric_ecc_ed25519两个模块即可独立启用 Ed25519 签名能力,后端由构建系统依据板级特性自动在硬件加速与 monocypher 软件实现之间切换。其 Makefile 是模块裁剪与密钥槽配置的现成范本,example_eddsa.c 则是 PSA Crypto 密钥生成、导入、签名、验签的标准 API 用法,配合 main.c 的 ztimer 计时与 testrunner 断言,形成了一套可复用的"最小 EdDSA 验证闭环"。读者可直接将本文的配置清单与代码模式移植到自己的安全启动、固件签名校验、消息认证等场景中。
【免费下载链接】RIOTRIOT - The friendly OS for IoT项目地址: https://gitcode.com/GitHub_Trending/riot/RIOT
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考