news 2026/10/30 4:37:35

铜锁/Tongsuo API 参数实战指南:一次讲清密码函数的上下文、密钥与坑点

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
铜锁/Tongsuo API 参数实战指南:一次讲清密码函数的上下文、密钥与坑点

铜锁/Tongsuo API 参数实战指南:一次讲清密码函数的上下文、密钥与坑点

【免费下载链接】Tongsuo铜锁/Tongsuo is a Modern Cryptographic Primitives and Protocols Library项目地址: https://gitcode.com/GitHub_Trending/to/Tongsuo

铜锁(Tongsuo)是一个现代密码学原语与协议库,本文带你把 API 调用中最高频的参数问题讲透:EVP_MD_CTX / EVP_CIPHER_CTX 上下文怎么管,SM3、AES-256-GCM、RSA/SM2 的密钥与 IV 长度怎么配,失败时错误码怎么查。读完你可以直接写出可编译的集成代码。

动手之前,先把"上下文对象"这层壳搞懂

铜锁的 EVP 接口有一个统一的套路:先 new 一个上下文,在它上面干活,最后 free。所有状态(算法元数据、中间缓冲区、结果长度)都挂在上下文对象上,而不是函数全局变量里——这就是它在多线程下能用、且同一个进程里可以同时跑多路加密的原因。

两类最核心的上下文:

  • EVP_MD_CTX:消息摘要(哈希)上下文,类型定义见 include/openssl/types.h
  • EVP_CIPHER_CTX:对称加解密上下文,类型定义见 include/openssl/types.h

生命周期三步走,以哈希上下文为例:

EVP_MD_CTX *ctx = EVP_MD_CTX_new(); // 创建,失败返回 NULL // ... 用它做计算 ... EVP_MD_CTX_free(ctx); // 用完释放,传 NULL 也安全
函数原型说明
创建EVP_MD_CTX *EVP_MD_CTX_new(void);见 include/openssl/evp.h
释放void EVP_MD_CTX_free(EVP_MD_CTX *ctx);见 include/openssl/evp.h
重置int EVP_MD_CTX_reset(EVP_MD_CTX *ctx);换算法复用同一对象,见 include/openssl/evp.h

这里会踩的坑:上下文不是线程共享资源。每个线程(或每条并发加解密任务)持有自己的上下文,别把"全局一个 ctx"当性能优化。另外free之后指针悬空,二次使用是典型的 use-after-free。

场景 A|"我要做哈希":Init → Update → Final 三连怎么串

哈希的调用链是固定三段,任何一步返回非 1 都应中断后续调用:

int sm3_or_sha256(const unsigned char *data, size_t len, const EVP_MD *md, unsigned char *out, unsigned int *outlen) { EVP_MD_CTX *ctx = EVP_MD_CTX_new(); if (ctx == NULL) return 0; /* type 参数就是算法选择器:EVP_sm3() 或 EVP_sha256() */ if (!EVP_DigestInit_ex(ctx, md, NULL) || !EVP_DigestUpdate(ctx, data, len) || !EVP_DigestFinal_ex(ctx, out, outlen)) { EVP_MD_CTX_free(ctx); return 0; } EVP_MD_CTX_free(ctx); return 1; }

三个函数的完整签名(均来自 include/openssl/evp.h):

int EVP_DigestInit_ex(EVP_MD_CTX *ctx, const EVP_MD *type, ENGINE *impl); int EVP_DigestUpdate(EVP_MD_CTX *ctx, const void *d, size_t cnt); int EVP_DigestFinal_ex(EVP_MD_CTX *ctx, unsigned char *md, unsigned int *s);
参数类型含义约束或推荐值
ctxEVP_MD_CTX *摘要上下文必须来自EVP_MD_CTX_new()
typeconst EVP_MD *算法选择器EVP_sm3()(GB/T 32905-2016,32 字节摘要)或EVP_sha256()(32 字节摘要)
implENGINE *硬件引擎句柄通常传NULL走内置实现
d / cntconst void */size_t待摘要数据及其长度可多次 Update 分段喂数据
md / sunsigned char */unsigned int *输出缓冲区与长度缓冲区至少 32 字节(SM3 与 SHA-256 同为 256 位)

算法选择器定义在 include/openssl/evp.h:EVP_sha256()在第 966 行,EVP_sm3()在第 979 行。选型上:需要满足国密合规(GB/T 32905-2016)用 SM3;对等方只认国际算法用 SHA-256。两者摘要长度相同,缓冲区可以统一按 32 字节分配。

一句话避坑:EVP_DigestFinal_ex之后上下文状态即已收尾,同一个 ctx 上直接再Update会得到错误结果——想算下一个值就先EVP_MD_CTX_reset或重新Init。

场景 B|"我要做对称加密":AES-256-GCM 从 key 到 tag 的全参数路径

对称加密的主链路和哈希同构:EVP_EncryptInit_ex → EVP_EncryptUpdate → EVP_EncryptFinal_ex。签名(include/openssl/evp.h):

int EVP_EncryptInit_ex(EVP_CIPHER_CTX *ctx, const EVP_CIPHER *cipher, ENGINE *impl, const unsigned char *key, const unsigned char *iv); int EVP_EncryptUpdate(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl, const unsigned char *in, int inl); int EVP_EncryptFinal_ex(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl);

以 AES-256-GCM 为例(算法选择器EVP_aes_256_gcm(),见 include/openssl/evp.h):

/* AES-256-GCM 加密一段数据,key 32 字节,iv 12 字节 */ int gcm_encrypt(const unsigned char *key, const unsigned char *iv, const unsigned char *aad, size_t aad_len, const unsigned char *in, size_t in_len, unsigned char *out, size_t *out_len, unsigned char tag[16]) { EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); if (ctx == NULL) return 0; int ret = 0; int len = 0, final_len = 0; if (EVP_EncryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, key, iv) == 1) { /* AAD:只认证不加密,用 in=NULL 的 Update 喂入 */ if (aad_len > 0 && EVP_EncryptUpdate(ctx, NULL, &len, aad, (int)aad_len) != 1) goto end; if (EVP_EncryptUpdate(ctx, out, &len, in, (int)in_len) != 1) goto end; if (EVP_EncryptFinal_ex(ctx, out + len, &final_len) != 1) goto end; *out_len = (size_t)len + (size_t)final_len; /* GCM 必须取出 16 字节 tag 随密文一起交付 */ if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_GET_TAG, 16, tag) != 1) goto end; ret = 1; } end: EVP_CIPHER_CTX_free(ctx); return ret; }

参数表:

参数类型含义约束或推荐值
ctxEVP_CIPHER_CTX *加密上下文EVP_CIPHER_CTX_new()创建,见 include/openssl/evp.h
cipherconst EVP_CIPHER *算法+模式EVP_aes_256_gcm();国密场景可用EVP_sm4_cbc()(evp.h 第 1086 行)
keyconst unsigned char *密钥32 字节(AES-256);SM4-CBC 为 16 字节
ivconst unsigned char *初始向量/nonce12 字节(GCM 推荐长度,避免内部 GHASH 预计算)
implENGINE *硬件引擎通常NULL

进阶坑点:AEAD 的 tag 长度与 AAD 喂法

GCM 属于 AEAD(认证加密),普通三件套之外还有两个参数必须显式处理:

  1. tag 长度:默认 16 字节,可用EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, len, tag)自定义,ctrl 命令常量见 include/openssl/evp.h(EVP_CTRL_AEAD_SET_TAG第 399 行)。解密侧必须先SET_TAG塞入对方发来的 tag 再Final,否则校验不过直接失败。
  2. AAD 的喂法:附加数据走EVP_EncryptUpdate(ctx, NULL, &len, aad, aad_len)——out 传 NULL 表示"只认证不加密",这一步不产生输出字节。
  3. IV 来源:nonce 用RAND_bytes()生成(include/openssl/rand.h),且同一个 (key, nonce) 组合绝对不允许复用,GCM 下复用 nonce 会直接泄漏机密与认证信息。

一句话避坑:密文交付时忘带 16 字节 tag,对端EVP_DecryptFinal_ex必定返回失败——把 tag 和密文放在同一信封里传递。

场景 C|"我要做非对称操作":RSA 填充怎么选、SM2 用户 ID 怎么配

非对称走EVP_PKEY_CTX三件套:

EVP_PKEY_CTX *EVP_PKEY_CTX_new(EVP_PKEY *pkey, ENGINE *e); /* evp.h L1798 */ int EVP_PKEY_encrypt_init(EVP_PKEY_CTX *ctx); /* evp.h L1962 */ int EVP_PKEY_encrypt(EVP_PKEY_CTX *ctx, unsigned char *out, size_t *outlen, const unsigned char *in, size_t inlen); /* evp.h L1964 */ void EVP_PKEY_CTX_free(EVP_PKEY_CTX *ctx); /* evp.h L1812 */

RSA 填充模式:为什么默认推荐 OAEP

填充模式通过EVP_PKEY_CTX_set_rsa_padding()(include/openssl/rsa.h)设置,常量定义在 include/openssl/rsa.h:

EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new(pub_key, NULL); EVP_PKEY_encrypt_init(pctx); EVP_PKEY_CTX_set_rsa_padding(pctx, RSA_PKCS1_OAEP_PADDING); /* 新代码一律 OAEP */ /* 还可继续配 OAEP 摘要:EVP_PKEY_CTX_set_rsa_oaep_md(pctx, EVP_sha256()); */
常量值描述安全性
RSA_PKCS1_OAEP_PADDING4OAEP 填充,随机化推荐:概率性填充,抗 Bleichenbacher 类自适应选择密文攻击
RSA_PKCS1_PADDING1PKCS#1 v1.5结构性填充,解密侧实现不当即成侧信道;仅限兼容旧系统
RSA_NO_PADDING3裸 RSA仅限 RSA-KEM 等由协议层自行组合的场景,裸用即明文可被乘法预言

推荐逻辑:OAEP 每次填充引入随机性,密文本身无法被代数分解;v1.5 在 OpenSSL 3.x 之后已逐步弃用,只有对接存量系统时才选它。

SM2 用户 ID 参数怎么配

SM2 签名/加密会把用户 ID 的 Z 值哈希进运算,对端不一致则验签失败:

const char sm2_id[] = "1234567812345678"; /* 用户 ID,双方必须约定一致 */ EVP_PKEY_CTX_set1_id(pctx, sm2_id, (int)sizeof(sm2_id) - 1); /* evp.h L1668,内部拷贝 */

EVP_PKEY_CTX_set1_id(EVP_PKEY_CTX *ctx, const void *id, int len)在 include/openssl/evp.h,注意函数名带set1表示库内部完成复制,你的缓冲区可在调用返回后释放。一句话避坑:SM2 双端 ID 一个用字符串、一个用空值,签出来的结果互验必挂——集成时把 ID 写进配置并写测试用例钉死。

操作失败了?先走一遍错误码调试路径

所有返回int的 EVP 函数,0 表示失败,失败原因堆在进程级错误栈里,两个函数就能读出来(原型见 include/openssl/err.h.in 与 include/openssl/err.h.in):

#include <openssl/err.h> unsigned long err = ERR_get_error(); /* 弹出一个错误码,0 表示栈空 */ char msg[256]; ERR_error_string_n(err, msg, sizeof(msg)); /* 转成可读字符串 */ printf("密码操作失败: %s\n", msg);
参数类型含义约束
errunsigned long单个错误码失败调用后至少循环取一次直到为 0
buf / lenchar */size_t输出缓冲区256 字节足以容纳常见错误文本

错误码定义文件:通用错误码模板 include/openssl/err.h.in,EVP 模块错误码常量 include/openssl/evperr.h(例如EVP_R_INVALID_KEY_LENGTH,evperr.h 第 69 行——出现它通常就是 key 长度对不上算法)。

一句话避坑:多线程环境里ERR_get_error()只取"当前线程"的栈,务必在同一线程内紧挨着失败调用读取,隔一层异步就取不到了。

参数选择速查表

维度推荐配置具体数值/说明
哈希算法SM3 或 SHA-256摘要均 32 字节;国密合规选 SM3(GB/T 32905-2016)
对称算法AES-256-GCM / SM4-CBCAES-256 密钥 32 字节;SM4 密钥 16 字节
IV / Nonce 长度GCM 12 字节;CBC 16 字节一律RAND_bytes()生成,禁止复用 (key, nonce) 组合
AEAD tag16 字节EVP_CTRL_AEAD_SET_TAG显式设置;tag 随密文交付
RSA 填充RSA_PKCS1_OAEP_PADDING(值 4)v1.5 仅限存量兼容
SM2 用户 ID双方约定一致的字符串EVP_PKEY_CTX_set1_id,内部拷贝语义
国密 vs 国际SM3/SM4/SM2 vs SHA/AES/RSA摘要长度 32 字节对齐;对称块/分组模式按对端实现选择
性能上下文复用 + 大块 Update热点路径EVP_MD_CTX_reset/EVP_CIPHER_CTX_reset代替 new/free

集成前最后核对一遍

  • 每条并发任务持有独立的EVP_MD_CTX/EVP_CIPHER_CTX,用完free,无全局共享
  • IV/nonce 全部来自RAND_bytes(),且 (key, nonce) 不重复;GCM 按 12 字节分配
  • AEAD 密文信封里带了 16 字节 tag,AAD 用in=NULL的EVP_EncryptUpdate单独喂入
  • RSA 新代码只出现RSA_PKCS1_OAEP_PADDING,v1.5 有存量依赖才降级
  • 所有EVP_*返回值逐一判 1,失败路径统一走ERR_get_error()+ERR_error_string_n()落日志

【免费下载链接】Tongsuo铜锁/Tongsuo is a Modern Cryptographic Primitives and Protocols Library项目地址: https://gitcode.com/GitHub_Trending/to/Tongsuo

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/30 4:28:20

LeetCode //C - 1309. Decrypt String from Alphabet to Integer Mapping

1309. Decrypt String from Alphabet to Integer Mapping You are given a string s formed by digits and ‘#’. We want to map s to English lowercase characters as follows: Characters (‘a’ to ‘i’) are represented by (‘1’ to ‘9’) respectively.Character…

作者头像 李华
网站建设 2026/10/30 4:25:22

沐曦股份曦云C系列GPU Day 0 适配智谱GLM-5.2旗舰模型

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/30 4:24:41

YOLO26涨点改进 | 全网独家复现 CVPR2025 RLAB残差线性注意力块创新改进,多尺度特征自适应融合与关键权重增强、助力小目标检测、遥感检测、图像分类、实例分割有效涨点

目录 一、深度剖析:YOLO26原生Neck特征融合核心缺陷 1.1 多尺度特征错位失配,高低层特征冲突严重 1.2 融合冗余信息堆积,关键有效特征被稀释 1.3 浅层细节融合丢失,小目标检测短板突出 1.4 无全局上下文建模,长距离依赖缺失 1.5 多任务适配性差,分割分类增益微弱 …

作者头像 李华
网站建设 2026/10/30 4:23:38

【RHCA+】影响Linux系统性能的因素

“ 目录&#xff1a;RHCA -> 12 系统监控 -> 1 影响Linux系统性能的因素 ”总体原则&#xff1a;完成任务的有效性、稳定性以及响应速度等系统默认设置通常是中庸的&#xff0c;适合大多数场景&#xff1b;在系统专用于某些特定业务场景时&#xff0c;就可以牺牲某方面的…

作者头像 李华