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

hey*_*ydy 7 c# pem x509certificate .net-core private-key

我想基于PEM文件创建X509Certificate2对象。问题是设置X509Certificate2的PrivateKey属性。我在.NET Core上阅读X509Certificate2.CreateFromCertFile() ,然后使用

var rsa = new RSACryptoServiceProvider();

rsa.ImportCspBlob(pvk);
Run Code Online (Sandbox Code Playgroud)

pvk私钥的字节数组在哪里(从GetBytesFromPEM读取,如此处所示,如何从PEM文件获取私钥?)来设置私钥,但随后我得到了一个

Internal.Cryptography.CryptoThrowHelper + WindowsCryptographicException,带有消息提供程序错误版本。

如何根据PEM文件中的私钥正确设置X509Certificate2的私钥?

如果我看创建X509Certificate2,他们使用

 RSACryptoServiceProvider prov = Crypto.DecodeRsaPrivateKey(keyBuffer);
 certificate.PrivateKey = prov;
Run Code Online (Sandbox Code Playgroud)

这似乎是一种整洁的方法,但这在.Net Core中不起作用...

Tom*_*Dee 11

使用.NET 5.0,我们终于有了一个很好的方法来做到这一点。

X509Certificate2 类提供两个静态方法X509Certificate2.CreateFromPemX509Certificate2.CreateFromPemFile。因此,如果您有文件路径,则可以调用:

var cert = X509Certificate2.CreateFromPemFile(filePath);
Run Code Online (Sandbox Code Playgroud)

如果创建没有文件的证书,则可以传入ReadOnlySpan<char>证书指纹和密钥。还有X509Certificate2.CreateFromEncryptedPem如果X509Certificate2.CreateFromEncryptedPemFile内容被加密的话。

更多信息可以在官方 API 文档中找到:https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.x509certificates.x509certificate2.createfrompemfile ?view=net-5.0


bar*_*njs 5

如果您刚刚从私钥文件的 Base64 编码中提取字节,则您有一个 PKCS#1、PKCS#8 或加密的 PKCS#8 私钥 blob(取决于它是否显示“BEGIN RSA PRIVATE KEY”,“ BEGIN PRIVATE KEY”或“BEGIN ENCRYPTED PRIVATE KEY”)。 ImportCspBlob想要数据的自定义格式,这就是它抱怨的原因。

不使用 BouncyCastle 的 c# 中的数字签名有前进方向的解释。最简单/最公式化的方法是使用证书和密钥制作 PFX,然后让X509Certificate2构造函数完成它的工作。

如果您走直接加载密钥对象的路线,那么您将私钥与证书配对的方式是使用一种新的CopyWithPrivateKey扩展方法。这将返回一个X509Certificate2知道私钥的新实例。

PrivateKey二传手从.NET核心“删除”,因为它有很多的Windows上的副作用,是难以复制的Linux和MacOS下,特别是如果你获取的证书出来的X509Store的一个实例。


此代码是对真实 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;
}
Run Code Online (Sandbox Code Playgroud)

  • 我使用 PEM 格式的原因是证书在 Kubernetes 中作为机密存储。是否有可能以某种方式将证书作为字符串读取,将内容转换为 PFX 格式 - 然后将其用作 X509Certificate2 构造函数的输入? (2认同)
  • @heydy 显然我今天受到了启发,并制作了一个轻量级的 PKCS8 阅读器。享受。 (2认同)