RIOT 中 PSA Crypto EdDSA(Ed25519)配置与测试实战:从模块裁剪到源码级验证
2026/9/20 12:28:59 网站建设 项目流程

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 一项,其目的是:

  1. 验证模块级裁剪是否可行:PSA Crypto 采用"基础模块 + 按操作拆分"的架构,本测试证明只选psa_asymmetricpsa_asymmetric_ecc_ed25519两个模块即可独立完成 Ed25519 签名运算,无需引入对称加密、哈希等其他功能模块;
  2. 验证后端自动解析:Ed25519 在支持硬件加速的平台上自动走periph_ecc_ed25519后端,否则回退到软件实现(monocypher),这一依赖关系在 sys/psa_crypto/Makefile.dep 中有明确体现;
  3. 作为配置正确性的回归保障:通过测试框架自动断言程序输出 "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 的依赖规则,它会自动附带randomprng_sha256prng(密钥生成需要随机数):
    ifneq (,$(filter psa_crypto,$(USEMODULE))) USEMODULE += random USEMODULE += prng_sha256prng endif
  • psa_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

逻辑解读:

  1. 只要用户选了psa_asymmetric_ecc_ed25519且没有显式指定psa_asymmetric_ecc_ed25519_custom_backend,构建系统就声明一个可选特性periph_ecc_ed25519
  2. 若目标板提供该硬件加速特性(FEATURES_USED命中),则启用psa_asymmetric_ecc_ed25519_backend_periph(外设硬件加速后端);
  3. 若目标板不提供该特性,则自动回退到软件后端psa_asymmetric_ecc_ed25519_backend_monocypher,该模块通过USEPKG += monocypher引入 monocypher 密码库,并启用psa_monocypher/psa_monocypher_ed25519
  4. 除默认路径外,还支持显式选择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_COUNT1非对称密钥对槽位数量。EdDSA 测试中需要 1 个私钥(签名),故设为 1
CONFIG_PSA_SINGLE_KEY_COUNT1单钥槽位数量。用于存放导入的公钥(PSA_KEY_TYPE_PUBLIC_KEY_OF_KEY_PAIR归入单钥槽),故设为 1
THREAD_STACKSIZE_MAIN4096主线程栈大小。软件后端(如 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-c031c6nucleo-l011k4samd10-xministm32f030f4-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 EdwardsPSA_ECC_FAMILY_TWISTED_EDWARDS),即 Edwards25519;
  • 算法标识为纯 EdDSAPSA_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_ALGexample_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 中的ztimerztimer_usec模块),测量单次 EdDSA(生成密钥 + 导出/导入公钥 + 签名 + 验签)的整体耗时并打印;
  • 失败时通过psa_status_to_humanly_readable(status)输出人类可读的错误状态,便于定位;
  • 无论成功失败,最终都会输出All DoneTests 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_keypsa_export_public_keypsa_import_keypsa_sign_messagepsa_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

并在menuconfigSystem -> PSA Crypto菜单下调整密钥槽数量(对应CONFIG_PSA_ASYMMETRIC_KEYPAIR_COUNTCONFIG_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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询