那些年我们踩过的坑——摘要数据类型

admin 2026-06-03 04:04:10 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文复盘国密SM3摘要数据解析中的三个典型技术陷阱:不定长编码需用栈管理嵌套层级避免错位;OID解码需逐位验证防止字节误判;国密应用常对EncapsulatedContentInfo进行非标准扩展需兼容处理。通过真实案例详解ASN.1结构解析方法,强调工具化解析与业务定义对接的重要性。 综合评分: 85 文章分类: 技术标准,安全开发,漏洞分析,二进制安全,应用安全


cover_image

那些年我们踩过的坑——摘要数据类型

原创

利刃信安 利刃信安

利刃信安

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 1183 表示后续两字节,(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
&nbsp; &nbsp;06 0A 2A 81 1C CF 55 06 01 04 &nbsp; // OID: 1.2.156.10197.6.1.4
&nbsp; &nbsp;02 01 A0 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // INTEGER 160
&nbsp; &nbsp;A0 80
&nbsp; &nbsp; &nbsp; 04 10 31 32 33 34 ... &nbsp; &nbsp; &nbsp; &nbsp;// "1234qwer1234qwer"
&nbsp; &nbsp; &nbsp; 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
&nbsp; &nbsp; &nbsp; 70 FB 3E 5B E0 12 08 0D EF 61 4D 78 E0 31 A1 C1

我将字符串 1234qwer1234qwer 喂给 SM3 算法,得出的杂凑值与上面完全一致。那一刻,所有的不定长嵌套、OID 困扰、结构变异,都化为了控制台上的一行 Match


六、写在最后

DigestedData 就像一个组装好的俄罗斯套娃,外表平实,内里却可能藏满机关。回顾这次解析之旅,三个坑值得铭记:

  • • 不定长编码务必用栈管理层级,不要目测;
  • • OID 解码要逐位验证,尤其注意两字节节点的低位合并;
  • • 别盲目信任标准结构,国密应用中的非标准扩展是常态。

当你下一次面对一段神秘的 ASN.1 数据时,希望这篇文章能帮你绕过那些我们曾经“踩”过的地方。毕竟,坑踩过一次是经验,重复踩进去,那就是事故了。


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:利刃信安 利刃信安 利刃信安《那些年我们踩过的坑——摘要数据类型》

评论:0   参与:  0