拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Erlang/OTP Crypto 引擎存储密钥(Engine Stored Keys)使用指南:签名、验签与口令保护

Erlang/OTP Crypto 引擎存储密钥Engine Stored Keys使用指南签名、验签与口令保护【免费下载链接】otpErlang/OTP项目地址: https://gitcode.com/gh_mirrors/ot/otp导读本文面向 Erlang/OTP 开发者系统讲解如何使用存放在加密引擎Engine中的公钥与私钥完成crypto:sign/4签名与crypto:verify/5验签操作。引擎可以将密钥存放在比普通文件系统更安全的存储介质中如硬件安全模块 HSM本文聚焦于如何构造engine_key_ref()映射、如何为受口令保护的密钥传入密码并结合 crypto 应用 的 NIF 底层实现与 engine_SUITE 测试套件 印证其调用链。读完本文你将掌握在 Erlang 中直接使用引擎托管密钥完成签名/验签的完整实战方案。背景OpenSSL Engine API 与密钥存储OpenSSL 对外暴露了一套 Engine API允许为部分或全部密码学操作插入替代实现。当配置适当时OpenSSL 会调用引擎的实现而非自身默认实现典型场景是硬件加速即把特定密码学运算交给硬件完成以获得更高性能。引擎的职责之一是提供私钥或公钥的存储。这类存储往往比普通文件系统更安全——例如密钥可以永不离开硬件设备。关于如何加载load一个引擎请参阅 Engine Load 章节本文不再赘述加载过程而专注于如何使用存储在引擎中的密钥。从底层契约看一个能提供密钥存储的引擎必须向 OpenSSL 注册两个回调ENGINE_set_load_privkey_function——用于加载私钥ENGINE_set_load_pubkey_function——用于加载公钥。在 OTP 仓库中otp_test_engineotp_test_engine.c就是这样一个示例引擎其bind_helper函数明确调用了这两个函数if (!ENGINE_set_load_privkey_function(e, test_privkey_load)) goto err; if (!ENGINE_set_load_pubkey_function(e, test_pubkey_load)) goto err;这意味着任何希望被 crypto 应用当作密钥存储使用的引擎都必须实现并注册这两个函数否则在调用加载密钥时会失败。使用引擎存储密钥的前置条件OTP 的 crypto 应用要求用户提供关于密钥的两项或三项信息已加载的引擎引用——引擎必须先被加载参见 Engine Load 章节 或 crypto:engine_load/3引擎中的密钥引用key_id——一个 Erlang 字符串或二进制其具体含义取决于所加载的引擎例如在示例引擎中它就是一个密钥文件的路径可选的密钥口令password——仅当密钥在引擎内受口令保护时需要。随后构造一个 Erlang 映射map作为密钥参数传入签名/验签函数。该映射的类型定义在 crypto.erl 中-type key_id() :: string() | binary() . -type password() :: string() | binary() . -type engine_key_ref() :: #{engine : engine_ref(), key_id : key_id(), password password(), term() term() }.字段语义如下字段是否必填类型说明engine必填engine_ref()term()由crypto:engine_load/3等函数返回的引擎引用key_id必填string() \| binary()引擎中密钥的标识符含义取决于引擎实现password可选string() \| binary()密钥口令仅当密钥在引擎中受保护时需要其他term() term()可选任意引擎相关的扩展字段提示用户应用通常处于更高抽象层例如 SSL 应用中的t:ssl:key/0此时引擎密钥映射会作为底层密钥载体被透明使用只有直接使用 crypto 应用时才需要亲手构造上述映射。场景一使用引擎存储的私钥签名假设引擎已在1处加载成功返回的引擎引用为EngineRef。构造私钥引用映射并调用crypto:sign/4完成 RSA 签名1 {ok, EngineRef} crypto:engine_load(....). {ok,#Ref0.2399045421.3028942852.173962} 2 PrivKey #{engine EngineRef, key_id id of the private key in Engine}. ... 3 Signature crypto:sign(rsa, sha, The message, PrivKey). 65,6,125,254,54,233,84,77,83,63,168,28,169,214,121,76, 207,177,124,183,156,185,160,243,36,79,125,230,231,...关键点key_id的取值完全取决于引擎——对示例引擎而言它是一个密钥文件的路径见下文与传入普通 RSA 私钥rsa_private()相比这里传入的是一个engine_key_ref()映射sign/4的规格crypto.erl明确接受engine_key_ref()作为Key的合法取值。底层调用链从映射到 ENGINE_load_private_key从源码看crypto:sign/4最终会进入 NIF 层。在 pkey.c 的get_pkey_private_key中当检测到传入的Key是一个 map 时会走引擎分支if (enif_is_map(env, argv[key_arg_num])) { #ifdef HAS_ENGINE_SUPPORT /* Use key stored in engine */ ENGINE *e; if (!get_engine_and_key_id(env, argv[key_arg_num], id, e)) assign_goto(*err_return, err, EXCP_BADARG_N(env, key_arg_num, Couldnt get engine and/or key id)); password get_key_password(env, argv[key_arg_num]); *pkey ENGINE_load_private_key(e, id, NULL, password); if (!*pkey) assign_goto(*err_return, err, EXCP_BADARG_N(env, key_arg_num, Couldnt get private key from engine));这里有两个值得注意的实现细节get_engine_and_key_idengine.c从映射中读取engine字段对应的资源对象enif_get_resource并从key_id字段取出二进制内容再zero_terminate成 C 字符串——所以key_id本质上是一个会被当作 C 字符串传递的标识符比如文件路径或硬件槽位号。get_key_passwordengine.c从映射中读取可选的password字段若映射中不存在该字段返回NULL即无口令。随后password被直接传给 OpenSSL 的ENGINE_load_private_key(e, id, NULL, password)。示例引擎如何解析 key_idOTP 自带的测试引擎把key_id解释为 PEM 文件的路径otp_test_engine.c 中的test_key_load会fopen(id, r)再根据公私钥标志分别调用PEM_read_PrivateKey或PEM_read_PUBKEY。这解释了测试套件中key_id为何形如rsa_private_key.pem。真实产品引擎则可能把key_id解释为 HSM 中的槽位、标签或对象句柄。场景二使用引擎存储的公钥验签用上一节的签名与消息构造公钥引用映射并调用crypto:verify/54 PublicKey #{engine EngineRef, key_id id of the public key in Engine}. ... 5 crypto:verify(rsa, sha, The message, Signature, PublicKey). true 6说明这里把公钥也放在引擎中仅用于演示“引擎存储公钥亦可使用”公钥当然也可以像往常一样公开处理例如直接传 RSA 公钥元组/二进制引擎存储并不是必须的。底层同样走引擎分支在 pkey.c 的get_pkey_public_key路径中会调用ENGINE_load_public_key(e, id, NULL, password)从引擎取出公钥用于验签。场景三使用受口令保护的私钥与第一个签名示例相同区别仅在映射中增加password字段——当引擎内的密钥受口令保护时必须提供该字段否则引擎无法解开密钥6 PrivKeyPwd #{engine EngineRef, key_id id of the pwd protected private key in Engine, password password}. ... 7 crypto:sign(rsa, sha, The message, PrivKeyPwd). 140,80,168,101,234,211,146,183,231,190,160,82,85,163, 175,106,77,241,141,120,72,149,181,181,194,154,175,76, 223,... 8测试用例佐证engine_SUITEcrypto 应用的测试套件 engine_SUITE.erl 专门验证了“使用引擎存储公私钥”这一能力可作为真实可运行的模式参考sign_verify_rsa(Config) - Priv #{engine engine_ref(Config), key_id key_id(Config, rsa_private_key.pem)}, Pub #{engine engine_ref(Config), key_id key_id(Config, rsa_public_key.pem)}, sign_verify(rsa, sha, Priv, Pub). sign_verify_rsa_pwd(Config) - Priv #{engine engine_ref(Config), key_id key_id(Config, rsa_private_key_pwd.pem), password password}, Pub #{engine engine_ref(Config), key_id key_id(Config, rsa_public_key_pwd.pem)}, sign_verify(rsa, sha, Priv, Pub).从测试用例可以观察到的实践要点算法覆盖面除 RSA 外测试还覆盖了 DSA、ECDSA、EdDSA 的引擎密钥签名/验签sign_verify_dsa、sign_verify_ecdsa、sign_verify_eddsa说明engine_key_ref()可配合多种pk_sign_verify_algs()使用口令错误处理sign_verify_rsa_pwd_bad_pwd用例故意传入Bad password并期望捕获error:{badarg,_,_}异常——这印证了错误口令会在 NIF 层以badarg形式抛出对应 pkey.c 中 Couldnt get private key from engine 的错误路径key_id 语义测试中的key_id对应测试数据目录下的 PEM 文件路径再次印证“key_id 的具体语义由引擎决定”。常见问题与注意事项引擎未加载使用engine_key_ref()前必须先加载引擎并获得EngineRef否则映射中engine字段无法解析为有效的引擎资源NIF 层会抛出badargCouldnt get engine and/or key id。引擎不支持密钥加载如果引擎没有调用ENGINE_set_load_privkey_function/ENGINE_set_load_pubkey_function注册加载函数ENGINE_load_private_key/ENGINE_load_public_key会失败表现为加载密钥失败的错误。无引擎支持的构建若底层 OpenSSL 实现未启用引擎支持HAS_ENGINE_SUPPORT未定义传 map 会直接得到badargNo engine support参见 pkey.c。口令与 key_id 的类型key_id和password均为string() | binary()底层会将其转换为以\0结尾的 C 字符串因此不要包含嵌入的空字符。OpenSSL 版本差异OpenSSL 3.0 已弃用deprecateCrypto Engine API加载动态库的文件名要求在不同 SSL 版本间也可能存在差异详见 Engine Load 章节 的注意事项在使用引擎功能时请以实际部署的 OpenSSL/LibreSSL 版本为准。延伸阅读Engine Load引擎加载指南——引擎的加载、注册方法、卸载与列举crypto.erl 引擎 API 实现——engine_load/3的 Erlang 层实现与错误处理otp_test_engine.c——OTP 自带的测试引擎演示了密钥存储引擎的完整 C 实现含test_privkey_load/test_pubkey_load/pem_passwd_cb_fun口令回调engine_SUITE.erl——覆盖 RSA/DSA/ECDSA/EdDSA 引擎密钥签名验签的完整测试套件pkey.c——签名/验签 NIF 层中引擎密钥的解析与加载逻辑。【免费下载链接】otpErlang/OTP项目地址: https://gitcode.com/gh_mirrors/ot/otp创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门