SignedCms 在 .NET Core 1.0 或 1.1 中不可用;也不会出现在 2.0 中。 (编辑:它将在即将发布的 2.1 版本中提供)。
如果您只关心写入数据(这比读取数据容易得多),您可以仅使用 RSA.SignData 来实现它的有限形式。
SignedCms 生成一个 DER 编码的 CMS 签名数据值 (RFC 5652, section 5),即
SignedData ::= SEQUENCE {
version CMSVersion,
digestAlgorithms DigestAlgorithmIdentifiers,
encapContentInfo EncapsulatedContentInfo,
certificates [0] IMPLICIT CertificateSet OPTIONAL,
crls [1] IMPLICIT RevocationInfoChoices OPTIONAL,
signerInfos SignerInfos
}
DigestAlgorithmIdentifiers ::= SET OF DigestAlgorithmIdentifier
SignerInfos ::= SET OF SignerInfo
要编写一个,您需要了解如何根据可分辨编码规则 (DER) 编写数据,即 ITU-T X.690(尽管它在 ASN.1 上构建了很多内容,并引用了 ASN.1。 1,即ITU-T X.680)。
假设您想使用 SHA-2-256 / RSA+SHA-2-256 签署“Hello”。当然,我们在密码学中没有字符串,所以这是字节序列48 65 6C 6C 6F。
// SEQUENCE (SignedData)
30 xa [ya [za]]
// INTEGER (Version=1)
02 01 01
// SET (OF DigestAlgorithmIdentifier (digestAlgorithms))
31 xb [yb [zb]]
// SEQUENCE (DigestAlgorithmIdentifier ::= AlgorithmIdentifier)
30 xc [yc [zc]]
// OBJECT IDENTIFIER (2.16.840.1.101.3.4.2.1 == SHA-2-256)
06 09 60 86 48 01 65 03 04 02 01
// SEQUENCE (EncapsulatedContentInfo)
30 xd [yd [zd]]
// OBJECT IDENTIFIER (1.2.840.113549.1.7.1 == pkcs7-data)
06 09 2A 86 48 86 F7 0D 01 07 01
// CONTEXT SPECIFIC 0 - CONSTRUCTED
A0 xe [ye [ze]]
// OCTET STRING (the data goes here)
04 05 48 65 6C 6C 6F // "Hello"
// CONTEXT SPECIFIC 0 - CONSTRUCTED (CertificateSet (certificates))
A0 xf [yf [zf]]
[cert.RawData goes here, which is already DER encoded]
[do you have an intermediate you want to share?
okay, write intermediate.RawData here; repeat]
// skip the crls.
// SET (OF SignerInfo (singerInfos))
31 xg [yg [zg]]
// SEQUENCE (SignerInfo)
30 xh [yh [zh]]
// INTEGER (Version=1)
02 01 01
// SEQUENCE (IssuerAndSerialNumber)
30 xi [yi [zi]]
// SEQUENCE (Issuer)
[cert.IssuerName.RawData]
// OCTECT STRING (SerialNumber)
02 xj [yj [zj]]
[cert.GetSerialNumberBytes() (see note "j")]
// SEQUENCE (DigestAlgorithm (digestAlgorithm))
30 xk [yk [zk]]
// OBJECT IDENTIFIER (2.16.840.1.101.3.4.2.1 == SHA-2-256)
06 09 60 86 48 01 65 03 04 02 01
// skip signedAttrs
// SEQUENCE (DigestEncryptionAlgorithmIdentifier ::= AlgorithmIdentifier)
30 xl [yl [zl]]
// OBJECT IDENTIFIER (1.2.840.113549.1.1.1 == rsaEncryption)
06 09 2A 86 48 86 F7 0D 01 01 01
// NULL (rsaEncryption says parameters must be explicit NULL)
05 00
// OCTECT STRING (signature)
04 xm [ym [zm]]
[rsa.SignData(
new byte[] { 0x48, 0x65, 0x6C, 0x6C, 0x6F },
HashAlgorithmName.SHA256,
RSASignaturePadding.Pkcs1)]
// skip unsignedAttrs
现在我们完成了,我们可以关闭所有缺失的长度。签名大小是 RSA 密钥的函数。假设它是一个 2048 位的密钥,它生成一个 2048 位的签名,即 256 个字节。 256 是 0x100,比 0x7F 大,所以我们要把它分成两个长度字节和一个长度长度字节:所以“m”系列字节是80 01 00。
“l”系列是完整的,它包含 13 个字节,所以 0D(没有 y 或 z 字节)。
“k”在 11 个字节处完成 (0B)。
“j”取决于序列号的长度。我的证书有序列号9B 5D E6 C1 51 26 A5 8B,但你不应该把它写成负数(第一个字节设置了高位),所以它需要一个填充字节,使内容00 9B 5D E6 C1 51 26 A5 8B,因此长度 9 (09)。
“i”取决于发行者名称的长度。我的是一个 141 字节的数组(已经 DER 编码),加上我们的序列号(9 字节 + 标签 + 长度 == 11 字节)=> 152 字节(0x98)。由于 0x98 大于 0x7F,我们必须为其添加长度前缀:81 98。
现在“h”完成了。 (3 + (1 + 2 + 152) + (1 + 1 + 11) + (1 + 1 + 13) + (1 + 3 + 256) => 446 = 0x1BE => 82 01 BE.
“g”是 (1 + 3 + 446) => 450 = 0x1C2 82 01 C2。
"f" 是您编码的所有证书的总和。我的结果是 683 = 0x2AB (82 02 AB)
“e”是 7 (07)
“d”是 11 + (1 + 1 + 7) = 20 = 0x14 (14)
“c”是 11 (0B)
“b”是 (1 + 1 + 11) = 13 (0D)
“a”是 3 + (1 + 1 + 13) + (1 + 1 + 11) + (1 + 3 + 683) + (1 + 3 + 450) = 1172 = 0x494 (82 04 94)。
30 82 04 94 02 01 01 31 0D 30 0B 06 09 60 86 48
01 65 03 04 02 01 30 14 06 09 2A 86 48 86 F7 0D
01 07 01 A0 07 04 05 65 6C 6C 6F A0 82 02 AB ...
...cert.RawData...
31 82 01 C2 30 82 01 BE 02 01 01 30 81 98 ...
...cert.IssuerName.RawData...
02 09 00 9B 5D E6 C1 51 26 A5 8B 30 0B 06 09 60
86 48 01 65 03 04 02 01 30 0D 06 09 2A 86 48 86
F7 0D 01 01 01 05 00 04 80 01 00 ... signature ...
如果您走这条路,您将得到 openssl asn1parse -i -dump -inform DER < your.signed.cms 或 ASN.1 Editor 等工具或其他此类 DER 读取器/渲染工具的帮助。