PBIDEA:用 uo_crypto 做 RSA 签名与验签,对接开放平台
本帖最后由 pbai 于 2026-8-31 11:05 编辑PBIDEA:用 uo_crypto 做 RSA 签名与验签,对接开放平台
阅读说明
1. 适用版本:PB 12.5 及以上;本文组件来自 PBIDEA(websuite.pbl 的 uo_crypto / uo_rsa),需配套 PbIdea.dll 运行库(含 RSA 签名、PKCS#1/PKCS#8 密钥解析)。
2. 支持数据库:本文为纯签名/验签,不依赖数据库;仅在"把签名回传服务器做业务校验"等扩展场景会用到你已有的后端库(按你实际后端选择,本文不绑定具体 DBMS)。
3. 操作系统与环境要求:Windows 7+;已安装 PBIDEA 运行库(PbIdea.dll 等),并把对应 pbl(websuite.pbl)导入工程。
4. 难度系数:★★★★☆(涉及密钥格式对齐、哈希算法选择与 UTF-8 编码细节,建议先读过 Day5 国密 SM4 一文了解 PbIdea.dll 的调用方式与编码约定)。
5. 其它阅读说明:示例私钥/公钥请用你自己的测试密钥替换;不要把真实业务私钥硬编码进源码提交到版本库。
一、为什么要用 RSA 签名
很多开放平台(支付、政务、第三方 API)要求你证明请求确实是你发出的、且中途没被篡改。做法是:
[*]你用私钥对报文(或报文摘要)做一次"签名",生成一个签名串随请求一起发出;
[*]平台用你事先登记过的公钥去"验签",验过才认这笔请求。
RSA 签名本质是"用私钥加密摘要、用公钥解密验证",它和"RSA 加密"不是一回事:加密是为了保密,签名是为了防抵赖和防篡改。本文只讲签名/验签这一件事,把 uo_crypto 上相关的 API 摸透。
二、核心 API 全貌(对照 websuite.pbl/uo_crypto)
uo_crypto 直接声明在 system library "PbIdea.dll" 里,和 RSA 签名/验签相关的有这几组(方法名取自真实导出源码,可直接抄):
方法签名(关键信息)用途
SignWithRSA(privateKey, text, method [, utf8 [, password]]) 返回 string用 PEM 私钥对字符串签名,返回 base64 签名
SignWithRSAVerify(publicKey, text, sign, method [, utf8]) 返回 boolean用 PEM 公钥验签,返回是否通过
AlignKey(key [, opensslKey [, privateKey]]) 返回 string把各种换行/头尾缺失的 PEM 补齐成标准格式,签名前务必先对齐
GenerateRSAKey(publicFile, privateFile [, keylen [, pkcs]]) 返回 boolean生成 RSA 密钥对文件
ToUTF8 / FromUTF8(string) 返回 blob / (blob) 返回 string字符串与 UTF-8 字节互转(uo_crypto 的对象方法,调用写作 luo_crypto.ToUTF8(...) / luo_crypto.FromUTF8(...),不是裸全局函数)
Base64Encode / Base64Decode(blob [, urlsafe [, removeEqualSign]])字节与 base64 互转
method 可取:sha1 / sha224 / sha256 / sha384 / sha512 / md4 / md5 / md5_sha1(注释里写明支持这些值)。开放平台现在几乎都用 sha256。
注意:还有另一个对象 uo_rsa(websuite.pbl/uo_rsa.sru),它的 sign/verify 接收的是 blob 原文(如 rsa.sign(luo_crypto.ToUTF8(原文), 私钥, "sha256", pss)),签名结果再用 luo_crypto.Base64Encode 收口。"RSA 签名本身是对任意二进制数据的摘要做运算,不局限于字符串"——图片、PDF、音视频等文件字节都能签。本文主推 uo_crypto.SignWithRSA 这条"字符串报文"省心链路(最常见于开放平台对接);当原文是二进制文件或需要 PSS 填充时,走 uo_rsa 的 blob 链路,详见第五节。
另外要澄清:上面列的是 uo_crypto 与"RSA 签名/验签"直接相关的 API;uo_crypto 其实也有 blob 级的 RSA 加解密(EncryptByPublicKey/EncryptByPrivateKey/DecryptByPublicKey/DecryptByPrivateKey,见第一节代码定义第 13~18 行),只是没有把 PSS/OAEP 包装成独立方法。本文聚焦"签名/验签",加解密不在主线范围内,但你知道它有即可,遇到"用公钥加密对称密钥"之类需求直接换这组方法。
三、最常用的一条链路:私钥签名 + 公钥验签
3.1 签名
前置:窗口 w_demo 上放一个按钮 cb_sign;工程已导入 websuite.pbl;准备你自己的 PEM 格式私钥字符串(下面用 ls_pri 占位,务必换成你自己的测试私钥)。
步骤:1) 声明 uo_crypto 并 create;2) 用 AlignKey 把私钥补齐为标准 PEM;3) 调用 SignWithRSA 得到 base64 签名;4) 用 MessageBox 看结果。
// 示例输入:待签名原文(实际场景替换为真实业务报文)
string ls_text
ls_text = 'order_no=20260824&amount=100×tamp=1766467200'
// 示例输入:你的 PEM 格式私钥(占位,请替换为真实测试私钥,切勿提交生产私钥)
string ls_pri
ls_pri = '-----BEGIN PRIVATE KEY-----'
ls_pri = ls_pri + '(此处填入你的 BASE64 私钥内容,多行拼接)'
ls_pri = ls_pri + '-----END PRIVATE KEY-----'
// 示例输入:哈希算法,开放平台常用 sha256
string ls_method
ls_method = 'sha256'
// 示例输入:原文是否按 UTF-8 编码参与签名(true=按 UTF-8)
boolean lb_utf8
lb_utf8 = true
// 对齐私钥为标准 PEM(补齐头尾与换行,避免签名失败)
uo_crypto luo_crypto
luo_crypto = create uo_crypto
string ls_pri_align
ls_pri_align = luo_crypto.AlignKey(ls_pri, true, true)
// 用私钥签名,返回 base64 签名串
string ls_sign
ls_sign = luo_crypto.SignWithRSA(ls_pri_align, ls_text, ls_method, lb_utf8)
MessageBox('RSA 签名结果(base64)', ls_sign)
3.2 验签
前置:同上,准备好与上面私钥配对的 PEM 公钥(占位替换);签名串用上面得到的 ls_sign。
步骤:用 AlignKey 对齐公钥 → 调 SignWithRSAVerify → MessageBox 显示"通过/失败"。
// 示例输入:待验签原文(必须与签名时完全一致,一字不差)
string ls_text
ls_text = 'order_no=20260824&amount=100×tamp=1766467200'
// 示例输入:与私钥配对的 PEM 公钥(占位,请替换)
string ls_pub
ls_pub = '-----BEGIN PUBLIC KEY-----'
ls_pub = ls_pub + '(此处填入你的 BASE64 公钥内容,多行拼接)'
ls_pub = ls_pub + '-----END PUBLIC KEY-----'
// 示例输入:上面签名得到的 base64 串
string ls_sign
ls_sign = '(填入 3.1 生成的签名串)'
// 示例输入:哈希算法与编码须与签名时一致
string ls_method
ls_method = 'sha256'
boolean lb_utf8
lb_utf8 = true
uo_crypto luo_crypto
luo_crypto = create uo_crypto
string ls_pub_align
ls_pub_align = luo_crypto.AlignKey(ls_pub, true, false)
boolean lb_ok
lb_ok = luo_crypto.SignWithRSAVerify(ls_pub_align, ls_text, ls_sign, ls_method, lb_utf8)
if lb_ok then
MessageBox('验签结果', '签名验证通过')
else
MessageBox('验签结果', '签名验证失败')
end if
四、生成一对测试密钥
如果你手头还没有密钥,可以用 GenerateRSAKey 直接落盘一对 PEM 文件,再用 uo_file 读出来交给 AlignKey 展示成标准格式。
前置:工程已导入 websuite.pbl(含 uo_crypto、uo_file);当前目录可写。
步骤:调 GenerateRSAKey 生成 pub.key/pri.key → 用 uo_file 读文本 → AlignKey 对齐 → MessageBox 看公钥。
// 示例输入:生成密钥的目标文件名与长度
string ls_pub_file
ls_pub_file = 'pub.key'
string ls_pri_file
ls_pri_file = 'pri.key'
// 示例输入:密钥位数,2048 为当前通用安全强度
int li_keylen
li_keylen = 2048
uo_crypto luo_crypto
luo_crypto = create uo_crypto
uo_file luo_file
luo_file = create uo_file
boolean lb_gen
lb_gen = luo_crypto.GenerateRSAKey(ls_pub_file, ls_pri_file, li_keylen, 'pkcs#8')
if lb_gen then
string ls_pub
ls_pub = luo_file.StringFromTextFile(ls_pub_file)
string ls_pub_align
// AlignKey(公钥, opensslKey=true, privateKey=false):与 3.2 保持一致,按 OpenSSL 标准格式补齐 PEM
ls_pub_align = luo_crypto.AlignKey(ls_pub, true, false)
MessageBox('生成的公钥(PEM)', ls_pub_align)
else
MessageBox('提示', '密钥生成失败')
end if
五、与 uo_rsa 的 blob 链路对比 / 进阶
上面 SignWithRSA 是"字符串进、base64 出"的省心链路。如果你的原文是二进制(比如要签一个文件内容),或想用 PSS 填充,可以走 uo_rsa 的 blob 链路:
[*]luo_rsa.sign(luo_crypto.ToUTF8(原文), 私钥, "sha256", false) 返回的还是 blob(第 4 参 use_pss 为布尔量,false=PKCS#1 v1.5、true=PSS),需要再 luo_crypto.Base64Encode(result) 收口成字符串;
[*]luo_rsa.verify(luo_crypto.ToUTF8(原文), 公钥, "sha256", 签名base64, false) 返回 boolean(签名串用字符串重载,blob 重载把第 4 参换成 blob 即可)。
两者对比:uo_crypto.SignWithRSA 适合"字符串报文 + 标准 PEM"的开放平台对接,步骤最少;uo_rsa 适合"二进制原文 / 需要 PSS"的场景,但要自己管 blob 与 base64 收口。日常对接平台优先用前者。
5.1 实例:给一个文件(PDF / 图片 / 任意二进制)做签名
签文件才是"签名不限字符串"最典型的应用。先用 uo_file.BlobFromFile 把文件读成 blob,再交给 uo_rsa.sign,完全不需要先把文件"变成字符串"。
前置:工程已导入 websuite.pbl(含 uo_crypto、uo_rsa、uo_file);准备一个测试文件(如 d:\test\doc.pdf)和配对的 PEM 私钥。
步骤:1) 读文件成 blob;2) 对齐私钥;3) uo_rsa.sign(blob, 私钥, "sha256") 得 blob 签名;4) Base64Encode 收口;5) MessageBox 看结果。
// 示例输入:待签名的文件(任意二进制,PDF/图片/音视频均可)
string ls_file
ls_file = 'd:\test\doc.pdf'
// 示例输入:你的 PEM 格式私钥(占位,请替换为真实测试私钥)
string ls_pri
ls_pri = '-----BEGIN PRIVATE KEY-----'
ls_pri = ls_pri + '(此处填入你的 BASE64 私钥内容,多行拼接)'
ls_pri = ls_pri + '-----END PRIVATE KEY-----'
// 示例输入:哈希算法,sha256 通用
string ls_method
ls_method = 'sha256'
uo_crypto luo_crypto
luo_crypto = create uo_crypto
uo_rsa luo_rsa
luo_rsa = create uo_rsa
uo_file luo_file
luo_file = create uo_file
// 把文件读成 blob(这就是被签名的"原文",无需先转字符串)
blob lb_data
lb_data = luo_file.BlobFromFile(ls_file)
// 对齐私钥为标准 PEM
string ls_pri_align
ls_pri_align = luo_crypto.AlignKey(ls_pri, true, true)
// 对 blob 原文签名(第 4 参 pss=false 用 PKCS#1 v1.5,true 用 PSS)
blob lb_sign
lb_sign = luo_rsa.sign(lb_data, ls_pri_align, ls_method, false)
// blob 签名收口成 base64 字符串便于传输
string ls_sign_b64
ls_sign_b64 = luo_crypto.Base64Encode(lb_sign)
MessageBox('文件签名结果(base64)', ls_sign_b64)
进阶扩展:如果你的平台要求的是 PKCS#7 带证书签名(常见于电子签章、政务回执),uo_crypto 还提供 PKCS7sign(blob data, pfx证书, 密码, algo) 与 PKCS7verify(...)(源码定义第 263~268 行)。注意第一参 data 是 blob——文件/字节直接传 blob 即可,pfx 证书可以是文件读出的 blob 或 base64 解码后的 blob,传入直接出签名串,不必自己拆私钥。这是 RSA 签名的一个常用变体,遇到此类需求直接换这对方法。
六、边界情况与常见坑
[*]原文不一致验签必定失败:验签时 text 必须与签名时的 text 逐字节一致(空格、参数顺序、换行都算)。开放平台通常要求按固定规则拼接后再签名,拼接错了验签 100% 不过。
[*]UTF-8 开关两边必须一致:签名传 utf8=true 表示把字符串先转 UTF-8 再算摘要;验签也必须 utf8=true,否则两边摘要对不上。中文报文务必两端统一。
[*]密钥必须先 AlignKey:你从配置或接口拿到的 PEM 经常缺头尾、\n 被转成空格或丢失。直接拿去签名会报"密钥格式错误",先用 AlignKey(key, opensslKey, privateKey) 补齐(公钥 privateKey=false、私钥 privateKey=true)最稳。
[*]method 要匹配:签名用 sha256 验签也必须 sha256;平台文档写 SHA256withRSA 对应的就是 method='sha256'。
[*]私钥别进版本库:示例里把私钥写死只是为了演示。真实项目把私钥放配置文件或加密存储,且不要提交到 git。
[*]PKCS#1 还是 PKCS#8:GenerateRSAKey 的 pkcs 参数与 AlignKey 的 opensslKey 要对应;和平台对接前先确认对方要的是哪种格式,用错了一样签不出来。
[*]二进制原文别硬塞字符串链路:uo_crypto.SignWithRSA 的入参 text 是 string 类型(源码第 40~42 行),没有 blob 重载。要签文件/图片等二进制数据,请用第五节 uo_rsa.sign(blob, ...) 的 blob 链路,配合 uo_file.BlobFromFile 读文件——这正是"签名不限字符串"的那条路。
七、小结
RSA 签名/验签在 PBIDEA 里就靠 uo_crypto.SignWithRSA / SignWithRSAVerify 两条主线:私钥签名出 base64,公钥验签拿 boolean。记住三件事——签名前先 AlignKey 对齐密钥、UTF-8 与 method 两端一致、原文一字不差。需要二进制(文件/图片)或 PSS 时切到 uo_rsa 的 blob 链路(配合 uo_file.BlobFromFile 读文件),需要带证书签章时切到 PKCS7sign。签名本质是对任意二进制数据的摘要做运算,不局限于字符串——字符串报文走 SignWithRSA,文件字节走 uo_rsa.sign(blob,...)。把这一套跑通,对接绝大多数开放平台的签名校验都没问题了。
还有个,uo_rsa,专干RSA的活
ehxz 说得对,uo_rsa 才是 PBIDEA 里专门干 RSA 的"引擎"对象,Day14 那篇只是把它在第五节一笔带过做了对比,确实讲浅了。这里把 uo_rsa 单独拎出来讲全——它和 uo_crypto 是"底层引擎"与"便捷封装"的关系,两者都得会用。
一、先说清分工:uo_rsa 与 uo_crypto 不是一回事
[*]uo_rsa(纯 RSA 引擎,blob 级):直接包了 pbidea.dll 的 evpRsa* 系列,所有输入输出都是 blob——加密/解密、签名/验签、生成密钥、随机串,全在 blob 层面干活。它还独占 PSS 签名填充和 OAEP 加密填充这两种硬核模式,uo_crypto 没有。
[*]uo_crypto(便捷封装,字符串级):Day14 讲的 SignWithRSA/SignWithRSAVerify 是"字符串进、base64 出",内部其实也是调同一套 EVP,只是替你做了 PEM 对齐、UTF-8、base64 收口,省事但能力面窄(没有 OAEP 加密、没有 PSS)。
结论:对接开放平台做"字符串报文签名"用 uo_crypto 最省事;要做真正的 RSA 加密通信、PSS 签名、OAEP 加密,或者原文是二进制文件,就得上 uo_rsa。
二、uo_rsa 的 API 全貌(取自 websuite.pbl/uo_rsa.sru 真实导出)
下面方法名、参数、常量均来自源码,可放心抄。
方法/常量真实签名用途
生成密钥RsaKey(bits, ref prikey, ref pubkey)(subroutine)生成 RSA 密钥对;私钥非空、公钥为空时,会从私钥推导出公钥
加密Encrypt(data:blob, pubkey:string [, padding, md, mgf1]) → blob用公钥加密 blob 原文
解密Decrypt(data:blob, prikey:string [, padding, md, mgf1]) → blob用私钥解密
签名sign(data:blob, prikey:string, md:string [, use_pss:boolean]) → blob用私钥对 blob 原文签名
验签verify(data:blob, pubkey:string, md:string, signData:blob [, use_pss]) → boolean(另有接收 base64 字符串的重载)验签,返回是否通过
随机串random() → string生成随机串(密钥派生/盐值可用)
OAEP 加密封装e_rsaecboaepwithsha256andmgf1padding(data:blob, pubkey:string) → blob一步 OAEP+SHA256 加密,对标 Java RSA/ECB/OAEPWithSHA-256AndMGF1Padding
OAEP 解密封装d_rsaecboaepwithsha256andmgf1padding(data:blob, prikey:string) → blob一步 OAEP+SHA256 解密
填充常量RSA_PKCS1_PADDING=1、RSA_SSLV23_PADDING=2、RSA_NO_PADDING=3、RSA_PKCS1_OAEP_PADDING=4、RSA_X931_PADDING=5、RSA_PKCS1_PSS_PADDING=6传给 Encrypt/Decrypt/sign 的 padding 参数
md 可用值(部分):md5 / sha1 / sha224 / sha256 / sha384 / sha512 / sm3 / blake2b* / sha3_* 等。
注意:源码注释示例里出现过 rsa.RsaEncrypt/rsa.RsaDecrypt 这种写法,那只是作者随手写的,对象里实际不存在这两个名字,正确就是 Encrypt/Decrypt,别抄错。
三、示例 1:用 uo_rsa 做签名 + 验签(完整链路)
前置:工程已导入 websuite.pbl;准备你自己的 PEM 私钥/公钥(下面用占位,务必替换)。
步骤:1) 声明并 create uo_rsa;2) 用 uo_crypto.AlignKey 把 PEM 对齐成标准格式(uo_rsa 本身不替你对齐,这一步别省);3) 用 toUtf8 把原文转 blob;4) sign 得到签名 blob,base64 收口看结果;5) verify 验签。
// 示例输入:待签名原文(实际场景替换为真实业务报文,务必与验签时逐字节一致)
string ls_text
ls_text = 'order_no=20260824&amount=100×tamp=1766467200'
// 示例输入:你的 PEM 私钥(占位,请替换为你自己的测试私钥,切勿提交生产私钥)
string ls_pri
ls_pri = '-----BEGIN PRIVATE KEY-----'
ls_pri = ls_pri + '(此处填入你的 BASE64 私钥内容,多行拼接)'
ls_pri = ls_pri + '-----END PRIVATE KEY-----'
// 示例输入:与私钥配对的 PEM 公钥(占位,请替换)
string ls_pub
ls_pub = '-----BEGIN PUBLIC KEY-----'
ls_pub = ls_pub + '(此处填入你的 BASE64 公钥内容,多行拼接)'
ls_pub = ls_pub + '-----END PUBLIC KEY-----'
// 示例输入:哈希算法,开放平台常用 sha256
string ls_md
ls_md = 'sha256'
uo_rsa luo_rsa
luo_rsa = create uo_rsa
// uo_rsa 不替你对齐 PEM,先用 uo_crypto.AlignKey 补齐头尾与换行
uo_crypto luo_crypto
luo_crypto = create uo_crypto
string ls_pri_align
ls_pri_align = luo_crypto.AlignKey(ls_pri, true, true) // 私钥:opensslKey=true, privateKey=true
string ls_pub_align
ls_pub_align = luo_crypto.AlignKey(ls_pub, true, false) // 公钥:privateKey=false
// 签名:原文先转 UTF-8 成 blob,私钥 + md,返回签名 blob
blob lb_data
lb_data = luo_crypto.ToUTF8(ls_text)
blob lb_sign
lb_sign = luo_rsa.sign(lb_data, ls_pri_align, ls_md) // 默认 PKCS#1 v1.5 填充
// 收口成 base64 方便传输
string ls_sign_b64
ls_sign_b64 = luo_crypto.Base64Encode(lb_sign, false)
MessageBox('uo_rsa 签名(base64)', ls_sign_b64)
// 验签:用同样原文 + 公钥 + 同一 md 验,返回 boolean
boolean lb_ok
lb_ok = luo_rsa.verify(lb_data, ls_pub_align, ls_md, lb_sign)
if lb_ok then
MessageBox('验签结果', '签名验证通过')
else
MessageBox('验签结果', '签名验证失败')
end if
四、示例 2:PSS 签名——uo_crypto 做不到,uo_rsa 可以
前置:同上(密钥已对齐)。PSS 是概率性签名,安全性更高,部分新平台(尤其国密/金融)要求。
步骤:sign 第 4 个参数传 true 开 PSS;verify 对应也传 true。
// 示例输入:待签名原文(与验签保持一致)
string ls_text
ls_text = 'amount=100&ts=1766467200'
// 示例输入:已对齐的 PEM 私钥/公钥(占位,请替换)
string ls_pri_align
ls_pri_align = '(填入用 uo_crypto.AlignKey 对齐后的私钥)'
string ls_pub_align
ls_pub_align = '(填入对齐后的公钥)'
// 示例输入:哈希算法
string ls_md
ls_md = 'sha256'
uo_rsa luo_rsa
luo_rsa = create uo_rsa
uo_crypto luo_crypto
luo_crypto = create uo_crypto
// PSS 签名:sign 第 4 参 use_pss=true
blob lb_data
lb_data = luo_crypto.ToUTF8(ls_text)
blob lb_sign
lb_sign = luo_rsa.sign(lb_data, ls_pri_align, ls_md, true)
string ls_sign_b64
ls_sign_b64 = luo_crypto.Base64Encode(lb_sign, false)
MessageBox('PSS 签名(base64)', ls_sign_b64)
// PSS 验签:verify 第 5 参 use_pss=true,必须用同一 md
boolean lb_ok
lb_ok = luo_rsa.verify(lb_data, ls_pub_align, ls_md, lb_sign, true)
if lb_ok then
MessageBox('PSS 验签', '通过')
else
MessageBox('PSS 验签', '失败')
end if
五、示例 3:RSA 加密 + 解密(真正"加密通信",uo_crypto 没有这能力)
前置:同上(密钥已对齐)。RSA 加密用公钥、解密用私钥;原文长度受密钥长度限制(2048 位约 190 字节内),长报文应改用"RSA 加密对称密钥 + 对称加密数据"的混合方案。
步骤:Encrypt(原文blob, 公钥) 得密文 blob → base64 收口;Decrypt(密文blob, 私钥) 还原。
// 示例输入:要加密的短原文(如会话密钥、令牌,长度控制在 190 字节内)
string ls_plain
ls_plain = 'session-token-20260824-abc'
// 示例输入:已对齐的 PEM 公钥/私钥(占位,请替换)
string ls_pub_align
ls_pub_align = '(填入对齐后的公钥)'
string ls_pri_align
ls_pri_align = '(填入对齐后的私钥)'
uo_rsa luo_rsa
luo_rsa = create uo_rsa
uo_crypto luo_crypto
luo_crypto = create uo_crypto
// 加密:公钥 + 默认 PKCS#1 v1.5 填充(padding 省略即走默认)
blob lb_plain
lb_plain = luo_crypto.ToUTF8(ls_plain)
blob lb_cipher
lb_cipher = luo_rsa.Encrypt(lb_plain, ls_pub_align)
string ls_cipher_b64
ls_cipher_b64 = luo_crypto.Base64Encode(lb_cipher, false)
MessageBox('RSA 密文(base64)', ls_cipher_b64)
// 解密:私钥还原原文
blob lb_dec
lb_dec = luo_rsa.Decrypt(lb_cipher, ls_pri_align)
string ls_back
ls_back = luo_crypto.FromUTF8(lb_dec)
MessageBox('解密还原', ls_back)
六、生成密钥对(RsaKey)与 OAEP 封装
[*]生成密钥:luo_rsa.RsaKey(2048, ls_pri, ls_pub),两个 ref 参数返回 PEM 字符串;只传私钥、公钥留空,会从私钥自动推导出公钥。生成后用 uo_file 落盘或 uo_crypto.AlignKey 再对齐一下更稳。
[*]OAEP 一步到位:不想自己拼 padding/md/mgf1 的,直接用封装方法 e_rsaecboaepwithsha256andmgf1padding(原文blob, 公钥) 加密、d_rsaecboaepwithsha256andmgf1padding(密文blob, 私钥) 解密,等价于 Java 的 RSA/ECB/OAEPWithSHA-256AndMGF1Padding,和主流后端互通最省心。
七、边界坑(比 uo_crypto 更要注意)
[*]uo_rsa 不管 PEM 对齐:它吃的是"已对齐的标准 PEM 字符串",直接丢未对齐的进去会报密钥格式错。务必先 uo_crypto.AlignKey(key, true, 是否私钥)。
[*]全程 blob,记得 UTF-8 与 base64 收口:原文 toUtf8 进 blob,结果 base64encode 出字符串;验签/解密出来是 blob,中文要 fromUtf8 转回。这一步漏了就会出现"签名对但验不过"或"解密出来是乱码"。
[*]padding 要两端一致:加密/解密、签名/验签的 padding(PKCS#1 v1.5 还是 PSS/OAEP)必须配对,和对接方约定清楚。
[*]原文长度限制:RSA 直接加密受密钥长度限制(2048 位约 190 字节),大文件别直接整段加密,走混合加密。
[*]私钥别进版本库:同 Day14,示例硬编码只为演示,真实项目外置并加密存储。
一句话收口:uo_crypto 是"字符串级便捷封装",uo_rsa 是"blob 级纯 RSA 引擎"——要签名省事用前者,要加密通信 / PSS / OAEP 用后者,两者都吃标准 PEM(记得先 AlignKey)。 把 Ehxz 提的这点补上,RSA 这块在 PBIDEA 里就算齐活了。 文章的表述里老是提字符串,有误导嫌疑。签名本身不管你内容是什么,可以是图片,可以是PDF,可以是影视,可以是一切数据,并不局限于字符串。 ehxz 这刀切得准——我前面两篇(主楼 + uo_rsa 补充)确实"老提字符串",表述不严谨,容易让人误以为 RSA 签名只能处理字符串。这里把话说全、把之前不严谨的地方改过来。
一、先认错:签名本来就不挑内容
ehxz 说得对:RSA 签名本质是对"任意二进制数据的摘要"做私钥运算,内容是什么无所谓——字符串、图片、PDF、音视频、任何文件字节,都能签。所谓"签字符串"只是最常用、最省事的入口,绝不是能力边界。
我在前面说"uo_crypto 没有加密能力"也错了,下面一并纠正。
二、为什么前面老提字符串:两条真实链路
对照 websuite.pbl/uo_crypto.sru 真实导出:
[*]uo_crypto.SignWithRSA(privateKey, text, method [, utf8 [, password]]) —— 第 40~42 行,注意 text 参数类型是 string,确实只有字符串入口,没有 blob 重载。所以这条"便捷链路"天然只接字符串。
[*]uo_rsa.sign(data:blob, prikey, md [, use_pss]) —— websuite.pbl/uo_rsa.sru 第 23~24 行,入参 data 是 blob。图片/PDF 直接当 blob 喂进来就能签。
也就是说:"字符串"只是 uo_crypto 那条链的入口限制,不是 RSA 签名的限制。我前面没把这个区分点透,确实有误导。补一句关键事实——
uo_crypto 其实也自带 RSA 加解密:EncryptByPublicKey(strPublicKey, text:blob)、EncryptByPrivateKey、DecryptByPublicKey、DecryptByPrivateKey(第 13~18 行),全部吃 blob。只是它没有像 uo_rsa 那样把 PSS / OAEP 包装成独立方法,所以我上条回帖说"uo_crypto 没有加密能力"是错的,特此更正。
三、对任意二进制数据签名:标准做法
核心就一句:先把文件/数据读成 blob,再交给 uo_rsa.sign。读文件成 blob 用 PBIDEA 自带的 uo_file.BlobFromFile(文件名),或裸全局函数 ReadFile(文件名)(两者底层都是 BlobFromFile)。
前置:工程已导入 websuite.pbl;准备你自己的 PEM 私钥(占位替换);待签名的文件(图片/PDF/任意)路径。
步骤:1) 用 uo_file.BlobFromFile 把文件读成 blob;2) 用 uo_crypto.AlignKey 对齐私钥;3) uo_rsa.sign(blob, 私钥, md) 得签名 blob,base64 收口;4) MessageBox 看结果。
// 示例输入:待签名的任意文件(图片/PDF/音视频均可,这里用一张图做示范)
string ls_file
ls_file = 'd:\docs\contract.pdf'
// 示例输入:你的 PEM 私钥(占位,请替换为你自己的测试私钥,切勿提交生产私钥)
string ls_pri
ls_pri = '-----BEGIN PRIVATE KEY-----'
ls_pri = ls_pri + '(此处填入你的 BASE64 私钥内容,多行拼接)'
ls_pri = ls_pri + '-----END PRIVATE KEY-----'
// 示例输入:哈希算法,开放平台常用 sha256
string ls_md
ls_md = 'sha256'
// 把任意文件读成 blob —— 这一步就是"不限字符串"的关键
uo_file luo_file
luo_file = create uo_file
blob lb_payload
lb_payload = luo_file.BlobFromFile(ls_file)
// 对齐私钥为标准 PEM(uo_rsa 不替你对齐,这一步别省)
uo_crypto luo_crypto
luo_crypto = create uo_crypto
string ls_pri_align
ls_pri_align = luo_crypto.AlignKey(ls_pri, true, true)
// 对 blob 原文签名(图片/PDF 的字节直接参与,不转字符串)
uo_rsa luo_rsa
luo_rsa = create uo_rsa
blob lb_sign
lb_sign = luo_rsa.sign(lb_payload, ls_pri_align, ls_md) // 默认 PKCS#1 v1.5 填充
// 收口成 base64 方便传输/落库
string ls_sign_b64
ls_sign_b64 = luo_crypto.Base64Encode(lb_sign, false)
MessageBox('对任意文件的签名(base64)', ls_sign_b64)
验签时把同一文件再用 BlobFromFile 读成 blob,连同公钥和签名 blob 交给 uo_rsa.verify(lb_payload, 公钥, md, lb_sign) 即可,逻辑完全对称。
四、一句话把"字符串 vs 任意数据"讲清
[*]内容是字符串报文(对接开放平台最常见):直接 uo_crypto.SignWithRSA 最省事,字符串进、base64 出。
[*]内容是文件/二进制(图片、PDF、音视频、任意字节流):先 BlobFromFile/ReadFile 读成 blob,再 uo_rsa.sign(blob, ...);要 PSS 就传 use_pss=true。
[*]两条链底层都是同一套 EVP,区别只在入口是 string 还是 blob,能力上都能签"一切数据"。
五、顺带纠正我上条回帖的一处不严谨
上条我说"uo_crypto 没有加密能力"——不对。uo_crypto 有 EncryptByPublicKey / EncryptByPrivateKey / DecryptByPublicKey / DecryptByPrivateKey(全是 blob 级 RSA 加解密),只是没 PSS/OAEP 包装。真正的分工应是:
[*]uo_crypto:字符串签名(SignWithRSA)最省事 + blob 级 RSA 加解密(EncryptBy*)+ 国密/哈希/base64 全家桶;
[*]uo_rsa:纯 blob 引擎,独占 PSS 签名、OAEP 加密,且 sign/verify 直接吃任意 blob(最适合"签文件")。
感谢 ehxz 挑刺,这几篇补完,PBIDEA 里 RSA 签名"从字符串到一切二进制"就算讲透了。
补充:源码注释示例里出现过 rsa.RsaEncrypt / rsa.RsaDecrypt 这种写法(第 45、56 行注释),那只是作者随手写的,对象里实际不存在这两个名字,真实是 encrypt / decrypt(小写,第 16、19 行),别抄错。
页:
[1]