在 .Net Core 中使用 X509 证书对字节数组进行签名

Sign an array of bytes with X509 Certificate in .Net Core

我想用 X509Certificate2,

对字节数组进行签名

这是 .Net Framework 4.7 的示例,但我需要相同的 .Net Core 示例:

 var argCertFirmante = new X509Certificate2(file, pass);
 var infoContenido = new ContentInfo(argBytesMsg);
 var cmsFirmado = new SignedCms(infoContenido);
 var cmsFirmante = new CmsSigner(argCertFirmante)
  { IncludeOption = X509IncludeOption.EndCertOnly };
 cmsFirmado.ComputeSignature(cmsFirmante, true);
 return cmsFirmado.Encode();

我想要相当于这个的:

Cryptographic Message Syntax (CMS) 使用 Net Core 2.0 和这个 nuget 包我工作得很好:

<PackageReference Include="System.Security.Cryptography.Algorithms"
Version="4.4.0-beta-24913-01" /> <PackageReference
Include="System.Security.Cryptography.Pkcs"
Version="4.5.0-preview1-26119-06" /> <PackageReference
Include="System.Security.Cryptography.X509Certificates"
Version="4.4.0-beta-24913-01" />

根据 Apisof.Net,类 SignedCms and CmsSigner 都不会移植到 .Net Core。您可以在这里做两件事:

  • 等待 .Net Core 的 2.0 版本,这样您就可以使用 PrivateKey property,并使用 AsymmetricAlgorithm 对象对数据进行签名
  • 或者你可以使用extension method GetRSAPrivateKey(X509Certificate2):

    public byte[] Sign(string message)
    {
        using (var key = certificate.GetRSAPrivateKey())
        {
            return key.SignData(Encoding.UTF8.GetBytes(message),
              HashAlgorithmName.SHA256,
              RSASignaturePadding.Pkcs1);
        }
    }
    

HashAlgorithmName has static names for algorithms and RSASignaturePadding 具有用于填充的默认对象。

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 (though it builds a lot on ASN.1, and makes references to ASN.1, which is 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.cmsASN.1 Editor 等工具或其他此类 DER reader/rendering 工具将为您提供帮助。