简体   繁体   English

从 .NET Core 中的 PEM 文件创建 X509Certificate2

[英]Create X509Certificate2 from PEM file in .NET Core

I want to create a X509Certificate2 object based on a PEM file.我想基于 PEM 文件创建一个 X509Certificate2 对象。 The problem is setting the PrivateKey property of X509Certificate2.问题是设置 X509Certificate2 的 PrivateKey 属性。 I read X509Certificate2.CreateFromCertFile() on .NET Core and then used在 .NET Core 上阅读了X509Certificate2.CreateFromCertFile()然后使用

var rsa = new RSACryptoServiceProvider();

rsa.ImportCspBlob(pvk);

Where pvk is the byte array of the private key (read from GetBytesFromPEM as shown here how to get private key from PEM file? ), to set the private key, but then I get an其中pvk是私钥的字节数组(从 GetBytesFromPEM 读取,如下所示如何从 PEM 文件中获取私钥? ),以设置私钥,但随后我得到一个

Internal.Cryptography.CryptoThrowHelper+WindowsCryptographicException with message Bad Version of provider. Internal.Cryptography.CryptoThrowHelper+WindowsCryptographicException 带有消息提供程序的错误版本。

How can I properly set the PrivateKey of the X509Certificate2 based on the private key in the PEM file?如何根据PEM文件中的私钥正确设置X509Certificate2的PrivateKey?

If I look at Creating the X509Certificate2 , they use如果我查看创建 X509Certificate2 ,他们使用

 RSACryptoServiceProvider prov = Crypto.DecodeRsaPrivateKey(keyBuffer);
 certificate.PrivateKey = prov;

which seems like a neat way to do this, but this does not work in .Net Core...这似乎是一种巧妙的方法,但这在 .Net Core 中不起作用......

If you've just extracted the bytes from the Base64 encoding of the private key file you have a PKCS#1, PKCS#8, or encrypted PKCS#8 private key blob (depending on if it said "BEGIN RSA PRIVATE KEY", "BEGIN PRIVATE KEY" or "BEGIN ENCRYPTED PRIVATE KEY").如果您刚刚从私钥文件的 Base64 编码中提取字节,则您有一个 PKCS#1、PKCS#8 或加密的 PKCS#8 私钥 blob(取决于它是否显示“BEGIN RSA PRIVATE KEY”,“ BEGIN PRIVATE KEY”或“BEGIN ENCRYPTED PRIVATE KEY”)。 ImportCspBlob wants a custom format for the data, and that's why it's complaining. ImportCspBlob需要数据的自定义格式,这就是它抱怨的原因。

Digital signature in c# without using BouncyCastle has an explanation of ways forward.不使用 BouncyCastle 的 c# 中的数字签名有前进方向的解释。 The easiest / most formulaic is to just make a PFX with the cert and key, and let the X509Certificate2 constructor do its thing.最简单/最公式化的方法是使用证书和密钥制作 PFX,然后让X509Certificate2构造函数完成它的工作。

If you go the route of loading the key object directly then the way you would mate a private key with the certificate is to use one of the new CopyWithPrivateKey extension methods.如果您采用直接加载密钥对象的方式,那么您将私钥与证书配对的方法是使用一种新的CopyWithPrivateKey扩展方法。 This returns a new instance of X509Certificate2 which knows about the private key.这将返回一个新的X509Certificate2实例,它知道私钥。

The PrivateKey setter was "removed" from .NET Core because it has a lot of side effects on Windows that are hard to replicate on Linux and macOS, particularly if you retrieved the certificate out of an instance of X509Store. PrivateKey setter 已从 .NET Core 中“删除”,因为它对 Windows 有很多副作用,很难在 Linux 和 macOS 上复制,尤其是当您从 X509Store 实例中检索证书时。


This code is a combination of overly strict and overly accepting for real BER rules, but this should read validly encoded PKCS#8 files unless they included attributes.此代码是对真实 BER 规则过于严格和过度接受的组合,但这应该读取有效编码的 PKCS#8 文件,除非它们包含属性。

private static readonly byte[] s_derIntegerZero = { 0x02, 0x01, 0x00 };

private static readonly byte[] s_rsaAlgorithmId =
{
    0x30, 0x0D,
    0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01,
    0x05, 0x00,
};

private static int ReadLength(byte[] data, ref int offset)
{
    byte lengthOrLengthLength = data[offset++];

    if (lengthOrLengthLength < 0x80)
    {
        return lengthOrLengthLength;
    }

    int lengthLength = lengthOrLengthLength & 0x7F;
    int length = 0;

    for (int i = 0; i < lengthLength; i++)
    {
        if (length > ushort.MaxValue)
        {
            throw new InvalidOperationException("This seems way too big.");
        }

        length <<= 8;
        length |= data[offset++];
    }

    return length;
}

private static byte[] ReadUnsignedInteger(byte[] data, ref int offset, int targetSize = 0)
{
    if (data[offset++] != 0x02)
    {
        throw new InvalidOperationException("Invalid encoding");
    }

    int length = ReadLength(data, ref offset);

    // Encoding rules say 0 is encoded as the one byte value 0x00.
    // Since we expect unsigned, throw if the high bit is set.
    if (length < 1 || data[offset] >= 0x80)
    {
        throw new InvalidOperationException("Invalid encoding");
    }

    byte[] ret;

    if (length == 1)
    {
        ret = new byte[length];
        ret[0] = data[offset++];
        return ret;
    }

    if (data[offset] == 0)
    {
        offset++;
        length--;
    }

    if (targetSize != 0)
    {
        if (length > targetSize)
        {
            throw new InvalidOperationException("Bad key parameters");
        }

        ret = new byte[targetSize];
    }
    else
    {
        ret = new byte[length];
    }

    Buffer.BlockCopy(data, offset, ret, ret.Length - length, length);
    offset += length;
    return ret;
}

private static void EatFullPayloadTag(byte[] data, ref int offset, byte tagValue)
{
    if (data[offset++] != tagValue)
    {
        throw new InvalidOperationException("Invalid encoding");
    }

    int length = ReadLength(data, ref offset);

    if (data.Length - offset != length)
    {
        throw new InvalidOperationException("Data does not represent precisely one value");
    }
}

private static void EatMatch(byte[] data, ref int offset, byte[] toMatch)
{
    if (data.Length - offset > toMatch.Length)
    {
        if (data.Skip(offset).Take(toMatch.Length).SequenceEqual(toMatch))
        {
            offset += toMatch.Length;
            return;
        }
    }

    throw new InvalidOperationException("Bad data.");
}

private static RSA DecodeRSAPkcs8(byte[] pkcs8Bytes)
{
    int offset = 0;

    // PrivateKeyInfo SEQUENCE
    EatFullPayloadTag(pkcs8Bytes, ref offset, 0x30);
    // PKCS#8 PrivateKeyInfo.version == 0
    EatMatch(pkcs8Bytes, ref offset, s_derIntegerZero);
    // rsaEncryption AlgorithmIdentifier value
    EatMatch(pkcs8Bytes, ref offset, s_rsaAlgorithmId);
    // PrivateKeyInfo.privateKey OCTET STRING
    EatFullPayloadTag(pkcs8Bytes, ref offset, 0x04);
    // RSAPrivateKey SEQUENCE
    EatFullPayloadTag(pkcs8Bytes, ref offset, 0x30);
    // RSAPrivateKey.version == 0
    EatMatch(pkcs8Bytes, ref offset, s_derIntegerZero);

    RSAParameters rsaParameters = new RSAParameters();
    rsaParameters.Modulus = ReadUnsignedInteger(pkcs8Bytes, ref offset);
    rsaParameters.Exponent = ReadUnsignedInteger(pkcs8Bytes, ref offset);
    rsaParameters.D = ReadUnsignedInteger(pkcs8Bytes, ref offset, rsaParameters.Modulus.Length);
    int halfModulus = (rsaParameters.Modulus.Length + 1) / 2;
    rsaParameters.P = ReadUnsignedInteger(pkcs8Bytes, ref offset, halfModulus);
    rsaParameters.Q = ReadUnsignedInteger(pkcs8Bytes, ref offset, halfModulus);
    rsaParameters.DP = ReadUnsignedInteger(pkcs8Bytes, ref offset, halfModulus);
    rsaParameters.DQ = ReadUnsignedInteger(pkcs8Bytes, ref offset, halfModulus);
    rsaParameters.InverseQ = ReadUnsignedInteger(pkcs8Bytes, ref offset, halfModulus);

    if (offset != pkcs8Bytes.Length)
    {
        throw new InvalidOperationException("Something didn't add up");
    }

    RSA rsa = RSA.Create();
    rsa.ImportParameters(rsaParameters);
    return rsa;
}

Using .NET 5.0 we finally have a nice way of doing this.使用 .NET 5.0,我们终于有了一个很好的方法来做到这一点。

The X509Certificate2 class provides two static methods X509Certificate2.CreateFromPem and X509Certificate2.CreateFromPemFile . X509Certificate2 类提供了两个静态方法X509Certificate2.CreateFromPemX509Certificate2.CreateFromPemFile So if you have the file path then can call:因此,如果您有文件路径,则可以调用:

var cert = X509Certificate2.CreateFromPemFile(filePath);

If creating the certificate without the file then can pass in ReadOnlySpan<char> for the certificate thumbprint and key.如果在没有文件的情况下创建证书,则可以传入ReadOnlySpan<char>作为证书指纹和密钥。 There are also X509Certificate2.CreateFromEncryptedPem and X509Certificate2.CreateFromEncryptedPemFile if the contents is encrypted.如果内容是加密的,还有X509Certificate2.CreateFromEncryptedPemX509Certificate2.CreateFromEncryptedPemFile

More info can be found in the official API docs here: https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.x509certificates.x509certificate2.createfrompemfile?view=net-5.0更多信息可以在官方 API 文档中找到: https : //docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.x509certificates.x509certificate2.createfrompemfile? view = net- 5.0

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 如何以编程方式创建有效的自签名X509Certificate2,而不是从.NET Core中的文件加载 - How to create a valid, self-signed X509Certificate2 programmatically, not loading from file in .NET Core x509certificate2 到 .NET FR 中的 PEM。 4.7.2 - x509certificate2 to PEM in NET FR. 4.7.2 X509Certificate2 的 .pem 私钥 - .pem private key to X509Certificate2 .NET Core ChannelFactory-将X509Certificate2设置为客户端证书 - .NET Core ChannelFactory - Set a X509Certificate2 as Client Certificate 使用 ASP.NET Core 3.0 内置 API 从 .crt 和 .key 文件创建 X509Certificate2 - Create X509Certificate2 from .crt and .key files using ASP.NET Core 3.0 built-in API 如何在 .NET Standard 2.0/Core 2.1 中使用 PrivateKey 创建 X509Certificate2? - How to create X509Certificate2 with PrivateKey in .NET Standard 2.0/Core 2.1? 如何从.NET中的X509Certificate2中提取AuthorityKeyIdentifier - How to extract the AuthorityKeyIdentifier from a X509Certificate2 in .NET 从证书和密钥创建 X509Certificate2,无需制作 PFX 文件 - Create X509Certificate2 from Cert and Key, without making a PFX file .NET Core 3.1 与 Net Framework 4.7 中的 X509Certificate2 - X509Certificate2 in .NET Core 3.1 vs .Net Framework 4.7 将X509Certificate2证书与.NET中的私钥相关联 - Associating an X509Certificate2 certificate with a private key in .NET
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM