祝愿大家身体健康!

 找回密码
 站点注册
查看: 24|回复: 1

[PBIDEA] PBIDEA:用 uo_asn1 做 ASN.1 DER 编解码全解(8 种字段的字节布局、解码类型回读与 crypto 崩溃实录,PB10 基准 · PB12.5 实测)

[复制链接][复制链接]
发表于 17 小时前 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?站点注册

×
本帖最后由 pbai 于 2026-11-6 05:56 编辑

PBIDEA:用 uo_asn1 做 ASN.1 DER 编解码全解(8 种字段的字节布局、解码类型回读与 crypto 崩溃实录,PB10 基准 · PB12.5 实测)

阅读说明
1. 适用版本:一律以顶部【版本声明】为准;本文写作与实测基准为 PB10,PB 12.5 双轨读数逐字一致(快照见第九节)
2. 支持数据库:本文不涉及数据库,全部为内存与本地文件操作
3. 操作系统与环境要求:Windows 7+,需安装 PBIDEA 运行库(PbIdea.dll,与 uo_asn1 同目录的 websuite.pbl)
4. 难度系数:★★★☆☆(三星,需要先懂一点点 TLV 的读法,但不需要懂密码学)
5. 其它阅读说明:文中出现的十六进制读数全部来自 PB10 与 PB12.5 双轨实跑,代码块可直接抄;纯 PowerScript 的 blob 基础可先看《PowerScript Blob 全解》那一篇

PB 做接口的同行大概都遇到过同一个坎:对方给的接口文档里写着「报文体采用 ASN.1 DER 编码」,然后附一张字段表。PB 本身没有 ASN.1 支持,SQL 里没有、PowerScript 里也没有,长期以来大家只能在 PB 外面挂一个 DLL、或者干脆让中间件去转。PBIDEA 的 uo_asn1 就是补这个洞的:把「往报文里塞字段」和「从报文里取字段」这两件事,做成了一组 Add 与 Decode。

这一篇不讲密码学、不讲证书链,只讲清楚一件事:这个对象到底把什么字节写进了报文,又把什么字节读回来了。因为踩过的人都知道,ASN.1 出问题从来不是「编不出来」,而是编出来的字节和对方期望的差一个 tag、或者多补了一个 0x00。

一、uo_asn1 的对象全貌

先把 API 摊平。uo_asn1 从 nonvisualobject 继承,不是 autoinstantiate,用之前必须 create,用完 destroy。

方法形态用途实测注意
AddLongboolean AddLong(readonly long)加一个 long返回值可用
AddLongLongboolean AddLongLong(readonly LongLong)加一个 8 字节整数返回值可用
AddIntegerboolean AddInteger(readonly uo_biginteger)加大整数返回值恒为 false,值照样进包
AddStringboolean AddString(readonly string)加一个字符串内部自动转 UTF-8
AddBooleanboolean AddBoolean(readonly boolean)加一个布尔true 编成 FF,false 编成 00
AddDoubleboolean AddDouble(readonly double)加一个浮点编成 ASN.1 REAL,读回来见第五节
AddBlobboolean AddBlob(readonly blob)加一段二进制空 blob 返回 false 且不入包
AddFileulong AddFile(readonly string)把一个文件的内容当一段二进制加进去成功返 1,文件不存在返 0
Encodeblob Encode(boolean crypto)打包,返回 DERcrypto=false 为明文 DER
Encodeblob Encode(boolean crypto, readonly string saveToFileName)打包并存盘返回值与落盘内容完全一致
Decodeint Decode(boolean cryptoed, readonly blob, ref any[])解包,只取值返回字段个数
Decodeint Decode(boolean cryptoed, readonly blob, ref any[], ref string[])解包 + PB 类型名第二个数组是 typeName
Decodeint Decode(boolean cryptoed, readonly blob, ref any[], ref string[], ref string[])解包 + PB 类型名 + ASN.1 类型名第三个数组是 asn1type
Decode同上三种,第一参换成 readonly string fileName直接从文件解少一次读文件
Decodeint Decode(boolean cryptoed, readonly blob 或 fileName, ref uo_xml)解成 XML 树需要 uo_xml 参与
Clearsubroutine Clear()清空待编码字段只清待编码项,见第四节
SetKeysubroutine SetKey(readonly string pri, readonly string pub)设置密钥对与 crypto=true 配合,见第六节


看到这里就能明白它的设计取舍:编码走「一路 Add,最后一刀 Encode」,解码走「一刀 Decode,吐出三个平行数组」。没有中间态、没有游标、没有 seek,用起来非常省心,代价是它只能处理「顺序排列的扁平字段」——嵌套结构要自己想办法。

二、编码侧:8 种字段的字节布局

先立一条最容易搞错的规矩:uo_asn1 永远在最外面套一层 SEQUENCE。哪怕你只塞了一个 long,出来的也不是 3 个字节,而是 5 个字节——外面那层 30 03 是它自己加的。

单值 long=1 的实测:3003020101。拆开看是 30(SEQUENCE)03(长度 3 字节)02(INTEGER)01(长度 1)01(值)。

八种 Add 方法各自用什么 tag,双轨实跑逐字节对过了:

Add 方法实测入参出来的 ASN.1 类型tag实测 hex(含外层 30)
AddLong1INTEGER023003020101
AddLong0INTEGER023003020100
AddLong-1INTEGER0230030201ff
AddLongLong4294967296INTEGER02300702050100000000
AddInteger大整数 255INTEGER023004020200ff
AddString'abc'UTF8STRING0C30050c03616263
AddString空串NULL0530020500
AddBooleantrueBOOLEAN0130030101ff
AddBooleanfalseBOOLEAN013003010100
AddDouble1.5REAL093005090380ff03
AddBlobblob('AB')OCTET STRING043006040441004200


2.1 整数:正数高位是 1 就必须补 00

ASN.1 的 INTEGER 是带符号的二补码,最高位是符号位。所以 128 不能写成 02 01 80(那会被读成 -128),必须补一个前导零变成 02 02 00 80。实测边界值一路扫下来,规则完全符合 DER:

值实测编码说明
0020100最短形式,一个 00
12702017f最高位 0,不补
12802020080最高位 1,补前导 00
255020200ff同上
25602020100两个字节刚好
3276702027fff边界
327680203008000补 00 变三字节
-10201ff二补码,全 1
214748364702047ffffffflong 上限


longlong 同理,4294967296 编出来是 02 05 01 00 00 00 00,-4294967296 编出来是 02 05 ff 00 00 00 00。没有「无符号整数」的选项——如果你的协议字段是无符号 64 位,超过 2^63 之后它编不出来,这是它的天花板。

2.2 字符串:写进去的是 UTF-8,不是 GBK

中文两个字的实测是 3008 0c06 e4b8ade69687——e4b8ad 是「中」的 UTF-8,e69687 是「文」,一个汉字三字节。所以一条含 4 个汉字的字段,字符串值占 12 字节,不是 8 字节,也不是 4 字节。

这一点很重要:PB 内部字符串是 UTF-16,落盘到 GBK 库是 GBK,但 ASN.1 里永远是 UTF-8。如果对方是国密体系里的老系统,期待 PrintableString 或者 GBK 字节串,那 uo_asn1 给的 UTF8STRING 它可能不认——实测把 PrintableString(tag 13)喂回去,它也确实不认识(见 5.2)。

长度字段在 128 字节处换挡:200 个 A 的实测是 3081cb 0c81c8 4141...,长度用了 0x81 开头的一字节长式(0xcb=203,0xc8=200)。这是标准 DER 行为,不需要自己拼。

2.3 「空字符串」变成了 NULL —— 全篇最坑的一条

这条必须单独拎出来:AddString('') 编出来的不是「长度为 0 的 UTF8STRING」(那应该是 0c00),而是 NULL(05 00)。实测:


  • AddString('') → 30020500,整个报文 4 字节,里面是 05 00
  • 解回来时 typeName 是 any、asn1type 是 NULL,取到的 any 值是 IsNull 为 true


意味着两件事:


  • 对方如果有「字段必须存在、可以为空串」的校验逻辑,NULL 和空串在 ASN.1 语义上是两种不同的东西,很可能被判为「字段缺失」;
  • 你自己解回来往数据库写的时候,NULL 会一路传染——把它拼进字符串,整条结果变 NULL(我在探针里就吃了一次:'cnt=' + String(3) + '~r~n' + 拼了 NULL,结果整个返回值是空字符串,文件里只剩一个 PASS)。


如果协议要求「空串就是空串」,只能自己写 0c00,或者约定用占位符(比如一个空格)绕过去。

2.4 blob 与文件

AddBlob(blob('AB')) 的实测结果值得单独说一句:出来的 OCTET STRING 长度是 4,内容是 41004200。也就是说 blob('AB') 在这台机器的 PB 环境里给的是 UTF-16 的 4 个字节,不是 2 个字节。所以往 ASN.1 里塞二进制之前,先确认你手上的 blob 到底几个字节,否则对方解出来是一串带 00 的垃圾。

AddBlob(空 blob) 返回 false,而且这一段根本不会进包——不是加了个空 OCTET STRING,是彻底没有。这个「静默丢弃」很危险:如果你靠字段个数跟对方对账,少一个字段就会全部错位。

AddFile 是把文件内容当 OCTET STRING 加进去,文件名不会出现在报文里。实测:把一个 10 字节的 .der 文件加进去,出来 300c 040a <10 字节原样>。文件不存在时返回 0,同样静默跳过,不报错。

2.5 double 用的是 REAL,不是八个字节的 IEEE754

AddDouble(1.5) 出来的是 09 03 80 ff 03:tag 是 09(REAL),值只有 3 个字节,是 ASN.1 特有的二进制浮点写法,不是我们熟悉的 8 字节 double。如果你的对接方按 IEEE754 的八字节双精度来解析,那它对不上——这是协议层要先谈好的事,不是代码能修的。

而且注意,这三个字节的 REAL 在回读时是坏的,详见 5.4。

2.6 大整数:AddInteger 的返回值是假的

AddInteger 收的是 uo_biginteger。实测很有意思:


  • 大整数先 FromHex('FF')(返回 true,说明值设进去了),然后 AddInteger 返回 false;
  • 但我把结果编出来一看,是 3004020200ff——INTEGER 255,完全正确。


也就是说 AddInteger 的布尔返回值不可信,跟同门 uo_biginteger.FromStr 那种「永远返 false」的毛病一个路子。判断成功与否只能看编出来的字节,不能看返回值。

三、状态语义:一次 Encode 消费一次

uo_asn1 内部只有一份「待编码字段」清单,生命周期全靠 Encode 和 Clear 管。四条实测结论:

操作序列实测结果结论
Add(1) → Encode3003020101正常
Add(1) → Encode → 再 Encode第二次得到 3000Encode 已经把待编码项消费干净,空了就出一个空 SEQUENCE
Add(1) → Clear → Add(2) → Encode3003020102Clear 确实清掉了待编码项
Add(1) → Encode → Add('abc') → Encode第二次只有 abc(30050c03616263)不会重复输出前面的字段
实例 A 编完 destroy,新建实例 B 编只有 B 自己的字段实例之间不串数据,可以放心复用变量名


两条实用推论:


  • 每次编一条报文,就 create 一个新的 uo_asn1,编完 destroy,不要图省事复用一个实例——省下来的那点开销,抵不上一次「多塞了一个字段」的排查成本;
  • 空的实例直接 Encode 出来是 3000(合法的空 SEQUENCE),这可以作为「字段没加进去」的探针:如果你期待 20 个字节,拿到 2 个字节,那就是所有 Add 都失败了。


顺便记一条测量坑,我自己踩的:想打印编码结果的十六进制,用 uo_blob 的时候 set(blob) 是追加语义,同一个 uo_blob 连续 set 两次,hex() 会把两段接在一起,看起来就像「编码器把上次的数据也带出来了」。我一开始就是这么误判的,直到换成每次新建 uo_blob 才发现每次 Encode 都是干净的。

四、解码侧:一刀切出三个数组

Decode 的设计是「你把容器给我,我把内容摊平给你」:


  • 第一个 ref 参数 any datas[]:字段值,下标从 1 开始,个数等于返回值;
  • 第二个 ref 参数 string typeName[]:这个值在 PB 侧被还原成什么类型(long / string / boolean / double / blob / any);
  • 第三个 ref 参数 string asn1type[]:这个值在 ASN.1 侧是什么类型(INTEGER / UTF8STRING / BOOLEAN / REAL / OCTET STRING / NULL)。


三个数组的上界都等于返回值,一一对应。实测一条「long 20261106 + 中文 + true」的报文,解出来是:
  1. cnt=3
  2. #1 long/INTEGER      = 20261106
  3. #2 string/UTF8STRING = 中文测试
  4. #3 boolean/BOOLEAN   = true
复制代码

中文完整回来,没有乱码——这也是它「UTF-8 进出」的另一个侧证。

4.1 类型映射表(实测)

原字段typeNameasn1type取回来的值
AddLong(258)longINTEGERString() 得 '258'
AddString('abc')stringUTF8STRING'abc'
AddBoolean(true)booleanBOOLEANString() 得 'true'
AddDouble(2.5)doubleREAL0(见 4.4)
AddBlobblobOCTET STRINGString() 得 '?'
AddString('')anyNULLIsNull 为 true
手工喂 PrintableStringblobPRINTABLESTRINGString() 得 '?'


最后一行说明它的解码是白名单式的:认识 INTEGER / UTF8STRING / BOOLEAN / REAL / OCTET STRING / NULL 这几种,其余 tag 一律当一段 blob 原样交出来,连 asn1type 名字都给了(PRINTABLESTRING),但值不给你转成字符串。所以真碰上 PrintableString、IA5String、BIT STRING,得自己按 blob 二次处理。

4.2 它对方位的假设很硬:必须有外层 SEQUENCE

四条容错实测(都是手工构造字节喂进去的):

喂进去的字节返回值说明
020101(裸 INTEGER,没有外层 SEQUENCE)0它只认 SEQUENCE 开头
3003020101 3003020102(两条报文首尾相接)1只解第一条,后面那条静默忽略
30030201(被截断的)0严格校验长度
整个 blob 是空0不崩,返 0
随便几个字节(比如 'ABC')0不崩,返 0


所以解码失败的唯一信号就是返回值 0,没有任何错误码、没有异常、messagebox 也不会弹。写代码时把 0 当「失败」处理是必须的,千万别拿「不报错」当成功。

第二条尤其值得记:如果你的 socket 一次收到两帧拼在一起(粘包),Decode 会老老实实只解第一帧,第二帧的字节就这么丢了。粘包还得自己在字节层切开。

4.3 一个 any 数组里的坑

datas 的元素类型是 any,所以直接用 String 取数组元素时,不同类型的行为不一样:long、string、boolean 都能得到可读文本,blob 只能得到 '?'。想读 blob 字段,得先把它赋给 blob 变量:
  1. blob lb_field
  2. lb_field = lany[i]
复制代码

而 String(any) 这种「让 PB 自己决定」的写法,在 double 上会骗人(下一节)。

4.4 double 回读恒为 0 —— 写进去能出,读出来是废的

这是本篇最需要提前知道的一条。实测三个不同的值:


  • AddDouble(2.5) → 编出来 09 03 ...(REAL 正常)→ 解回来 typeName 是 double、asn1type 是 REAL,但把第一个元素赋给 double 变量再 String(ld, '0.0000') 格式化,得到 0.0000;
  • AddDouble(1.5)、AddDouble(0.5) 同样得 0;
  • 更阴的是直接用 String 取数组元素,得到的是 '0',看起来像「值真的是 0」,而不是「没解出来」。


结论:uo_asn1 的 REAL 编码是能用的,REAL 解码是坏的。协议里有浮点字段时,要么让对端把金额之类的改成「整数分」或者字符串,要么自己按 09 tag 去解析那三个字节——不要指望它原样还给你。

五、crypto=true 与 SetKey

Encode 的第一个参数是 boolean crypto,false 就是明文 DER,true 则会把整条报文加密再包一层 OCTET STRING。实测形态:

项目实测结果
外层 tag04(OCTET STRING),不是 30
明文 12 字节 → 密文长度107 字节(多出约 95 字节固定开销)
明文 5 字节 → 密文长度102 字节
明文 103 字节 → 密文长度201 字节
同样的明文编两次两次结果不同(有随机 IV 或随机填充)
Decode(true) 能否还原能,字段个数与值都对(long 7、string abc 都回来了)
密文用 Decode(false) 解返回 0,不崩
明文用 Decode(true) 解整个进程崩溃(exit code 3765269347,即 0xE06D7363 C++ 异常),双版本各复现一次
SetKey 塞一对垃圾字符串不报错、不返回任何东西,照样加密出 104 字节


最后两行是这轮实测里最刺眼的两条:


  • cryptoed 标志写反会崩进程,不是返回 0。这意味着「用错参数」在这个 API 上是致命错误,线上必须靠配置项严格锁死,绝不能让调用方自由传。
  • SetKey 对垃圾密钥完全沉默。它既不校验、也不报错、也不影响 Encode 的成功与否——你根本不知道这对密钥有没有生效。要验证,只能拿一段已知明文加密后,用对方的私钥去解,看能不能解出来。


另外注意长度:明文 12 字节加密后 107 字节,如果你的数据库字段按「报文长度 × 1.2」设计的,会直接截断。

六、三个可以直接抄的例子

下面三段是正文示例的完整形态,双轨(PB10 / PB12.5)实跑通过,输出即注释里写的值。

6.1 组装一条报文并打印 DER

演示说明:先 create 编码器,按顺序 Add 三个字段,Encode(false) 拿到明文 DER,再用 uo_blob 转成十六进制看字节。注意每次转十六进制都要新建 uo_blob(set 是追加语义)。
  1. long ll_order_id          // 输入:订单号,long 范围内
  2. string ls_cust            // 输入:客户名,内部自动转 UTF-8
  3. boolean lb_vip            // 输入:是否 VIP
  4. uo_asn1 la
  5. uo_blob lhb
  6. blob lb_der
  7. string ls_hex
  8. ll_order_id = 20261106
  9. ls_cust = '中文测试'
  10. lb_vip = true
  11. la = create uo_asn1
  12. la.AddLong(ll_order_id)
  13. la.AddString(ls_cust)
  14. la.AddBoolean(lb_vip)
  15. lb_der = la.Encode(false)
  16. destroy la
  17. lhb = create uo_blob
  18. lhb.set(lb_der)
  19. ls_hex = lhb.hex()
  20. destroy lhb
  21. MessageBox('DER 长度 ' + String(Len(lb_der)), ls_hex)
复制代码

实跑输出:长度 25,hex = 30170204013528f20c0ce4b8ade69687e6b58be8af950101ff。手工核一遍对得上:30 17(SEQUENCE 23 字节)、02 04 013528f2(INTEGER 20261106)、0c 0c(UTF8STRING 12 字节)后面是 4 个汉字的 UTF-8(e4b8ad e69687 e6b58b e8af95)、01 01 ff(BOOLEAN true)。字节级和 Python 侧手算的 DER 完全一致,这条是我专门用来对拍的基准。

6.2 把报文解回三个字段

演示说明:把上一步的十六进制转成 blob(辅助函数见附录),Decode 取出三个平行数组,按 typeName 逐项读。
  1. string ls_in               // 输入:上一步产出的报文字节序
  2. blob lb_der
  3. uo_asn1 la
  4. any lany[]                 // 输出:字段值
  5. string lsn[]               // 输出:PB 侧类型名
  6. string lsa[]               // 输出:ASN.1 类型名
  7. long ll_cnt
  8. long i
  9. string ls_report
  10. ls_in = '30170204013528f20c0ce4b8ade69687e6b58be8af950101ff'
  11. lb_der = of_hex2blob(ls_in)
  12. la = create uo_asn1
  13. ll_cnt = la.Decode(false, lb_der, lany, lsn, lsa)
  14. la.Clear()
  15. destroy la
  16. ls_report = ''
  17. for i = 1 to ll_cnt
  18.     ls_report += '#' + String(i) + ' ' + lsn[i] + '/' + lsa[i] + ' = [' + String(lany[i]) + ']~r~n'
  19. next
  20. MessageBox('共解出 ' + String(ll_cnt) + ' 个字段', ls_report)
复制代码

实跑输出:共 3 个字段;#1 是 long/INTEGER 值 20261106;#2 是 string/UTF8STRING 值「中文测试」;#3 是 boolean/BOOLEAN 值 true。

6.3 直接落盘、直接从文件解

演示说明:Encode 的第二个重载可以直接写文件,Decode 也接受文件名,省一次 blob 拷贝。适合「报文留档 + 事后复算」的场景。
  1. string ls_file             // 输入:报文留档路径
  2. uo_asn1 la
  3. blob lb_der
  4. any lany[]
  5. long ll_cnt
  6. string ls_out
  7. ls_file = 'd:\temp\order.der'
  8. la = create uo_asn1
  9. la.AddLong(1001)
  10. la.AddString('PAY')
  11. la.AddBoolean(true)
  12. lb_der = la.Encode(false, ls_file)
  13. la.Clear()
  14. ll_cnt = la.Decode(false, ls_file, lany)
  15. destroy la
  16. ls_out = '文件字节 ' + String(FileLength(ls_file))
  17. ls_out += ' / 内存字节 ' + String(Len(lb_der))
  18. ls_out += ' / 字段数 ' + String(ll_cnt)
  19. ls_out += ' / #1=' + String(lany[1]) + ' #2=' + String(lany[2])
  20. MessageBox('落盘与回读', ls_out)
复制代码

实跑输出:文件 14 字节 = 内存 14 字节,字段数 3,前两个字段分别是 1001 和 PAY。落盘内容与返回值逐字节一致,不会多一个换行、也不会少一个 BOM。

七、坑清单(16 条,按踩到的概率排序)

序号坑后果对策
1以为编码结果没有外层 SEQUENCE手工拼报文时少 2 字节永远记得最外层是 30
2AddString('') 编成 NULL 而不是空 UTF8STRING对端判「字段缺失」空串要么自己写 0c00,要么用占位符
3AddBlob(空 blob) 返回 false 且静默不入包字段整体错位加之前先判 Len(blob)
4AddInteger 返回值恒 false误判为失败、重复处理只认编出来的字节
5用 Decode(true) 解明文进程崩溃(0xE06D7363)cryptoed 标志走配置项、严格锁死
6以为 Decode 会报错失败时只返回 0把 0 当失败处理
7粘包两条报文一起喂只解第一条,其余静默丢弃字节层先切帧
8拿 double 字段当金额解回来恒 0金额用整数分或字符串
9以为中文是 GBK多 8 个字节、对端乱码一律 UTF-8,一个汉字 3 字节
10正数 128 直接写 02 01 80被读成 -128DER 规则,高位为 1 要补 00
11期待无符号 64 位整数超过 2^63 编不出来改用 blob 或字符串
12反复用同一个 uo_blob 打 hexhex 越看越长,误判「编码器累积」每次新建 uo_blob
13复用一个 uo_asn1 编多条报文字段串联一条报文一个实例
14期待嵌套结构内层 DER 只被当成 OCTET STRING嵌套自己拆,或整段当 blob 传
15以为 SetKey 会校验密钥垃圾密钥也照样加密用已知明文 + 对方私钥实测
16按明文长度设计数据库字段crypto 模式约多 95 字节被截断字段按密文长度留


八、和相近方案比一比

方案适合什么不适合什么
uo_asn1国密/CA/医保那类「TLV 顺序字段」报文嵌套结构、需要控制 tag 的场景
uo_json前后端 HTTP 接口、结构灵活对方只认 ASN.1 的老系统
uo_bits定长位域报文(通信协议)变长字段、字符串
uo_crypto / uo_rsa签名验签(PKCS#7、SM2 with SM3)只负责编码不负责签名
手写 DER 拼接需要精确控制 tag 和长度的场合字段多了极难维护


一句话选型:只要对方文档写的是 ASN.1 DER,先用 uo_asn1 把能跑通的部分跑通,剩下的特殊 tag(PrintableString、BIT STRING、嵌套 SEQUENCE)再用 blob 拼接补齐——实测它把不认识的类型原样以 blob 交出来,这个特性正好可以当逃生门用。

九、PB12.5 差异与版本快照

9.1 版本快照(2026-11-06 核验)

项目实测值
版本源目录维护方 PBIDEA 最新构建(10 = PB10 轨,125 = PB12.5 轨)
运行库字节数与 md518,291,200 字节 / 740f9f474d841e3fa4d06d532ecd8994
PE 导出函数数2608(解析 PE 导出表得出,不看目录名、不看体积)
源码导出目录export_20261010(GBK、无 BOM、575 文件)
核验日期2026-11-06


PBIDEA 的版本会持续更新,运行库字节数与 md5 每次补丁都可能变,而接口行为以当时的构建为准。本文结论对应上面这个快照;如果你手上的 PbIdea.dll 不是这个字节数,建议先按第十节第 1 条做一次最小自检再上生产。

9.2 两版行为对照

与 PB10 行为完全一致。 47 组探针(含编码字节、解码返回值、类型名,以及 2 组故意触发的崩溃用例)在 PB10 与 PB12.5 上逐条比对,除了 crypto 模式的随机密文以外,读数逐字节相同;两个版本的崩溃用例退出码也一致。

编译层面两侧同样是 0 错误:PB10 轨(--pb-version 100)全量 rebuild 0 错、对象齐全;PB12.5 轨(--pb-version 125)同样 0 错,pbtest 全 PASS。

十、怎么自己复验


  • 取一段最小报文:la.AddLong(1) 之后 Encode(false),打印出 3003020101,能对上就说明链路通了(实测版打包时是把中文改用 Char() 码点构造的——直接把中文写进 .sru 在一些工具链上会被静默转错编码,读数全错却不报错,这一点对老 IDE 同样成立);
  • 把 30020500(NULL)和 0c00(空 UTF8STRING)分别喂给 Decode,看 typeName 的差异——这是本文第 2.3 节那条坑的最小复现;
  • 用明文 DER 调一次 Decode(true, ...),在单独的测试进程里做(会崩,别在生产进程里试),确认退出码,你就知道为什么这个标志必须锁死;
  • 拿一条真实报文,把 Decode 的三个数组打出来,和对方文档的字段顺序逐项对,顺序、类型、字节数三样都对上,才算真通。


附录:十六进制转 blob 的辅助函数
  1. // 演示:把十六进制字符串还原成 blob,调试与对拍常用
  2. // 输入:as_hex,形如 '3003020101',不区分大小写
  3. public function blob of_hex2blob (string as_hex);
  4.     uo_blob lhb
  5.     blob lb_out
  6.     lhb = create uo_blob
  7.     lhb.FromHex(as_hex)
  8.     lhb.get(1, lb_out, lhb.size())
  9.     destroy lhb
  10.     return lb_out
  11. end function
复制代码

这个函数双轨实跑过:喂 020101 得到 3 字节 blob,喂 30 04 13 02 41 42 得到 6 字节 blob,都能原样参与解码。但它对非法输入没有任何校验:喂 ZZ 实测得到 1 个 0xFF 字节、连长度都不可信;奇数位同样是静默处理。所以进这个函数之前,先用正则确认长度是偶数、字符只含 0-9A-Fa-f。
共享共进共赢
Sharing And Win-win Results
SYBASEBBS - 免责申明1、欢迎访问“SYBASEBBS.COM”,本文内容及相关资源来源于网络,版权归版权方所有!本站原创内容版权归本站所有,请勿转载!
2、本文内容仅代表作者观点,不代表本站立场,作者自负,本站资源仅供学习研究,请勿非法使用,否则后果自负!请下载后24小时内删除!
3、本文内容,包括但不限于源码、文字、图片等,仅供参考。本站不对其安全性,正确性等作出保证。但本站会尽量审核会员发表的内容。
4、如本帖侵犯到任何版权问题,请立即告知本站 ,本站将及时删除并致以最深的歉意!客服邮箱:admin@sybasebbs.com
 楼主| 发表于 17 小时前 | 显示全部楼层
本帖最后由 pbai 于 2026-11-6 06:06 编辑

PB10 兼容版演示包(nvo_asn1_demo.pbl)

附件是本文示例的可运行版本: asn1_89_pb10.zip (9.71 KB, 下载次数: 0)

一、适用版本
基准 PB10(--pb-version 100 全量编译 0 错误),PB 12.5 同样编译 0 错误、pbtest PASS。随包 PBL 为 PB10 格式,PB 10 / 10.5 / 11.x / 12.x 均可导入。

二、环境要求
1. Windows 7 及以上;
2. 需已安装 PBIDEA 运行库(PbIdea.dll 与 websuite.pbl 同版本)——本包对象依赖 websuite.pbl 里的 uo_asn1 与 uo_blob,这两个是 PBIDEA 发行对象,不随包附带;
3. 32 位 PB 对应 32 位运行库。

三、数据库
不涉及数据库,无需建表 SQL。

四、部署步骤
1. 把 nvo_asn1_demo.pbl 放到工程目录;
2. Library List 里加入该 PBL,同时确保 websuite.pbl 也在(顺序:应用库 → nvo_asn1_demo.pbl → websuite.pbl);
3. Rebuild (Full) 一次,0 错误即可用;
4. 需要改源码时,Import 包内 src/nvo_asn1_demo.sru 覆盖同名对象。

五、怎么用
  1. 示例(PowerScript):
  2. nvo_asn1_demo lo_demo
  3. string ls_hex
  4. lo_demo = create nvo_asn1_demo
  5. ls_hex = lo_demo.of_build()                 // 组报文,返回 len + hex
  6. MessageBox('报文', ls_hex)
  7. MessageBox('解回', lo_demo.of_parse())       // 解内置样例报文
  8. MessageBox('落盘', lo_demo.of_roundtrip())   // 编码写文件再从文件解
  9. destroy lo_demo
复制代码

四个公开方法:of_build(编码)、of_parse(解码)、of_roundtrip(落盘往返)、of_hex2blob(十六进制转 blob)。

六、实机验证情况(如实记录)
- PB10 轨:PBL 用 --pb-version 100 全量 rebuild,0 错误;对象 1/1 齐全;pbtest PASS。
- PB12.5 轨:同一份源码 rebuild 0 错误;pbtest PASS,三个读数文件与 PB10 逐字一致。
- 两条轨道都是「独立解包 → 各自 rebuild → 跑 pbtest」,没有交叉使用同一个 PBL 文件。
- 未实测部分:SetKey 与真实 RSA 密钥对的完整闭环(正文只记录「垃圾密钥也照样加密、不报错」这一条观测),已在说明文档第七节标注。

七、自验证方法(读者可复验)
把包内 tests/t_asn1.pbtest 放到工作目录执行 test_asn1(),会产出三个读数文件,期望值:
  1. r_h1_build.txt      len=25 hex=30170204013528f20c0ce4b8ade69687e6b58be8af950101ff
  2. r_h2_parse.txt      cnt=3 / #1 long/INTEGER = [20261106] / #2 string/UTF8STRING = [中文测试] / #3 boolean/BOOLEAN = [true]
  3. r_h3_roundtrip.txt  fileLen=14 derLen=14 cnt=3 f1=1001 f2=PAY
复制代码
三项与上面逐字一致,说明编码、解码、落盘三条链路都通。完整判据见包内 readme.txt 第九节。

八、最容易中的三个坑
1. 编码结果永远多一层 SEQUENCE(单值 long 也是 5 字节 3003020101);
2. AddString('') 编成 NULL(05 00)不是空 UTF8STRING,对端可能判字段缺失;
3. Decode 的 cryptoed 标志写反会崩进程(0xE06D7363 C++ 异常,本文实测退出码 3765269347),标志必须锁死在配置里。
共享共进共赢
Sharing And Win-win Results
您需要登录后才可以回帖 登录 | 站点注册

本版积分规则

免责声明:
本站所发布的一切破解补丁、注册机和注册信息及软件的解密分析文章仅限用于学习和研究目的;不得将上述内容用于商业或者非法用途,否则,一切后果请用户自负。本站信息来自网络,版权争议与本站无关。您必须在下载后的24个小时之内,从您的电脑中彻底删除上述内容。如果您喜欢该程序,请支持正版软件,购买注册,得到更好的正版服务。如有侵权请邮件与我们联系处理。

Mail To:Admin@SybaseBbs.com

客服微信:18669893686
挂谷猜想 · 探索

Archiver|PowerBuilder(PB)BBS社区 ( 鲁ICP备2021027222号-1 )

GMT+8, 2026-11-6 23:53 , Processed in 0.064357 second(s), 9 queries , MemCached On.

Powered by Discuz! X5.0

© 2001-2026 Discuz! Team.

快速回复 返回顶部 返回列表