马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?站点注册
×
PBIDEA:用 SM3 与 HMAC 做数据完整性校验
阅读说明
1. 适用版本:PBIDEA(基于 PB 12.5 运行环境;所用算法来自 PbIdea.dll 运行库,PB 10/12.5 均可调用)
2. 支持数据库:本文为纯算法/工具类,不操作任何数据库;PBIDEA 运行库独立于具体 DBMS,凡已安装 PbIdea.dll 的 PB 应用均可直接调用
3. 操作系统与环境要求:Windows 7+;需在工程中导入 websuite.pbl(含 sm3utils / uo_hash / uo_hmac),并确保 PbIdea.dll 在可搜索路径(与可执行文件同目录即可)
4. 难度系数:★★★☆☆(需理解 blob/hex 与“密钥参与哈希”的概念,但调用方式直接)
5. 其它阅读说明:示例均为最小可运行片段,贴在按钮 clicked 事件中即可看到 MessageBox 结果;生产环境密钥应从配置/加密存储读取,切勿硬编码
一、为什么需要哈希与 HMAC
数据在网络传输或落盘后,最怕两件事:被意外改坏(完整性)、被别人伪造(来源可信)。
- 哈希(摘要):把任意长度数据算成一串固定长度的值(SM3 输出 256 位 = 64 个十六进制字符)。相同输入永远得到相同输出,输入稍有变化输出就天差地别。它只能校验“有没有被改”,不能证明“是谁发的”——因为任何拿到数据的人都能重新算一遍。
- HMAC(带密钥的哈希):在哈希时把双方约定的密钥一起揉进去。没有密钥的人算不出正确的 HMAC,于是它既能校验完整性,又能证明数据确实来自持有密钥的一方。
PBIDEA 在 websuite.pbl 里给了三件趁手的工具:
| 对象 | 用途 | 关键特点 | | sm3utils | SM3 摘要、SM3-HMAC、SM3-KDF,以及 blob↔hex、base64 转换 | autoinstantiate,声明即可用;SM3 国密一步到位 | | uo_hash | 通用纯哈希(sha1/sha256/sha512/sm3…),无密钥 | 需 open(算法) → update → Final 流式 | | uo_hmac | 通用 HMAC(sha256/sm3…),带密钥 | open(密钥, 算法) → update → Final 流式,适合大文件分块 |
注意:sm3utils 是 autoinstantiate 对象,声明后即可调用方法,不需要 create;而 uo_hash / uo_hmac 是普通 nonvisualobject,示例中用局部实例 create + destroy,destroy 时其 destructor 会自动调用 close() 释放底层 C 句柄,最为稳妥。
二、API 全貌(对照真实源码)
sm3utils(autoinstantiate)
- blob sm3(readonly string data) / blob sm3(readonly blob data) —— SM3 摘要,返回 blob
- blob sm3hmac(readonly string key, readonly string data) 及 blob 组合的 4 个重载 —— SM3-HMAC,返回 blob
- blob sm3kdf(readonly string data, int len) —— SM3 密钥派生(KDF)
- string BlobToHex(readonly blob data) / blob HexToBlob(readonly string hex) —— blob 与十六进制互转
- string Base64Encode(readonly blob data) / blob Base64Decode(readonly string strBase64) —— base64 编解码
uo_hash(通用纯哈希,无密钥)
- boolean open(readonly string algo) —— 以算法名开算子(支持 sm3/sha1/sha224/sha256/sha384/sha512/sha3_256/ripemd160/whirlpool…)
- boolean update(readonly string data) / boolean update(readonly blob data) —— 累加数据(string 自动按 UTF-8)
- int Final(ref string hex) / int Final(ref blob hash) —— 取结果,hex 经 ref 返回
- close() —— 释放算子(destructor 已自动调用)
uo_hmac(通用 HMAC,带密钥)
- boolean open(readonly string key, readonly string algo) —— 两参:密钥 + 算法(算法名同上)
- boolean update(...) / int Final(ref string hex) / close() —— 同 uo_hash
三、典型用法示例
示例 1:SM3 摘要(一次性,最简单)
前置:工程已导入 websuite.pbl 且 PbIdea.dll 可用;把下方代码贴在窗口按钮 cb_run 的 clicked 事件中,运行点击即可。 - // 示例输入:待计算摘要的原始文本
- string ls_text
- ls_text = 'PowerBuilder 数据完整性校验'
- // 示例输入:十六进制输出存放变量
- string ls_hex
- // sm3utils 为 autoinstantiate 对象,声明即可用,无需 create
- sm3utils sm
- // 计算 SM3 摘要(string 入参自动按 UTF-8 处理),返回 blob
- blob lb_hash
- lb_hash = sm.sm3(ls_text)
- // 摘要是 blob,需转十六进制字符串才能显示与比对
- ls_hex = sm.BlobToHex(lb_hash)
- MessageBox('SM3 摘要(hex)', ls_hex)
复制代码
示例 2:SM3-HMAC 一次性校验(带密钥)
前置同示例 1;HMAC 需要收发双方约定的密钥,既防篡改又证明来源。 - // 示例输入:共享密钥(生产环境应从加密配置读取,切勿硬编码)
- string ls_key
- ls_key = 'my-secret-key-2026'
- // 示例输入:待校验的消息内容
- string ls_msg
- ls_msg = 'orderId=1001&amount=88.00'
- // sm3utils 为 autoinstantiate,声明即用
- sm3utils sm
- // 一次性计算 SM3-HMAC:参数顺序固定为 (key, data),返回 blob
- blob lb_hmac
- lb_hmac = sm.sm3hmac(ls_key, ls_msg)
- // 转十六进制输出
- string ls_hex
- ls_hex = sm.BlobToHex(lb_hmac)
- MessageBox('SM3-HMAC(hex)', ls_hex)
复制代码
示例 3:通用 HMAC(HMAC-SHA256)流式累加码
前置同示例 1;当数据很大或来自分块读取(如大文件、网络流)时,用 uo_hmac 分多次 update 最合适。 - // 示例输入:密钥与算法
- string ls_key
- ls_key = 'my-secret-key-2026'
- string ls_algo
- ls_algo = 'sha256' // 可选 sha1/sha256/sha384/sha512/sm3 等
- // 示例输入:分两段模拟大文件分块
- string ls_part1
- ls_part1 = '这是第一段内容'
- string ls_part2
- ls_part2 = '这是第二段内容'
- // 声明并创建 HMAC 算子对象(普通 nonvisualobject,需 create)
- uo_hmac lh
- lh = create uo_hmac
- // 以 (密钥, 算法) 打开算子 —— 注意是两参,不是 open(算法)
- boolean lb_ok
- lb_ok = lh.open(ls_key, ls_algo)
- // 分块累加数据(string 自动转 UTF-8)
- lb_ok = lh.update(ls_part1)
- lb_ok = lh.update(ls_part2)
- // 取结果:hex 经 ref 参数返回,需先声明 string 变量接收
- string ls_hex
- int li_rc
- li_rc = lh.Final(ref ls_hex)
- MessageBox('HMAC-SHA256(hex)', ls_hex)
- // destroy 触发 destructor 自动 close(),释放底层句柄
- destroy lh
复制代码
示例 4:通用纯哈希(SHA-256)对比
前置同示例 1;纯哈希不带密钥,仅做完整性校验,不能防伪造——适合校验本地文件是否被意外改坏。 - // 示例输入:待哈希文本
- string ls_text
- ls_text = 'hello pbidea'
- // 声明并创建哈希算子对象
- uo_hash lh
- lh = create uo_hash
- // 纯哈希 open 只传算法,不带密钥
- boolean lb_ok
- lb_ok = lh.open('sha256')
- // 累加数据
- lb_ok = lh.update(ls_text)
- // 取十六进制结果
- string ls_hex
- int li_rc
- li_rc = lh.Final(ref ls_hex)
- MessageBox('SHA-256(hex)', ls_hex)
- destroy lh
复制代码
示例 5:校验实战——比对 HMAC 判断真伪
接收方拿到「数据 + 随附的 HMAC(hex)」后,用同一密钥本地重算并比对,一致才放行。 - // 示例输入:收到的 HMAC(hex),由发送方随数据一并发来
- string ls_received
- ls_received = 'a1b2c3d4...' // 此处替换为真实收到的十六进制串
- // 本地用相同密钥重算得到的 hex(承接示例 2/3 的 ls_hex)
- // 比对:字符串相等即说明数据完整且来源可信
- if ls_hex = ls_received then
- MessageBox('校验结果', '数据完整且来源可信')
- else
- MessageBox('校验结果', '数据被篡改或来源不可信')
- end if
复制代码
四、边界情况与易错点
- 参数顺序别写反:sm3utils.sm3hmac 是 (key, data),先密钥后数据;写反会得到错误的 HMAC 且不易察觉。
- uo_hmac.open 是两参:open(key, algo)。很多人按纯哈希习惯写成 open(algo),编译能通过但密钥没进去,等于退化成纯哈希,失去来源认证能力。
- string 与 blob 入参的编码差异:update(string) / sm3(string) 内部自动按 UTF-8 编码;若你传入 blob,则按 blob 原始字节计算。同一段内容用 string 和用「手工 UTF-8 后的 blob」结果一致,但用 GBK blob 就会不同——跨语言对接(如 Java/Go 侧)务必统一 UTF-8。
- Final 的 hex 是 ref 输出:必须先用 string ls_hex 声明变量再 Final(ref ls_hex),不能用常量或未经声明的变量接收;返回的 int 是结果码(成功通常非 0/长度),可忽略或用于判错。
- blob 不可读,一定要转 hex/base64:sm3 / sm3hmac / Final(ref blob) 返回的都是 blob,直接 MessageBox 会显示乱码;显示与网络传输请用 BlobToHex 或 Base64Encode。
- 一次性 vs 流式结果一致:sm3utils.sm3hmac(key, 整段) 与 uo_hmac.open(key,algo) 后一次性 update(整段) 结果相同;只有当你分块 update 时流式才有意义,不要混用两套导致比对失败。
- sm3utils 不要 destroy:它是 autoinstantiate,声明即用、由 PB 自动管理,示例里没写 destroy;uo_hash/uo_hmac 才需要 create/destroy(或手动 close())。
五、与相近方案对比
| 场景 | 推荐做法 | 理由 | | 只要国密 SM3 摘要/SM3-HMAC,代码越短越好 | sm3utils.sm3 / sm3utils.sm3hmac | 一步到位,无需维护算子生命周期 | | HMAC 但算法要 sha256/sha512 等 | uo_hmac | 算法可配,且支持流式分块 | | 纯摘要(校验本地文件完整性,不防伪造) | uo_hash | 无密钥、语义清晰 | | 大文件/网络流分块校验 | uo_hmac / uo_hash 的 update 循环 | 不必一次性把整文件读进内存 |
一句话:SM3 国密且求简 → sm3utils;需要其它算法或流式 → uo_hmac/uo_hash。
六、进阶扩展
- 密钥派生:sm3utils.sm3kdf(data, len) 可基于口令派生固定长度密钥,比直接拿明文口令当 HMAC key 更安全。
- 传输友好:算出的 blob 用 Base64Encode 转 base64 再走 JSON/HTTP,比十六进制更省体积;接收方 Base64Decode 还原。
- SM3-KDF + HMAC 组合:对外的签名值建议「口令 → sm3kdf 派生密钥 → sm3hmac 签名」,避免弱口令直接当密钥。
- 与 uo_httpclient 联动:把 ls_hex 放进请求头(如 X-Sign)或作为 body 字段,服务端用同一密钥重算比对,即可实现 PB 客户端调用开放接口的请求签名校验(常见于回调验签、防重放)。
|