文章总结: 本文复盘国密SM3摘要数据解析中的三个典型技术陷阱:不定长编码需用栈管理嵌套层级避免错位;OID解码需逐位验证防止字节误判;国密应用常对EncapsulatedContentInfo进行非标准扩展需兼容处理。通过真实案例详解ASN.1结构解析方法,强调工具化解析与业务定义对接的重要性。 综合评分: 85 文章分类: 技术标准,安全开发,漏洞分析,二进制安全,应用安全
那些年我们踩过的坑——摘要数据类型
原创
利刃信安 利刃信安
利刃信安
2026年6月1日 19:19 北京
在小说阅读器读本章
去阅读
那些年我们踩过的坑——摘要数据类型
摘要: 在密码应用开发中,DigestedData(摘要数据类型)是一个基础结构,承载着数据原文与它的消息摘要。但看似简单的ASN.1结构,却因为不定长编码、非标准扩展以及一个字节的OID解码偏差,让无数工程师在深夜怀疑人生。本文复盘了一个真实的SM3摘要数据解析过程,拆解那些让人“踩坑”的细节:不定长标签的结束条件、OID编码的两位联动规则、以及国密应用中对EncapsulatedContentInfo的“二创”。希望这些经验能帮助你在面对类似数据时,少走一些弯路。
一、背景:当DigestedData遇上国密
在国密改造项目中,我们经常需要处理一种被称为“摘要数据”的结构。
它由标准定义:
DigestedData ::= SEQUENCE {
version Version,
digestAlgorithm DigestAlgorithmIdentifier,
encapContentInfo EncapsulatedContentInfo,
digest Digest
}
字面意思再清楚不过:一个版本号、一个摘要算法标识、被摘要的内容、以及摘要值。当算法是SM3时,这个结构就变成了国密版CMS签名流程里的常客。 那天,我拿到了一段这样的十六进制数据:
3080020101300A06082A811CCF550183113080060A2A811CCF550601040201A0800410
313233347177657231323334717765720000000004206B8395AB5AD3C6FA8B685BEC56
2DDA1170FB3E5BE012080DEF614D78E031A1C10000
需求很简单:把它解析出来,验证一下摘要是否正确。然而,就是这个“简单”的活儿,让我掉进了一连串的坑。
二、第一坑:不定长编码的“幽灵”终结符
拿过数据,一眼看到开头的 30 80 —— 这是一个不定长编码的 SEQUENCE。在 BER/DER 编码中,如果长度字节为 80,表示数据块由两个连续的零字节 00 00 作为结束标记(EOC)。
我的解析思路是:逐层剥开。很快,我定位到了版本号 02 01 01(INTEGER 1),以及紧跟的一个定长 SEQUENCE 30 0A,内部是一个 OID 06 08 ...。毫无疑问,那就是摘要算法标识。
但问题出在后面的 30 80—— 又是一个不定长的 SEQUENCE,这应该是encapContentInfo。它里面嵌套了一个A0 80(上下文标签0,也是不定长),而A0的内部又套了一个04 10和 16 字节数据。到这里,EOC 的嵌套层级就开始让人头晕:最外层需要两个00 00结束,内层的A0 80也需要一个00 00,而encapContentInfo自己又需要一个00 00。
当我数完那一串00 00 时,就已经预感——不定长结构一旦嵌套,人工定位非常容易错位,程序实现也要小心处理栈。
教训: 处理不定长编码,一定要用栈式解析器,每遇到一个 80 长度就压栈,遇到 00 00 就出栈。不要把 00 00 当成数据的一部分。
三、第二坑:OID 解码,一个字节差出十万八千里
解析到摘要算法 OID 时,我得到了 2A 81 1C CF 55 01 83 11。按照标准,前两个节点 1.2 已经由 2A 编码,接下来:
- •
81 1C:两字节 VLV 编码,表示 156。 - • 接着是
CF 55,我习惯性地将其解析为(0x4F << 7) | 0x55 = 10197。这一步没错。 - • 下一字节是
01,我顺理成章地认为节点值是 1。 - • 再后面是
83 11,83表示后续两字节,(0x03 << 7) | 0x11 = 401。
于是,我拼出的 OID 是 1.2.156.10197.1.401。这正是 SM3 密码杂凑算法的标准 OID。
然而,当我兴冲冲地将解析结果展示出来时,却被指出一个诡异的错误:我在某处竟然把它误写成了 1.2.156.10197.85.1.401——凭空多了一个 85。
复盘才发现,因为 CF 55 是两字节,而 10197 的编码是 (0x4F) 和 0x55,但 0x4F 如果算成十进制是 79,而不是 85。原来是我在笔记中将 CF(0xCF)高位置 1 处理后,误以为第一位是“85”的编码,又把 55 当成单独的节点,于是编造出了一个并不存在的 85 弧。这个低级错误让我惊出一身冷汗:一个字节的误解,就足以将标准 SM3 OID 变成一个不存在的怪物。
正确的解码是:
1.2.156.10197.1.401,没有 85。这也是国家密码管理局正式颁定的 SM3 算法 OID。
事后我专门写了一个 OID 解码校验函数,要求每一位都进行反序列化和比对,再也不敢靠心算。
四、第三坑:非标准扩展的 encapContentInfo
按照 CMS 标准,EncapsulatedContentInfo 应该是一个包含 eContentType OID 和一个可选的 eContent 的 SEQUENCE。但在这段数据里,它长这样:
30 80
06 0A 2A 81 1C CF 55 06 01 04 // OID: 1.2.156.10197.6.1.4
02 01 A0 // INTEGER 160
A0 80
04 10 31 32 33 34 ... // "1234qwer1234qwer"
00 00
00 00
除了预期的 OID 和 [0] 内容,还多出了一个 INTEGER 160。这个多出来的字段显然不是标准的一部分,但却是该厂商国密实现中的约定——它可能代表了用户标识 "1234qwer1234qwer" 的某种长度参数,或是签名操作中需要的熵值。
如果按照标准解析器去严格匹配,这里一定会报结构错误。我们不得不根据业务需求,给解析器增加了可选的“容忍模式”,当遇到标准模板之外的类型时,跳过并记录警告。
教训: 国密标准在落地时,不同厂商可能会对 CMS 结构做出“微创新”。对接之前,务必拿到对方的 ASN.1 定义,不要想当然。
五、验证:最终的破局
拨开层层迷雾后,剩下的 digest 是一个 32 字节的 OCTET STRING:
04 20 6B 83 95 AB 5A D3 C6 FA 8B 68 5B EC 56 2D DA 11
70 FB 3E 5B E0 12 08 0D EF 61 4D 78 E0 31 A1 C1
我将字符串 1234qwer1234qwer 喂给 SM3 算法,得出的杂凑值与上面完全一致。那一刻,所有的不定长嵌套、OID 困扰、结构变异,都化为了控制台上的一行 Match。
六、写在最后
DigestedData 就像一个组装好的俄罗斯套娃,外表平实,内里却可能藏满机关。回顾这次解析之旅,三个坑值得铭记:
- • 不定长编码务必用栈管理层级,不要目测;
- • OID 解码要逐位验证,尤其注意两字节节点的低位合并;
- • 别盲目信任标准结构,国密应用中的非标准扩展是常态。
当你下一次面对一段神秘的 ASN.1 数据时,希望这篇文章能帮你绕过那些我们曾经“踩”过的地方。毕竟,坑踩过一次是经验,重复踩进去,那就是事故了。
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:利刃信安 利刃信安 利刃信安《那些年我们踩过的坑——摘要数据类型》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。









评论