在 .NET Core 中从 PEM 文件创建 X509Certificate2
Create X509Certificate2 from PEM file in .NET Core
我想创建一个基于 PEM 文件的 X509Certificate2 对象。问题是设置 X509Certificate2 的 PrivateKey 属性。我在 .NET Core 上阅读了 X509Certificate2.CreateFromCertFile()
然后用
var rsa = new RSACryptoServiceProvider();
rsa.ImportCspBlob(pvk);
其中 pvk
是私钥的字节数组(如此处所示从 GetBytesFromPEM 读取 how to get private key from PEM file?),用于设置私钥,但随后我得到一个
Internal.Cryptography.CryptoThrowHelper+WindowsCryptographicException with message Bad Version of provider.
如何根据PEM文件中的私钥正确设置X509Certificate2的PrivateKey?
如果我查看 Creating the X509Certificate2,他们会使用
RSACryptoServiceProvider prov = Crypto.DecodeRsaPrivateKey(keyBuffer);
certificate.PrivateKey = prov;
这似乎是一个很好的方法,但这在 .Net Core 中不起作用...
如果您刚刚从私钥文件的 Base64 编码中提取字节,您将拥有 PKCS#1、PKCS#8 或加密的 PKCS#8 私钥 blob(取决于它是否表示 "BEGIN RSA PRIVATE KEY"、"BEGIN PRIVATE KEY" 或 "BEGIN ENCRYPTED PRIVATE KEY")。 ImportCspBlob
想要数据的自定义格式,这就是它抱怨的原因。
Digital signature in c# without using BouncyCastle 对前进方向进行了解释。最简单/最公式化的是只使用证书和密钥制作 PFX,然后让 X509Certificate2
构造函数执行它的操作。
如果您采用直接加载密钥对象的方式,那么将私钥与证书配对的方法是使用一种新的 CopyWithPrivateKey
扩展方法。 returns 一个知道私钥的 X509Certificate2
的新实例。
PrivateKey
setter 来自 .NET Core "removed" 因为它对 Windows 有很多副作用,很难在 [=34= 上复制] 和 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;
}
我们终于有了使用 .NET 5.0 的好方法。
X509Certificate2 class 提供了两个静态方法 X509Certificate2.CreateFromPem
和 X509Certificate2.CreateFromPemFile
。因此,如果您有文件路径,则可以调用:
var cert = X509Certificate2.CreateFromPemFile(filePath);
如果在没有文件的情况下创建证书,则可以传入 ReadOnlySpan<char>
证书指纹和密钥。如果内容被加密,还有X509Certificate2.CreateFromEncryptedPem
和X509Certificate2.CreateFromEncryptedPemFile
。
可以在此处的官方 API 文档中找到更多信息:https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.x509certificates.x509certificate2.createfrompemfile?view=net-5.0
我想创建一个基于 PEM 文件的 X509Certificate2 对象。问题是设置 X509Certificate2 的 PrivateKey 属性。我在 .NET Core 上阅读了 X509Certificate2.CreateFromCertFile() 然后用
var rsa = new RSACryptoServiceProvider();
rsa.ImportCspBlob(pvk);
其中 pvk
是私钥的字节数组(如此处所示从 GetBytesFromPEM 读取 how to get private key from PEM file?),用于设置私钥,但随后我得到一个
Internal.Cryptography.CryptoThrowHelper+WindowsCryptographicException with message Bad Version of provider.
如何根据PEM文件中的私钥正确设置X509Certificate2的PrivateKey?
如果我查看 Creating the X509Certificate2,他们会使用
RSACryptoServiceProvider prov = Crypto.DecodeRsaPrivateKey(keyBuffer);
certificate.PrivateKey = prov;
这似乎是一个很好的方法,但这在 .Net Core 中不起作用...
如果您刚刚从私钥文件的 Base64 编码中提取字节,您将拥有 PKCS#1、PKCS#8 或加密的 PKCS#8 私钥 blob(取决于它是否表示 "BEGIN RSA PRIVATE KEY"、"BEGIN PRIVATE KEY" 或 "BEGIN ENCRYPTED PRIVATE KEY")。 ImportCspBlob
想要数据的自定义格式,这就是它抱怨的原因。
Digital signature in c# without using BouncyCastle 对前进方向进行了解释。最简单/最公式化的是只使用证书和密钥制作 PFX,然后让 X509Certificate2
构造函数执行它的操作。
如果您采用直接加载密钥对象的方式,那么将私钥与证书配对的方法是使用一种新的 CopyWithPrivateKey
扩展方法。 returns 一个知道私钥的 X509Certificate2
的新实例。
PrivateKey
setter 来自 .NET Core "removed" 因为它对 Windows 有很多副作用,很难在 [=34= 上复制] 和 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;
}
我们终于有了使用 .NET 5.0 的好方法。
X509Certificate2 class 提供了两个静态方法 X509Certificate2.CreateFromPem
和 X509Certificate2.CreateFromPemFile
。因此,如果您有文件路径,则可以调用:
var cert = X509Certificate2.CreateFromPemFile(filePath);
如果在没有文件的情况下创建证书,则可以传入 ReadOnlySpan<char>
证书指纹和密钥。如果内容被加密,还有X509Certificate2.CreateFromEncryptedPem
和X509Certificate2.CreateFromEncryptedPemFile
。
可以在此处的官方 API 文档中找到更多信息:https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.x509certificates.x509certificate2.createfrompemfile?view=net-5.0