如何在不依赖外部库的前提下快速判断PKCS7二进制数据是已签名还是已加密?
如何在不依赖外部库的前提下快速判断PKCS7二进制数据是已签名还是已加密?
我太懂这种纠结了!用.NET自带的SignedCms和EnvelopedCms处理S/MIME数据时,必须提前搞清楚数据是签名还是加密的,一旦搞反就直接抛异常,又不想引入BouncyCastle这种重量级依赖——毕竟只是要做个简单判断,犯不上拉个大库进来。
其实咱们可以直接从PKCS7的二进制结构里抓关键标识,不用完整解析整个PKCS7包。PKCS7是基于ASN.1 DER编码的,每种类型都有对应的OID(对象标识符),咱们只需要提取开头的这个OID就能快速区分:
- 已签名的PKCS7数据(SignedData)对应的OID是
1.2.840.113549.1.7.2 - 加密的PKCS7数据(EnvelopedData)对应的OID是
1.2.840.113549.1.7.3
下面给你一段轻量的C#代码,直接读取二进制数据里的OID来判断类型,完全不用外部依赖:
using System; using System.Text; public enum Pkcs7Type { Unknown, SignedData, EnvelopedData } public static class Pkcs7Detector { private static readonly string SignedDataOid = "1.2.840.113549.1.7.2"; private static readonly string EnvelopedDataOid = "1.2.840.113549.1.7.3"; public static Pkcs7Type DetectPkcs7Type(byte[] data) { if (data == null || data.Length < 10) return Pkcs7Type.Unknown; int offset = 0; // 跳过PKCS7 ContentInfo的通用标签(0x30 = SEQUENCE) if (data[offset] != 0x30) return Pkcs7Type.Unknown; offset++; // 读取长度字段,处理短格式和长格式 int length = data[offset]; offset++; if (length > 0x80) { int lengthBytes = length & 0x7F; if (offset + lengthBytes > data.Length) return Pkcs7Type.Unknown; length = 0; for (int i = 0; i < lengthBytes; i++) { length = (length << 8) | data[offset]; offset++; } } // 读取OID的标签(0x06 = OBJECT IDENTIFIER) if (offset >= data.Length || data[offset] != 0x06) return Pkcs7Type.Unknown; offset++; // 读取OID的长度 int oidLength = data[offset]; offset++; if (offset + oidLength > data.Length) return Pkcs7Type.Unknown; // 解析OID字节为字符串 string oid = ParseOid(data, offset, oidLength); if (oid == SignedDataOid) return Pkcs7Type.SignedData; if (oid == EnvelopedDataOid) return Pkcs7Type.EnvelopedData; return Pkcs7Type.Unknown; } private static string ParseOid(byte[] data, int offset, int length) { StringBuilder sb = new StringBuilder(); long value = 0; // 处理第一个字节,特殊格式:(a * 40) + b byte firstByte = data[offset]; sb.Append(firstByte / 40); sb.Append("."); sb.Append(firstByte % 40); offset++; length--; for (int i = 0; i < length; i++) { byte b = data[offset + i]; value = (value << 7) | (b & 0x7F); if ((b & 0x80) == 0) { sb.Append("."); sb.Append(value); value = 0; } } return sb.ToString(); } }
使用的时候就很简单了:
byte[] pkcs7Data = ...; // 你的PKCS7二进制数据 Pkcs7Type type = Pkcs7Detector.DetectPkcs7Type(pkcs7Data); switch (type) { case Pkcs7Type.SignedData: // 用SignedCms处理 break; case Pkcs7Type.EnvelopedData: // 用EnvelopedCms处理 break; default: // 不是预期的PKCS7数据 break; }
这里还要提醒你几个点:
- 这段代码只解析了PKCS7最开头的OID部分,速度极快,哪怕是大文件也不会有性能问题
- 如果你怕自己写的解析有边界case,也可以用“试错法”:先尝试初始化
SignedCms,如果抛异常再试EnvelopedCms,但这种方法效率不如直接读OID,尤其是数据量很大的时候 - 要注意处理非PKCS7格式的情况,代码里已经做了基本的边界检查
备注:内容来源于stack exchange,提问作者Alex
相关产品推荐
相关产品推荐

