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

C# 从.NET Core中的PEM文件创建X509Certificate2,c#,.net-core,x509certificate,private-key,pem,C#,.net Core,X509certificate,Private Key,Pem,我想基于PEM文件创建一个X509Certificate2对象。问题在于设置X509Certificate2的PrivateKey属性。我阅读然后使用 var rsa = new RSACryptoServiceProvider(); rsa.ImportCspBlob(pvk); 其中,pvk是私钥的字节数组(如图所示从GetBytesFromPEM读取),用于设置私钥,但随后我得到一个 内部.Cryptography.CryptothWhelper+WindowsCryptography

我想基于PEM文件创建一个X509Certificate2对象。问题在于设置X509Certificate2的PrivateKey属性。我阅读然后使用

var rsa = new RSACryptoServiceProvider();

rsa.ImportCspBlob(pvk);
其中,
pvk
是私钥的字节数组(如图所示从GetBytesFromPEM读取),用于设置私钥,但随后我得到一个

内部.Cryptography.CryptothWhelper+WindowsCryptography异常,消息提供程序版本错误

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

如果我看一看,他们使用

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

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

如果您刚刚从私钥文件的Base64编码中提取了字节,那么您就有了PKCS#1、PKCS#8或加密的PKCS#8私钥blob(取决于它是说“开始RSA私钥”、“开始私钥”还是“开始加密私钥”)
ImportCspBlob
需要数据的自定义格式,这就是它抱怨的原因

他解释了前进的道路。最简单/最公式化的方法是使用cert和key创建一个PFX,然后让
X509Certificate2
构造函数完成它的工作

如果直接加载密钥对象,那么将私钥与证书匹配的方法是使用一种新的扩展方法。这将返回一个新的
X509Certificate2
实例,该实例了解私钥

PrivateKey
setter已从.NET Core中“删除”,因为它在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;
}
private static readonly byte[]s_derIntegerZero={0x02,0x01,0x00};
专用静态只读字节[]SrsaAlgorithmId=
{
0x30,0x0D,
0x06、0x09、0x2A、0x86、0x48、0x86、0xF7、0x0D、0x01、0x01、0x01、,
0x05,0x00,
};
专用静态整型读取长度(字节[]数据,参考整型偏移量)
{
字节长度=数据[offset++];
如果(长度小于0x80)
{
返回长度或长度;
}
int lengthLength=lengthOrLengthLength&0x7F;
整数长度=0;
对于(int i=0;iushort.MaxValue)
{
抛出新的InvalidOperationException(“这似乎太大了。”);
}
长度(目标尺寸)
{
抛出新的InvalidOperationException(“坏键参数”);
}
ret=新字节[targetSize];
}
其他的
{
ret=新字节[长度];
}
块复制(数据、偏移量、返回、返回长度-长度、长度);
偏移量+=长度;
返回ret;
}
私有静态无效EatFullPayloadTag(字节[]数据,参考整数偏移量,字节tagValue)
{
如果(数据[offset++]!=tagValue)
{
抛出新的InvalidOperationException(“无效编码”);
}
int length=读取长度(数据,参考偏移量);
if(data.Length-offset!=长度)
{
抛出新的InvalidOperationException(“数据并不精确地表示一个值”);
}
}
专用静态匹配(字节[]数据,参考整数偏移,字节[]匹配)
{
if(data.Length-offset>toMatch.Length)
{
if(data.Skip(offset).Take(toMatch.Length).SequenceEqual(toMatch))
{
偏移量+=匹配长度;
返回;
}
}
抛出新的InvalidOperationException(“坏数据”);
}
专用静态RSA解码器RSAPKCS8(字节[]pkcs8Bytes)
{
整数偏移=0;
//PrivateKeyInfo序列
EatFullPayloadTag(pkcs8Bytes,参考偏移量,0x30);
//PKCS#8 PrivateKeyInfo.version==0
EatMatch(PKCS8字节,参考偏移量,s_derIntegerZero);
//RSA加密算法标识符值
EatMatch(PKCS8字节,参考偏移量,Srsu算法ID);
//PrivateKeyInfo.privateKey八位组字符串
EatFullPayloadTag(pkcs8Bytes,参考偏移量,0x04);
//RSA私钥序列
EatFullPayloadTag(pkcs8Bytes,参考偏移量,0x30);
//RSAPrivateKey.version==0
EatMatch(PKCS8字节,参考偏移量,s_derIntegerZero);
RSAPERAMETERS RSAPERAMETERS=新的RSAPERAMETERS();
rsapameters.modules=ReadUnsignedInteger(pkcs8Bytes,参考偏移量);
rsapameters.index=ReadUnsignedInteger(pkcs8Bytes,ref offset);
rsapameters.D=ReadUnsignedInteger(pkcs8Bytes,ref offset,rsapameters.modules.Length);
int半模=(rsapeters.modules.Length+1)/2;
rsapameters.P=ReadUnsignedInteger(pkcs8Bytes,ref偏移量,半模);
rsapameters.Q=ReadUnsignedInteger(pkcs8Bytes,ref偏移量,半模);
RSAPERAMETERS.DP=读取无符号整数(pkcs8字节,参考偏移量,半模);
RSAPERAMETERS.DQ=读取无符号整数(PKCS8字节,参考偏移量,半模);
RSAPERAMETERS.InverseQ=读取无符号整数(pkcs8字节,参考偏移量,半模);
if(偏移量!=pkcs8Bytes.Length)
{
抛出新的InvalidOperationException(“出现了一些错误”);
}
RSA=RSA.Create();
rsa.输入参数(rsa参数);
返回rsa;
}

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

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

var cert = X509Certificate2.CreateFromPemFile(filePath);
如果在没有文件的情况下创建证书,则可以为证书指纹和密钥传入
ReadOnlySpan
。如果内容是加密的,还有
X509Certificate2.CreateFromEncryptedPem
X509Certificate2.CreateFromEncryptedPemFile


更多信息可以在官方API文档中找到:

我之所以使用PEM格式,是因为证书是st