开源之夏-4
第五周工作进展与疑问
第五周主要围绕gmssl库中sdf_init.h熟悉代码。
通读了密标委的SDF接口文件,找到了数据结构和函数接口的一一对应关系。
下载了Gmssl的子项目SoftSDF,进行了连接测试。
sdf_init.h定义了实现SDF接口的所有数据接口和函数接口。
1 |
|
1 |
|
现有问题
1.GMSSL 没有 provider/engine,如何支持硬件?
GMSSL 通过 SDF(GM/T 0018)对接硬件:运行时装载厂商库为 SDF_METHOD(函数指针表),用 SDF_VENDOR 适配算法 ID/能力位/ECCCipher 编解码/错误码;对外统一 SDF_* API(src/sdf/sdf_lib.c),上层还有易用封装(src/sdf/sdf.c)。
2.sdf_init.h里边的SDF_VENDOR 结构体“在做什么?”
厂商适配“虚函数表”:把标准与厂商差异做映射(cipher/digest/pkey 标准↔厂商、cap 位图映射、ECCCipher encode/decode、错误码解释),实现“统一 API,厂商差异可插拔”。
3.“隔离”“VENDOR”“函数指针表”再解释
隔离:把厂商差异集中在适配层,避免污染上层业务。
VENDOR:具体设备厂商(HSM/密码卡)的适配策略对象。
函数指针表:运行时决定调用哪个实现(类似 vtable)。
4.sdf_lib.c 和 sdf.c 的区别
sdf_lib.c:标准 SDF_* 的统一实现与分发(适配 vendor,调用厂商库)。
sdf.c:面向应用的易用封装(简化参数/上下文/流程),基于 SDF_*。
5.为什么要有“易用封装层”
降低使用复杂度,隐藏算法 ID/结构体细节,提供默认安全、资源生命周期管理与更统一的错误处理,支持一站式组合操作。
6.如何测试 SoftSDF 的函数(你已编出 libsoftsdf.so、softsdfinit、softsdftest)
用 gmssl 工具链通过 -lib 指向 libsoftsdf.so 做端到端验证:sdfinfo、sdftest、sdfexport(导出公钥)、sdfsign+sm2verify、sdfencrypt+sdfdecrypt、sdfdigest、sdfutil -rand。建议先最小化排查再跑 sdftest 并收集日志。
sdftest 退出码 1 的诊断
先跑 sdfinfo、sdfutil -rand、sdfexport、sdfsign/verify、sdfencrypt/decrypt、sdfdigest 定位失败点;用绝对路径 -lib;必要时 ldd 检查依赖;将 sdftest 首个错误输出贴出定位。
7.为什么 GmSSL 不用 provider/engine
依赖收敛与可移植性;engine 已弃用、provider 接口演进成本高;SDF 的设备/会话/索引语义与 EVP 不完全匹配;直连 SDF 性能更直接、控制更强;中国密码生态优先。若必须融入 OpenSSL 生态,可另外实现一个 provider,但工程量大。
8.我现在要实现的 SDF 接口已经脱离了 EVP 体系吗?
是。GmSSL 的 SDF 完全独立于 EVP/provider/engine。实现/调用标准 SDF_* 即可,由 SDF_METHOD/SDF_VENDOR 连接厂商库。
9.GMSSL 的 SDF 与当前 sdf_Tongsuo 的差异与指导
GMSSL:完整方法表+厂商适配层+工具链;TongSuo:已有 TSAPI_SDF_* 封装与方法表,但部分 API(如 Hash)和厂商适配层缺失。建议先补 HashInit/Update/Final 的方法表/DSO 绑定/包装,再完善对称/ECC/对象 API,逐步引入轻量 vendor 适配与命令行/单测。
什么是 TSAPI、什么是 DSO
TSAPI:TongSuo 对外 API 的前缀/接口层(做参数校验/错误规范/调度方法表)。
DSO:动态装载外部库的抽象(dlopen/dlsym/LoadLibrary),运行时绑定厂商 libsdf.so 的符号。
还是没懂,为什么要在链接时绑定
链接时绑定:依赖强可用、代码简单、编译期类型检查、调用开销低、合规易控、版本/ABI 锁定。运行时绑定(DSO):可插拔、多厂商/热替换。建议两条路径并存:默认 DSO,可选强链接(构建选项)。
10.如何将软实现的SDF集成到Tongsuo?
我尝试添加编译选项,一直跑不通。
GPT说:
- 不用“只加一个编译选项”硬连就完事。最佳做法是“DSO 默认 + 可选强链接”,补齐 Hash 等方法表与绑定,提供环境变量 TS_SDF_LIB 指向 SoftSDF。按上面的最小链打通后,你的 sdf_Tongsuo 即可用 SoftSDF 做端到端测试。
可是我还是没看懂,这部分求老师给我看看该怎么办好。
小结(精炼版)
方法表 = C 里实现“可插拔后端 + 统一调用接口”的常规手段,解决多实现选择、动态加载、测试 mock、扩展演进、错误集中处理、最小可链接占位等问题;代价是极小的间接调用开销和一些结构体样板。对库级 / 可扩展 / 可能接第三方硬件的场景几乎是标准解。
C,但是先给我讲讲软件实现思路,我基础较差,第一次接触这种工程项目,请告诉我该如何做
你要做 C)——写一个“纯软件实现”后端,但你是第一次接触这种工程。我先不改代码,先把整体思路、分步骤路线、关键数据结构与每个函数该干什么详尽讲清楚。看完你就知道“先做哪一点、做到什么算完成、下一步如何扩展”。之后你说“可以动手了”,我再帮你生成骨架文件和占位实现。
总体目标一句话
实现一套 SDF_METHOD
(比如命名 ts_sdf_sw_meth
),不用真实硬件,用 OpenSSL/Tongsuo 已有的 EVP/随机/ECC 功能,把各个 SDF API 的功能在内存里模拟出来:设备 → 会话 → 密钥句柄 → 操作。
分阶段路线图(建议按阶段逐步提交)
- 最小可用阶段 (MVP):能成功 OpenDevice/OpenSession/GenerateRandom/Close。
- 对称密钥阶段:ImportKeyWithKEK / DestroyKey / Encrypt / Decrypt / MAC。
- 内部 ECC 公钥导出、内部签名(简单用软件生成固定索引私钥数组)。
- 内部 ECC 加解密(SM2 Encrypt/Decrypt)。
- 扩展 GenerateKey(动态生成对称密钥并返回句柄)。
- 访问控制(Get/ReleasePrivateKeyAccessRight)+ 密钥权限管理。
- 错误码、线程安全、资源清理、能力查询扩展。
每完成一个阶段,写一个极简自测(小 main 或单元测试)验证,再进入下阶段。
概念与数据结构设计
1. 设备 (Device)
代表“一个逻辑加密设备”。软件实现里实际就一个实例。
结构示例:
1 |
|
OpenDevice:
- 如果已存在全局 device,则增加引用 + 返回指针。
- 否则分配、初始化锁、生成若干内部 ECC 私钥(或懒生成:第一次用到某 index 再生成)。
CloseDevice: - 减引用;为 0 时释放所有密钥、锁、结构。
2. 会话 (Session)
提供临时操作上下文、随机、已导入会话密钥列表。
1 |
|
OpenSession:
- 分配 session,引用 device(refcnt++)。
CloseSession: - 释放所有会话密钥,device refcnt–,释放 session。
3. 会话对称密钥句柄 (Key Handle)
1 |
|
DestroyKey:
- 从 session 的栈/数组中找到该指针,清零内存后释放。
4. 内部私钥 (Internal SK / ISK)
- 存在 device 里:
isk_ecc[index]
。 - 索引方式:
uiISKIndex
。 - 生成方法:
EVP_PKEY_keygen
用 SM2(或 prime256v1,如果 SM2 不可用)算法。 - ExportSign/EncPublicKey:读取对应私钥的公钥参数,填充
OSSL_ECCrefPublicKey
(需要坐标转大端定长缓冲;先简化:只要长度 bits + x,y 的 bn2bin 填入)。
5. 算法 ID 映射
你需要一张表把 uiAlgID
→ EVP 层描述:
1 |
|
在 Encrypt/Decrypt:
- 按 alg_id 找映射;检查 key 长度、IV 长度;调用
EVP_CipherInit_ex
+ Update + Final。
MAC:
- 如果 SDF 语义是对称算法的 CBC-MAC 或 GMAC,需要再拆;
- 简化第一版:用 HMAC(SM3) 产生 MAC(只为让接口通,后续再换成标准 SDF 语义)。
- 或者:如果 alg_id 指 SM3,直接
EVP_DigestInit
/ Update。
6. 随机数
x_GenerateRandom
:
- 直接调用
RAND_bytes(pucRandom, uiLength)
;返回 0 表示成功(和你的错误码约定一致)。
7. ImportKeyWithKEK / ImportKeyWithISK_ECC
第一版可以“假实现”,即:
- 忽略 KEK/ISK 真实解包流程,直接把输入的密钥材料(假定是明文)复制到 SW_SYMKEY,返回句柄。
- 同时在注释里写:TODO:实现真实 KEK 包封解密流程。
益处:先让上层流程跑通,之后再补安全细节。
8. InternalEncrypt/Decrypt_ECC (SM2)
- 使用
EVP_PKEY_encrypt
/EVP_PKEY_decrypt
(SM2 需要设置 SM2 ID 或者用EVP_PKEY_CTX_set1_id
),不行的话采用 OpenSSL SM2 封装函数。 - 填充/解析
OSSL_ECCCipher
:包含 C1(x,y) + C3(M) + C2(密文) 的 SM2 标准格式。第一版可直接返回 NOTSUPPORT,先实现 Sign,之后再做 Encrypt/Decrypt。
9. InternalSign_ECC
- 用
EVP_DigestSign*
(SM3 + SM2)。流程:EVP_MD_CTX_new
EVP_DigestSignInit
with SM3/SM2 keyEVP_DigestSignUpdate(data)
EVP_DigestSignFinal
拿 (r||s) DER;解析 DER 里的 r,s BIGNUM,再写入OSSL_ECCSignature
固定缓冲(大端填充到 MAX_LEN)。
(第一版也可以只返回 NOTSUPPORT;但签名逻辑相对简单,早实现有价值。)
10. GenerateKey (扩展)
- 根据
type
(自定义:比如 1=对称 SM4, 2=AES-128, …)和len
:- 分配 SW_SYMKEY
- RAND_bytes 填充密钥
- 推入 session
- 返回句柄
no_kek
若含义是“不要外部包封形式”,第一版可忽略;后续支持时再增加一个输出缓冲参数(如果标准需要)。
11. 私钥访问控制
初版可直接返回成功(假装已授权)。以后:
- 保存某索引是否“已授权”状态 bitset,在
GetPrivateKeyAccessRight
校验密码(可用硬编码)后设置 bit;ReleasePrivateKeyAccessRight
清除 bit,没有权限的内部操作返回错误。
12. 错误码策略
统一宏:
1 |
|
每个函数最前做参数检查;失败路径 goto cleanup。
13. 线程安全
- 设备结构有读写锁;操作内部私钥/生成新私钥需加写锁。
- 会话结构有锁;增删 sym_keys 要加锁。
- 若第一版只在单线程测试,可先不加锁,后续补。
14. 资源生命周期
函数 | 分配/释放 |
---|---|
OpenDevice | 分配 device(或复用),refcnt++ |
CloseDevice | refcnt–,为 0 才真正释放 |
OpenSession | 分配 session + ref device |
CloseSession | 释放其持有 key 句柄,ref device |
Import / GenerateKey | 分配 SW_SYMKEY(挂在 session) |
DestroyKey | 从 session 移除、清零、free |
确保任何返回错误的路径不泄露内存:使用统一 cleanup。
15. 日志/调试(可选)
加一个简单宏:
1 |
|
开发时开,发版时关。
从 0 到 MVP 的具体步骤(最小工作量版本)
第一轮只做 5 个函数:
- x_OpenDevice
- x_CloseDevice
- x_OpenSession
- x_CloseSession
- x_GenerateRandom
详细操作:
(1) 新文件:sdf_sw.c
(2) 定义内部结构 SW_SDF_DEVICE
/ SW_SDF_SESSION
(3) 全局静态指针:static SW_SDF_DEVICE *g_sw_device = NULL;
(4) 实现 5 个函数逻辑(如上)
(5) 组装 SDF_METHOD ts_sdf_sw_meth = { ... (填只实现的函数,其余指向占位或本文件里的 not_support 函数) }
(6) 在 loader 里(你还没贴 sdf_lib.c,可以稍后)加:
- 如果未加载硬件库,fallback 到
ts_sdf_sw_meth
,再不行退到ts_sdf_meth
。
(7) 写一个简易测试 main(或暂时用现有对外 API 调用)验证随机数与句柄生命周期。
逐步增加对称加密
添加:
- ALG_ID 常量(假设你在
<openssl/sdf.h>
里已有;如果没有先临时定义)。 static const ALG_MAP_ENTRY alg_map[] = { ... }
- x_ImportKeyWithKEK: 复制输入
pucKey
→ 新 SW_SYMKEY - x_Encrypt/x_Decrypt:
- 查 key handle → alg_id → cipher_name
EVP_CIPHER *cipher = EVP_CIPHER_fetch(NULL, cipher_name, NULL);
EVP_CipherInit_ex(ctx, cipher, NULL, key, iv, enc=1/0);
- Update + Final
- 处理输入/输出长度(需要上层提供足够缓冲;第一版不做分片,假设一次全部)
MAC:
- 先用 HMAC(SM3)(或 SM3 digest 直接当 MAC):
EVP_DigestInit_ex(ctx, EVP_sm3(), NULL);
- Update(data)
- Final(macbuf)
ECC 签名实现亮点(稍后阶段)
- 预置/懒生成私钥:
1
2
3
4static EVP_PKEY *get_isk(int idx) {
if (!device->isk_ecc[idx]) generate_sm2_key();
return device->isk_ecc[idx];
} - 签名:
1
2
3
4
5
6EVP_MD_CTX *mctx = EVP_MD_CTX_new();
EVP_DigestSignInit(mctx, &pctx, EVP_sm3(), NULL, pkey);
EVP_DigestSignUpdate(mctx, pucData, uiDataLength);
EVP_DigestSignFinal(mctx, der, &derlen);
解析 DER -> r,s
填充 OSSL_ECCSignature - 加解密参考 SM2 标准(可以推迟)。
判断“一个阶段完成”的验收标准(Checklist)
阶段 1 (MVP):
- OpenDevice 返回 0
- OpenSession 返回非空句柄
- GenerateRandom 返回不同随机数据
- CloseSession 不泄露(运行 valgrind/ASAN 若可)
- CloseDevice 后再次 Open 仍能工作
阶段 2 (对称):
- ImportKeyWithKEK 后返回句柄
- Encrypt + Decrypt roundtrip 等于原文
- DestroyKey 释放后再用返回 NOT_FOUND
阶段 3 (签名):
- ExportSignPublicKey 返回 bits 和坐标非零
- InternalSign_ECC 返回固定长度 r,s
- (可选)使用 EVP_PKEY_verify 复核签名正确性(需要再写一个 verify 辅助)
常见初学坑提示
坑 | 说明 | 避免方式 |
---|---|---|
句柄直接用数组下标 | 删除时挪动导致悬挂 | 用指针 + list/stack 保存 |
未清零密钥内存 | 安全风险 | OPENSSL_cleanse 或 memset_s |
EVP_CTX 复用不当 | 加密/解密错乱 | 每次操作新建+free 或正确 Reset |
忘记 Final 导致少尾块 | CBC/PKCS7 出错 | Update 后必须 Final |
SM2 签名 DER 解析错 | r,s 有前导 0x00 | 使用 BIGNUM 读取后再 bn2binpad 定长填充 |
多线程竞态 | refcnt 不准 | 加锁或暂时单线程 |
给你的一份“最小软件实现框架”脑图(文字版)
设备生命周期
→ OpenDevice: alloc/ref
→ OpenSession: alloc session (ref device)
→ GenerateRandom: RAND_bytes
→ ImportKey: store SW_SYMKEY
→ Encrypt/Decrypt: EVP cipher
→ CalculateMAC: EVP digest/HMAC
→ InternalSign: SM2 + SM3
→ DestroyKey: erase & free
→ CloseSession: free keys & deref device
→ CloseDevice: free all when refcnt=0
你现在可以怎么做
选项:
A. 先让我生成“阶段 1 + 阶段 2 的骨架代码”(全部函数写好结构 + TODO + 基本对称加密)。
B. 只生成阶段 1 最小 5 个函数,自己熟悉后再扩展。
C. 先生成数据结构头文件(sdf_sw_local.h
)+ 空实现文件。
告诉我你选哪种(或自定义),我就直接在仓库里加文件并写初版代码。也可以先问任何一块你还没弄懂的,我再展开。
回复一个选项或继续提问就行。
这个软实现骨架必须调用evp吗?还是我可以不用其它库实现
你问:软实现骨架“必须”用 EVP 吗?还是可以完全不用其它库自己写?下面给你一个清晰决策框架:什么时候强烈建议用 EVP,什么时候可以(或不得不)绕过,完全自写要面对哪些成本与坑,并给出混合方案。
核心结论先说
- 不是“必须”,理论上你可以不用 EVP,自己实现所有算法。
- 实际工程里强烈建议:90% 功能用 EVP/Provider 层现成实现,只在极少数 SDF 规范与 EVP 语义不完全吻合的地方做“格式适配/封装”。
- 彻底不用 EVP 重新实现算法,风险极高(正确性、安全性、时间成本、侧信道防护、FIPS/国密一致性),除非有硬性约束(比如严禁依赖 OpenSSL/Tongsuo 算法实现、做学术演示、练习用途、或要通过某些合规流程要求算法独立验证)。
选项对比一览
方案 | 描述 | 优点 | 缺点 / 风险 | 适用场景 |
---|---|---|---|---|
A: 全部用 EVP | SDF 函数内部直接调用 EVP_* / RAND_bytes / EVP_PKEY_* | 快速、安全、维护轻 | 需理解 EVP 抽象与参数映射 | 标准工程首选 |
B: 混合封装 | 80% 用 EVP,某些结构(SM2 Cipher 格式、特殊 MAC)自己拼装 | 折中,减少重复 | 仍需理解底层格式 | SDF 规范格式与 EVP 输出略有差异 |
C: 低层 provider 调度 | 使用 provider dispatch(OSSL_DISPATCH )或 EVP_*_fetch 做更细粒度控制 | 可选特定实现(软/硬) | 复杂度高 | 需要多实现策略/性能调优 |
D: 直接调用低层算法内部函数(非公开 API) | 引用库内部私有符号 | 可能性能略高 | 升级易崩,API 不稳定 | 不推荐 |
E: 自己实现全部 SM2/SM3/SM4/AES/HMAC/RNG | 全部手写 | 理论上最大自主 | 极高安全与时间成本;易有侧信道漏洞 | 只适合研究或无第三方依赖硬约束 |
F: 部分自写(比如仅 SM2 封装),其余 EVP | 自写少量特殊算法 | 在特殊需求下减依赖 | 仍有安全负担 | SM2 特殊行为需求 |
为什么优先用 EVP(具体价值)
- 安全性:已有的常量时间实现、边界检查、随机数质量保障。
- 算法演进与补丁:上游修复(比如 side-channel)自动受益。
- 多算法统一接口:减少你 SDF 层分支复杂度,只维护“ID→EVP 名称的映射”。
- 性能优化:硬件加速(AES-NI、SM4 指令、国密指令扩展)由 EVP 层透明启用。
- FIPS / 国密模式:将来若要进入受控模式,只需设置加载合规 provider。
- 测试覆盖:上游已有大量测试;你只需补 SDF 语义对应的小测试。
- 代码量减少:节省大量调试时间,把精力放在句柄/生命周期/错误码/规范兼容。
不用 EVP 自己实现要面对的清单
类别 | 需要做的事 |
---|---|
对称加密 | SM4/AES 模式实现 + PKCS#7 填充 + IV 管理 + 分组对齐 |
MAC | HMAC 或 CBC-MAC/GMAC 算法实现,注意 padding 与验证 |
随机数 | 真随机源熵聚合,或至少系统调用 (/dev/urandom /WinCrypt/getrandom );DRBG 构建 |
ECC/SM2 | 椭圆曲线点运算(大数库、自带曲线参数)、签名/加解密、KDF、C1C3C2 编解码 |
哈希 | SM3 / SHA 系列迭代函数、长度/位数填充 |
常量时间比较 | 避免早停差异导致侧信道 |
内存清零 | 安全清零函数(避免被优化掉) |
测试向量 | 覆盖全部算法官方/公开测试向量验证正确性 |
侧信道防护 | 避免分支、避免 secret-dependent array index |
维护 & 升级 | 跟进规范/安全公告,自测回归 |
这对“第一次接触这种工程”的你来说几乎不可取。
混合封装的典型场景(你后面很可能碰到)
SDF 规范里一些结构化格式(举例 SM2 Encrypt 输出 C1 || C3 || C2,内部字段长固定)和 EVP 原生输出(可能是整体 DER 或者不同字段分离)不 1:1 对应。做法:
- 用 EVP 生成或解析“核心密码材料”(例如 SM2 加密输出的点坐标、密文、hash)。
- 自己按照 SDF 定义打包进
OSSL_ECCCipher
(x,y,M,L,C[…])。 - 反向解密时再拆开。
这样只写“格式转换”代码,不写“曲线点乘 / KDF”。
如果仍想验证“手写最小实现”可行(学习用)
你可以挑一个最简单环节单独练练手,比如:
- 只写一个伪随机函数(调用
RAND_bytes
作为后备); - 或只手写一个 XOR“加密”算法 ID,用来感受句柄/生命周期,而真实算法用 EVP。
但最终产出还是要替换成真实安全算法。
推荐分层结构(使用 EVP 的优雅方式)
逻辑分两层:
层 A:SDF 接口适配层
- 实现
x_Encrypt/x_Decrypt/x_CalculateMAC/x_InternalSign_ECC
等 - 做参数检查、句柄查找、错误码转换、结构打包
层 B:算法调度层(极薄)
1 |
|
lookup_alg(alg_id)
返回描述,然后使用:
1 |
|
SM2 签名:
1 |
|
何时可能“临时不用 EVP”
- 想先做一个极简演示:加密 = 明文复制;MAC = 全 0;只验证调用链。这种阶段性 stub 可以帮助调通生命周期,不涉及真实算法。完成后必须替换。
- 某特定厂商给了自带闭源静态库,只暴露低层函数,你需要包装它们填进方法表(仍然是“非 EVP”,但逻辑等价)。
- 某算法 EVP 尚未支持(极少数冷门或最新算法)——此时你自己实现后,未来可以再迁移到官方 provider。
决策指导流程(给你一个简单判断树)
- 目标是交付可用安全版本? → 用 EVP (YES)
- 只是演练接口生命周期? → 可以先写假实现,但打 TODO
- 有监管 / 合规 / FIPS 可能? → 必须用受控 provider (EVP)
- 是否有算法 EVP 不支持? → 仅该算法局部自写,其余用 EVP
- 你有资深密码实现经验?(常量时间 & 大数)→ 如果没有,请不要全栈自写
落地建议(对你这种“第一次接触”)
第一阶段(MVP):
- 所有加密/随机/签名都用 EVP
- Import/GenerateKey 直接存明文 key(后续再做 KEK 包封)
- InternalSign 实现,InternalEncrypt 可以暂时 NOTSUPPORT
第二阶段:
- 引入算法映射表
- 增加 DestroyKey / Encrypt / Decrypt / MAC
第三阶段:
- 实现 SM2 Encrypt/Decrypt(借助 EVP_PKEY_encrypt/ decrypt 或 provider 专用接口)
- 结构格式转换
第四阶段:
- 访问控制 / 权限 / 错误码清晰化
- 安全清零 / 销毁顺序
你如果“坚持不用 EVP”最小工作量方向(不推荐但给你参考)
挑一个强约束最短路径:
- 只支持 SM4-CBC PKCS7:手写 SM4 轮函数 + CBC 模式 + padding。
- 只支持伪随机:读取
/dev/urandom
/ Windows RtlGenRandom。 - 只支持“签名”= 哈希(SM3)后截断前 32 字节(伪签名,危险!)——仅调通流程。
- 所有其它函数返回 NOTSUPPORT。
然后再逐个替换为正式实现。这种方式风险是容易“忘记”替换伪实现进入产线。
结论重申 + 建议下一步
- 绝大多数情况下:用 EVP。你当前经验水平下这是正确路线。
- 不用 EVP 只有“演示 stub”或“外部闭源库”两类必要场景。
- 建议你让我们进入“生成阶段 1 + 阶段 2(对称)”的 EVP 骨架,这样你能看到具体代码模式,然后再继续扩展。
如果你同意,就回复:“生成 A(或 A+B)骨架”,我会:
- 新增
sdf_sw.c
- 定义内部结构
- 填写 Open/CloseDevice/Session/GenerateRandom
- 加对称 Encrypt/Decrypt(用 EVP)
- 其它函数留 TODO 注释
或者你想先只看最小 5 个函数也可以。告诉我你的选择。
我不明白,evp和sdf应该是两套体系,那么我现在软实现sdf接口的“基座”,还需要调用evp接口?我说的对吗?
你现在困惑的点抓得很准:EVP 和 SDF 是两套“体系 / 抽象层”。问题是:在做一个“纯软件版”的 SDF 接口实现时,是否应该在内部调用 EVP?下面把概念分开、再说明为什么“调用 EVP”其实是一种非常典型、合理的“适配层”做法,并不是混淆体系。
✅ 你的问题拆解(Checklist)
- SDF 与 EVP 是否同一层 / 同一体系?(否,是不同抽象)
- 软实现 SDF“基座”是否必须依赖 EVP?(不是必须,但强烈推荐)
- 为什么可以 / 值得在 SDF 实现里调用 EVP?(作为下层密码原语提供者)
- 如果不用 EVP,有什么代价?(安全、时间、正确性成本)
- 什么时候不调用 EVP?(已有硬件、闭源驱动或学习 stub)
1. 两套体系的定位(垂直分层,而不是平行冲突)
可以把它们想成三层:
1 |
|
- SDF:面向“设备 + 会话 + 句柄 + 内部索引 + 国密接口规范”语义的一组函数集合,强调“硬件/受控资源管理”。
- EVP:通用的密码算法高层 API(抽象各种算法:对称、杂凑、签名、KDF、随机),不关心“设备句柄/密钥索引”这些 SDF 设备概念。
- 你的软实现:用软件模拟“设备/会话/密钥管理”;真正做加密、签名时,需要调用一个“密码引擎”——EVP 正好就是现成引擎。
所以:SDF 是业务侧协议/接口层;EVP 是算法执行层。它们不是“竞争”关系,是“适配”关系。
2. 为什么在 SDF 实现里调用 EVP 是常规模式
把每个 SDF 函数拆开看:
SDF 函数语义 | 内部需要的底层能力 | 直接用 EVP 是否合适 |
---|---|---|
OpenDevice / OpenSession | 分配结构、引用计数 | 与算法无关,不用 EVP |
GenerateRandom | 真随机 / DRBG | RAND_bytes() ✅ |
ImportKey / DestroyKey | 存储 & 清零 | 自己写;不需要 EVP |
Encrypt / Decrypt | 对称算法 + 模式 + Padding | EVP_Cipher* ✅ |
CalculateMAC | HMAC / Hash | EVP_Digest* / EVP_MAC* ✅ |
ExportEnc/SignPublicKey | 公钥序列化 | EVP_PKEY 提取坐标 ✅ |
InternalSign_ECC | Hash+签名(SM3+SM2) | EVP_DigestSign* ✅ |
InternalEncrypt_ECC | SM2 加解密 | EVP_PKEY_encrypt/decrypt ✅ |
GenerateKey(扩展) | 随机字节 | RAND_bytes ✅ |
只有“设备/句柄管理”是你自己逻辑;算法工作全部交给 EVP,减少大量重复劳动与安全风险。
3. 如果“不调用 EVP”意味着什么
你得自己提供:
- SM3 / SM4 / SM2 全套算法实现(正确、高性能、抗侧信道)
- CBC/CTR 模式、填充、MAC/HMAC / KDF
- 椭圆曲线点运算(大数、逆元、随机私钥生成、参数检查)
- 随机数来源质量控制
- 后续安全补丁跟进
这对初学者实现成本极高,而且容易出安全隐患。
4. 软实现调用 EVP 的准确说法
不是“把两套体系揉在一起变成一套”,而是:
- 你在实现“一个 SDF provider / backend”,它的“算法执行层”选择“EVP 软件算法”。
- 如果未来换成真实硬件,只要替换方法表,不再调用 EVP,而是调硬件驱动函数即可。
同理,在另一些场景:
- TLS 库的高层 handshake → 调用 EVP 加解密
- PKCS#11 适配层 → 也会在软实现里调用 OpenSSL EVP
这是标准的“适配桥接”模式(Adapter / Bridge Pattern)。
5. 类比帮助直观理解
类比“操作系统文件 API”:
- 你的 SDF 函数 =
fopen/fread/fwrite/close
这一层的行为(统一接口)。 - EVP = 磁盘驱动 / 存储控制器的“读写块”实现。
- 软件模拟时直接用内存数组 + 系统调用。换硬件后,调用真正硬件 ioctls。
6. 你可以选择的 3 种具体路线
路线 | 做法 | 适用 |
---|---|---|
纯 EVP 封装(推荐) | SDF 方法表里:设备/句柄逻辑自写,算法全走 EVP | 快速、安全、维护小 |
分段:先写假算法再逐步替换 | 第一版 Encrypt 就 memcpy,后面替换成 EVP | 学习/调通生命周期 |
最终想接硬件 | 现在写 EVP 版;未来再添加硬件版 method;运行时选择 | 可平滑迁移 |
7. 一个具体“内部调用 EVP”的最小示例(只示意架构)
伪代码(不需要你现在写,只看结构):
1 |
|
SDF 对外 API 完全看不到 EVP,它只看到“我传了句柄进来得到了密文”。
8. 什么时候你会分离得更彻底
如果未来:
- 需要根据配置选择“软件实现 / 硬件实现”
- 需要统计每个算法耗时
你可以再加一层抽象(算法策略表),但对初学阶段没必要。
9. 快速判断你是否理解到位(自测)
回答下面三个问题(自问):
- 如果我以后接硬件,SDF 接口代码是否要大改?——不,需要新增一个硬件 method 表,替换指针。
- EVP 与 SDF 是不是同一“规格”的 API?——不是,侧重不同层次。
- 我现在写的软件版 SDF 调 EVP,会不会污染或限制以后接硬件?——不会,它只是一个 backend 选择。
如果这三句都能解释给别人听,说明你理解到位。
10. 下一步建议
- 确认采用“纯 EVP 封装路线”。
- 先生成软件 method 骨架(Open/Close/Session/Random)。
- 再加对称加密 + MAC。
- 然后 ECC 公钥导出 + 签名。
如果你准备好了,可以让我直接生成那套骨架文件。
总结一句话
SDF 是“规范+设备/句柄”语义层,EVP 是“密码算法执行”层;软实现 SDF 时调用 EVP 只是“用成熟算法引擎填充规范接口”,不是混淆体系,而是标准的适配模式——不是必须,但强烈建议。
告诉我:要不要现在就生成软件实现骨架(告诉我范围:仅阶段 1 或阶段 1+2)?我可以直接加文件。只需回复你的选择。