我正在尝试使用 ECDSA 生成带有私钥的(自签名)证书。
目标是获得与使用 openssl 时“相同”(pkcs12)的证书:

openssl ecparam -genkey -name secp256r1 -out mykey.key
openssl req -new -key mykey.key -out myreq.csr
openssl req -x509 -days 7 -key mykey.key -in myreq.csr -out mycert.crt
openssl pkcs12 -export -out mycert.pfx -inkey mykey.key -in mycert.crt

我已经使用 BouncyCaSTLe 来帮助我创建基于 RSA 的证书,所以接下来的步骤或多或少遵循我用来创建 RSA 证书的方式。

(注意 BC 前缀用于 BouncyCaSTLe 的类,MS 用于 .NET 类)

1 生成 key 对:私钥和公钥
BC.IAsymmetricCipherKeyPairGenerator bcKpGen = BC.GeneratorUtilities.GetKeyPairGenerator("ECDSA");
bcKpGen.Init(new BC.ECKeyGenerationParameters(BC.SecObjectIdentifiers.SecP256r1, new BC.SecureRandom()));
BC.AsymmetricCipherKeyPair bcSubjKeys = bcKpGen.GenerateKeyPair();

2 使用私钥对带有一些附加数据(主题、有效期等)的公钥进行签名
BC.X509V3CertificateGenerator bcXgen = new BC.X509V3CertificateGenerator();
// .. set subject, validity period etc
bcXgen.SetPublicKey(bcSubjKeys.Public);
BC.ISignatureFactory bcSigFac = new BC.Asn1SignatureFactory("SHA256WITHECDSA", bcSubjKeys.Private);
BC.X509Certificate bcCert = bcXgen.Generate(bcSigFac);

3 “加入”步骤 1 中的私钥和步骤 2 中的证书以获取带有私钥的证书。

如果我可以使用没有私钥的证书,我可以执行以下操作:
MS.X509Certificate mcCert = new MS.X509Certificate2(bcCert.GetEncoded(), null);

我已经完成了。

尝试设置私钥时出现问题:
msCert.PrivateKey = ConvertBouncyToNetSomehow(bcSubjKeys.Private)

(注意 typeof msCert.PrivateKeyMS.AsymmetricAlgorithmbcSubjKeys.Private 的类型是 BC.ECPrivateKeyParameters )

似乎合适的方法是使用 MS.ECDsaCng 类(它继承自 MS.AsymmetricAlgorithm ),但是:

1 我发现将 BC.ECPrivateKeyParameters 转换为 MS.CngKey(MS.ECDsaCng 需要)的唯一方法是通过 pkcs8 格式:
BC.PrivateKeyInfo bcPKInfo = BC.PrivateKeyInfoFactory.CreatePrivateKeyInfo(bcSubjKeys.Private);
byte[] pkArr = bcPKInfo.GetDerEncoded();
MS.CngKey msPKCng = MS.CngKey.Import(pkArr, MS.CngKeyBlobFormat.Pkcs8PrivateBlob);

但是使用这种方法会丢失一些信息,因为 msPKCng.AlgorithmGroup 的值是 "ECDH"bcSubjKeys.Private.AlgorithmName 表示 "ECDSA" 。此外,ECDH-key 不能与 MS.ECDsaCng 一起使用。

尽管如此..如果..我可以继续使用MS.ECDiffieHellmanCng而不是请求的MS.ECDsaCng
2 MS.X509Certificate2.set_PrivateKey 的实现需要对象实现接口(interface) MS.ICspAsymmetricAlgorithm 。但是它们中的任何一个( ECDsaCngECDiffieHellmanCng )都没有实现它。

在这一点上,似乎必须使用不同的方法(因为 MS.ICspAsymmetricAlgorithm 条件),例如将证书和私钥导出到 pkcs 文件并使用 X509Certificate2.Import(..)

任何提示?
问候

最佳答案

不幸的是,现在不可能直接开箱即用。您可以使用 P/Invokes 和 .NET 4.6.2(当前处于预览状态)获得其余的方法。或者,绕过 .NET Core,您可以构建一个适用于 .NET 4.6.1 的 PFX。
“ECDSA”与“ECDH”
Windows CNG 库将 ECC 拆分为 ECDSA 和 ECDH。 ECDSA key 对象只能用于 ECDSA;但是每当 Windows 在 PFX 导入(或 PKCS#8 导入)期间无法确定使用情况时,它就会调用私钥 ECDH。为什么?由于 Windows 允许 ECDH key 对象同时进行 key 协商 (ECDH) 和数字签名 (ECDSA),因此 ECDH 更加灵活。
但是 .NET 4.6.1 不知道这一点。
.NET Core 没有此限制(请参阅 https://github.com/dotnet/corefx/pull/5850 ),并且 .NET 4.6.2 也取消了此限制(根据 https://github.com/Microsoft/dotnet/blob/master/releases/net462/dotnet462-changes.md#user-content-bcl )。
生成“ECDSA” key ,而不是“ECDH”
.NET Core 现在在 ECDsa 上有一个 ImportParameters 方法。如果可以将 BC.ECPrivateKeyProperty 对象转换为 MS.ECParameters 结构,则可以将 blob 导入 ECDsaCng 对象。 (确保将其用作命名曲线,而不是显式复制所有曲线参数)。
由于它被有意导入到 ECDsa 对象中,因此它会获得一个 ECDSA key ,并且该信息将嵌入到 PFX 中。
构建 PFX(将它们捆绑在一起)
通过一些 P/Invoking,您可以说服 Windows 使用临时 key 构建 PFX。虽然 .NET 无法从证书访问临时私钥,但如果从 PFX 加载,它将能够使用它:

[DllImport(Libraries.Crypt32, CharSet = CharSet.Unicode, SetLastError = true)]
private static extern unsafe bool CertSetCertificateContextProperty(IntPtr pCertContext, CertContextPropId dwPropId, CertSetPropertyFlags dwFlags, SafeNCryptKeyHandle pvData);

internal enum CertContextPropId : int
{
    CERT_NCRYPT_KEY_HANDLE_PROP_ID = 78,
}

[Flags]
internal enum CertSetPropertyFlags : int
{
    None = 0,
}

private static X509Certificate2 MateECDsaPrivateKey(
    X509Certificate2 cert,
    CngKey privateKey)
{
    // Make a new certificate instance which isn't tied to the current one
    using (var tmpCert = new X509Certificate2(cert.RawData))
    {
        SafeNCryptKeyHandle keyHandle = privateKey.Handle;

        // Set the ephemeral key handle property
        if (!CertSetCertificateContextProperty(
            tmpCert.Handle,
            CertContextPropId.CERT_NCRYPT_KEY_HANDLE_PROP_ID,
            CertSetPropertyFlags.None,
            keyHandle))
        {
            throw new CryptographicException(Marshal.GetLastWin32Error());
        }

        // You could emit this, if you prefer.
        byte[] pfxBytes = tmpCert.Export(X509ContentType.Pkcs12);

        // Clear the key handle out again to prevent double-free
        keyHandle = new SafeNCryptKeyHandle();

        if (!CertSetCertificateContextProperty(
            tmpCert.Handle,
            CertContextPropId.CERT_NCRYPT_KEY_HANDLE_PROP_ID,
            CertSetPropertyFlags.None,
            keyHandle))
        {
            throw new CryptographicException(Marshal.GetLastWin32Error());
        }

        // Now load a new certificate which has a temporary keyfile on disk.
        // Note: If you don't want exportability here, don't request it.
        var matedCert = new X509Certificate2(pfxBytes, (string)null, X509KeyStorageFlags.Exportable);

        using (ECDsa ecdsa = matedCert.GetECDsaPrivateKey())
        {
            if (ecdsa == null)
            {
                throw new InvalidOperationException("It didn't work");
            }
        }

        return matedCert;
    }
}
您需要 .NET 4.6.1(或更新版本)才能访问 GetECDsaPrivateKey()。

关于c# - 在 C# 中使用 ECDSA 生成证书,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/36624105/

10-10 03:09