使用 C# 创建 PKCS#1 v2.1 签名

问题描述 投票:0回答:1

我正在尝试将使用 PKCS#12 证书对质询进行签名的参考实现从 Java 移植到 C#。据我了解参考资料,签名应该是 PKCS#1 v2.1 签名(几乎没有注释,也没有其他文档)。 Java 实现使用 Bouncy Castle,这对我来说是一个选择,不过如果我可以使用系统库解决这个问题那就更好了。

尽管尝试了很多方法,但我仍未能生成目标 API 接受的签名。

Java参考代码:

private byte[] signChallenge(final byte[] challenge) {
    X509Certificate sigCert = ...;
    PrivateKey sigKey = ...;

    ContentSigner signer = new JcaContentSignerBuilder("SHA256withRSAandMGF1").setProvider("BC").build(sigKey);
    CMSTypedData cmsData = new CMSProcessableByteArray(challenge);

    CMSSignedDataGenerator cmsGenerator = new CMSSignedDataGenerator();
    cmsGenerator.addSignerInfoGenerator(
        new JcaSignerInfoGeneratorBuilder(
            new JcaDigestCalculatorProviderBuilder().build()
        ).build(signer, sigCert)
    );
    Store certs = new JcaCertStore(Collections.singletonList(sigCert));
    cmsGenerator.addCertificates(certs);

    CMSSignedData cms = cmsGenerator.generate(cmsData, false);
    byte[] signedMessage = cms.getEncoded();
    return signedMessage;
}

首次尝试使用 Bouncy Castle 进行移植:

private byte[] SignChallenge(byte[] challenge)
{
    var sigCert = DotNetUtilities.FromX509Certificate(Cert);
    var sigKey = DotNetUtilities.GetRsaKeyPair(Cert.GetRSAPrivateKey()).Private;
    
    var signer = SignerUtilities.InitSigner("SHA256withRSAandMGF1", true, sigKey, new());
    var cmsData = new CmsProcessableByteArray(challenge);

    var cmsGenerator = new CmsSignedDataGenerator();
    var factory = new Asn1SignatureFactory("SHA256withRSAandMGF1", sigKey);
    cmsGenerator.AddSignerInfoGenerator(new SignerInfoGeneratorBuilder().Build(factory, sigCert));
    cmsGenerator.AddCertificate(sigCert);

    var cms = cmsGenerator.Generate(cmsData, false);
    var signedMessage = cms.GetEncoded();
    return signedMessage;
}

如您所见,除了两行使用 Jca 类(Java 特定的)之外,这主要是 1:1 的副本。这会生成一个前约 30 个字节匹配的签名,但除此之外它不再匹配,并且对于相同的输入具有不同的总长度(Java 中为 2324 字节,而 C# 中为 2231 字节)。

我的第二次尝试是尝试使用 System.Security.Cryptography.Pkcs:

private byte[] SignChallenge(byte[] challenge)
{
    var contentInfo = new System.Security.Cryptography.Pkcs.ContentInfo(challenge);

    var signedCms = new SignedCms(contentInfo, true);
    var signer = new CmsSigner(Cert);
    signer.DigestAlgorithm = Oid.FromFriendlyName("SHA256", OidGroup.HashAlgorithm);
    signer.SignaturePadding = RSASignaturePadding.Pss;

    signedCms.ComputeSignature(signer);
    var signedMessage = signedCms.Encode();
    return signedMessage;
}

这更简洁,但也不走运。

c# cryptography bouncycastle pkcs#12
1个回答
0
投票

我能够通过将签名写入文件并使用 OpenSSL 检查它们来解决这个问题:

openssl cms -in <signature file> -noout -cmsout -print -inform DER

这显示了一些差异,主要的一个是我生成的签名没有签名属性。参考签名具有 contentType (1.2.840.113549.1.9.3)、signingTime (1.2.840.113549.1.9.5)、messageDigest (1.2.840.113549.1.9.4) 和 CMSAlgorithmProtection (1.2.840.113549.1.9.52)。

前三个属性在 Bouncy Castle 和 System.Security.Cryptography.Pkcs 中都很容易添加,但 CMSAlgorithmProtection 需要特殊处理。在 Bouncy Castle 中,使用

DefaultSignedAttributeTableGenerator
时应该自动生成该属性,但是 相关代码当前已被注释掉。不过,我可以自己构建该属性:

var signatureAlgorithm = DefaultSignatureAlgorithmFinder.Instance.Find("SHA256withRSAandMGF1");
var algorithmProtection = new CmsAlgorithmProtection(new(new("2.16.840.1.101.3.4.2.1")), 1, signatureAlgorithm);
var attributeTable = new Org.BouncyCastle.Asn1.Cms.AttributeTable(new Dictionary<DerObjectIdentifier, object>
{
    [CmsAttributes.CmsAlgorithmProtect] = new Org.BouncyCastle.Asn1.Cms.Attribute(CmsAttributes.CmsAlgorithmProtect, new DerSet(algorithmProtection))
});

cmsGenerator.AddSignerInfoGenerator(
    new SignerInfoGeneratorBuilder()
        .WithSignedAttributeGenerator(new DefaultSignedAttributeTableGenerator(attributeTable))
        .Build(new Asn1SignatureFactory(algorithm, sigKey), sigCert));

这段代码最终产生了一个有效的签名。

我无法仅使用 System.Security.Cryptography.Pkcs 生成有效签名。我能够利用 Bouncy Castle

CmsAlgorithmProtection
类生成工作签名属性,但签名算法的编码 (CMS_ContentInfo -> d.signedData ->signerInfos ->signatureAlgorithm) 似乎略有不同,并且缺少一些 NULL 值。不过,Bouncy Castle 解决方案目前已经足够好了。

© www.soinside.com 2019 - 2024. All rights reserved.