问题
I read the .der file as follows.
byte[] byteKey = File.ReadAllBytes(openFileDialog1.FileName);
X509Certificate2 cert = new X509Certificate2(byteKey);
but it doesn't have private key. It has only public key.
cert.HasPrivateKey return false.
When I search it, I found that '.der file doesn't have private Key, private Key is in .key file'.
I use Notepad ++ to open a .key file in the same path as the .der file, the broken text will be printed.
first question, How to read private key from .key file on C#?
and second, How to convert .key file to .pem file on C#? Is it just using openssl?
I would appreciate your teaching.
回答1:
Current versions of .NET don't have a very good story here. .NET Core 3.0 has a better story. And if you're willing to use NuGet packages with "Experimental" in their name there's a differently better story.
Note: Throughout this answer I'm not going to consider third party libraries like BouncyCastle. It might do what you want perfectly, but it's not my area of expertise. I will consider NuGet packages developed by the same set of people as work on the inbox libraries for .NET, when I know of them.
Interpretation 1: "What single method do I call to load a private key from a file?"
Current versions
No solution.
.NET Core 3.0
There's not a single answer, you need to know what kind of file you have (or just try al the answers).
- RSA
- ImportRSAPrivateKey
- For when the data is in the PKCS#1 RSAPrivateKey format (PEM open header: BEGIN RSA PRIVATE KEY)
- ImportPkcs8PrivateKey
- For when the data is in the PKCS#8 PrivateKeyInfo format (PEM open header: BEGIN PRIVATE KEY)
- ImportEncryptedPkcs8PrivateKey
- For when the data is in the PKCS#8 EncryptedPrivateKeyInfo format (PEM open header: BEGIN ENCRYPTED PRIVATE KEY)
- ImportRSAPrivateKey
- ECDSA
- ImportECPrivateKey
- For when the data is in the RFC 5915 ECPrivateKey format (PEM open header: BEGIN EC PRIVATE KEY)
- ImportPkcs8PrivateKey
- ImportEncryptedPkcs8PrivateKey
- ImportECPrivateKey
- ECDiffieHellman
- ImportECPrivateKey
- ImportPkcs8PrivateKey
- ImportEncryptedPkcs8PrivateKey
- DSA
- ImportPkcs8PrivateKey
- ImportEncryptedPkcs8PrivateKey
The caveat to these methods is that they only understand BER/DER data, not PEM data. So if your file is in the PEM format (which makes it easiest to identify what the payload is supposed to be) you first need to convert it to BER/DER.
For most PEM files that's easy: you just find the contents between the BEGIN and END markers, run it through Convert.FromBase64String, and voila. Technically PEM supports attributes, and dealing with those is harder (and beyond the scope of this answer).
So, you might end up with something like
RSA rsa = RSA.Create();
try
{
rsa.ImportRSAPrivateKey(data, out _);
return rsa;
}
catch (CryptographicException)
{
}
try
{
rsa.ImportPkcs8PrivateKey(data, out _);
return rsa;
}
catch (CryptographicException)
{
}
try
{
// prompt for password, then
rsa.ImportEncryptedPkcs8PrivateKey(password, data, out _);
return rsa;
}
catch (CryptographicException)
{
}
rsa.Dispose();
ECDsa ecdsa = ECDsa.Create();
...
The ignored out
values are the number of bytes that were used from the input bytes. It's mainly only relevant when reading from the middle of a file.
System.Security.Cryptography.Asn1.Experimental
No solution, this library is much lower level than that.
Interpretation 2: "How do I understand these files in practice?"
Okay, this isn't really a way the question was interpreted, but a segue.
Cryptographic key files are always (in my experience) DER- (though occasionally relaxed to BER-) encoded ASN.1 data structures. To fully understand them you would need to read and understand
- ITU-T REC X.680: The ASN.1 language
- ITU-T REC X.690 The Basic Encoding Rules (BER) byte layout for ASN.1 data (and also the rarely-used restrictions Canonical Encoding Rules (CER) and commonly-used restrictions Distinguished Encoding Rules (DER)).
- Whatever thing describes the particular formats, and possibly things they reference.
- RSAPrivateKey: Public Key Cryptography Standards #1 (PKCS#1) or RFC 8017
- ECPrivateKey: RFC 5915
- PKCS#8 PrivateKeyInfo: PKCS#8 / RFC 5208
- PKCS#8 EncryptedPrivateKeyInfo: PKCS#8 / RFC 5208 (and PKCS#5, at least, as dependencies)
These structures are then sometimes converted to a textual representation using the Privacy Enhanced Mail (PEM) syntax, which is essentially
- 5 hyphen-minus characters
- All-caps
BEGIN
followed by a space - A format identifier, not ending in a space
- 5 hyphen-minus characters
- A newline (CRLF or LF)
- The base64-encoded version of the BER/DER data, wrapped at 64 characters per line
- A newline (CRLF or LF) at the end of the last part of the base64 data
- 5 hyphen-minus characters
- All-caps
END
followed by a space - The same format identifier used in BEGIN
- 5 hyphen-minus characters
- (ideally a newline or just end-of-file)
See RFC 7468 for more information.
Interpretation 3: "How do I read the parts of these files in code?"
Current versions
No solution.
.NET Core 3.0
No solution.
System.Security.Cryptography.Asn1.Experimental
This NuGet package is the ASN.1 reader from .NET Core 2.1 / 3.0 made public (with the idea that it becomes public from .NET Core after some usability feedback).
To read an RSAPrivateKey, for example:
// PKCS#1 doesn't say that this structure is always DER encoded, so read it as BER
AsnReader reader = new AsnReader(data, AsnEncodingRules.BER);
// RSAPrivateKey ::= SEQUENCE {
AsnReader contents = reader.ReadSequence();
// version Version (0 for two-prime RSA)
if (!contents.TryReadInt32(out int version) || version != 0)
{
throw new CryptographicException();
}
// modulus INTEGER,
BigInteger modulus = contents.ReadInteger();
// publicExponent INTEGER,
BigInteger publicExponent = contents.ReadInteger();
// privateExponent INTEGER,
BigInteger privateExponent = contents.ReadInteger();
// prime1 INTEGER,
BigInteger prime1 = contents.ReadInteger();
// prime2 INTEGER,
BigInteger prime2 = contents.ReadInteger();
// exponent1 INTEGER,
BigInteger exponent1 = contents.ReadInteger();
// exponent2 INTEGER,
BigInteger exponent2 = contents.ReadInteger();
// coefficient INTEGER,
BigInteger coefficient = contents.ReadInteger();
// otherPrimeInfos OtherPrimeInfos OPTIONAL,
// we don't support this, we limited to version 0.
// good thing the next token is:
// }
contents.ThrowIfNotEmpty();
// All done.
// If you expected no trailing data:
reader.ThrowIfNotEmpty();
Similarly for other formats.
回答2:
I have solved this before but i only can answer you half.You should use openssl to open .key and this is code (notice:this code is from this codeproject post):
using System;
using System.IO;
using System.Text;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Runtime.InteropServices;
using System.Security;
using System.Diagnostics;
using System.ComponentModel;
namespace OpenSSL
{
public class opensslkey
{
public string SignString(string pKeyFile,string pPassword,string OriginalString)
{
string SignedString = "";
string filename = pKeyFile;
if (!File.Exists(filename))
{
return ".key file does not exist " + pKeyFile;
}
RSACryptoServiceProvider rsa=OpenKeyFile(filename, pPassword);
if (rsa != null)
{
byte[] CO=Encoding.UTF8.GetBytes(OriginalString);
byte[] SignedBytes=rsa.SignData(CO, new SHA1CryptoServiceProvider());
SignedString = Convert.ToBase64String(SignedBytes);
}
return SignedString;
}
public RSACryptoServiceProvider OpenKeyFile(String filename,string pPassword)
{
RSACryptoServiceProvider rsa = null;
byte[] keyblob = GetFileBytes(filename);
if (keyblob == null)
return null;
rsa = DecodePrivateKeyInfo(keyblob, pPassword); //PKCS #8 encrypted
if (rsa != null)
{
return rsa;
}
return null;
}
public static RSACryptoServiceProvider
DecodePrivateKeyInfo(byte[] encpkcs8,string pPassword)
{
// encoded OID sequence for PKCS #1 rsaEncryption szOID_RSA_RSA ="1.2.840.113549.1.1.1"
// this byte[] includes the sequence byte and terminal encoded null
byte[] OIDpkcs5PBES2 = { 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x05, 0x0D };
byte[] OIDpkcs5PBKDF2 = { 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x05, 0x0C };
byte[] OIDdesEDE3CBC = { 0x06, 0x08, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x03, 0x07 };
byte[] seqdes = new byte[10];
byte[] seq = new byte[11];
byte[] salt;
byte[] IV;
byte[] encryptedpkcs8;
byte[] pkcs8;
int saltsize, ivsize, encblobsize;
int iterations;
// --------- Set up stream to read the asn.1 encoded SubjectPublicKeyInfo blob ------
MemoryStream mem = new MemoryStream(encpkcs8);
int lenstream = (int)mem.Length;
BinaryReader binr = new BinaryReader(mem); //wrap Memory Stream with BinaryReader for easy reading
byte bt = 0;
ushort twobytes = 0;
try
{
twobytes = binr.ReadUInt16();
if (twobytes == 0x8130)
//data read as little endian order (actual data order for Sequence is 30 81)
binr.ReadByte(); //advance 1 byte
else if (twobytes == 0x8230)
binr.ReadInt16(); //advance 2 bytes
else
return null;
twobytes = binr.ReadUInt16(); //inner sequence
if (twobytes == 0x8130)
binr.ReadByte();
else if (twobytes == 0x8230)
binr.ReadInt16();
seq = binr.ReadBytes(11); //read the Sequence OID
if (!CompareBytearrays(seq, OIDpkcs5PBES2)) //is it a OIDpkcs5PBES2 ?
return null;
twobytes = binr.ReadUInt16(); //inner sequence for pswd salt
if (twobytes == 0x8130)
binr.ReadByte();
else if (twobytes == 0x8230)
binr.ReadInt16();
twobytes = binr.ReadUInt16(); //inner sequence for pswd salt
if (twobytes == 0x8130)
binr.ReadByte();
else if (twobytes == 0x8230)
binr.ReadInt16();
seq = binr.ReadBytes(11); //read the Sequence OID
if (!CompareBytearrays(seq, OIDpkcs5PBKDF2)) //is it a OIDpkcs5PBKDF2 ?
return null;
twobytes = binr.ReadUInt16();
if (twobytes == 0x8130)
binr.ReadByte();
else if (twobytes == 0x8230)
binr.ReadInt16();
bt = binr.ReadByte();
if (bt != 0x04) //expect octet string for salt
return null;
saltsize = binr.ReadByte();
salt = binr.ReadBytes(saltsize);
bt = binr.ReadByte();
if (bt != 0x02) //expect an integer for PBKF2 interation count
return null;
int itbytes = binr.ReadByte(); //PBKD2 iterations should fit in 2 bytes.
if (itbytes == 1)
iterations = binr.ReadByte();
else if (itbytes == 2)
iterations = 256 * binr.ReadByte() + binr.ReadByte();
else
return null;
twobytes = binr.ReadUInt16();
if (twobytes == 0x8130)
binr.ReadByte();
else if (twobytes == 0x8230)
binr.ReadInt16();
seqdes = binr.ReadBytes(10); //read the Sequence OID
if (!CompareBytearrays(seqdes, OIDdesEDE3CBC)) //is it a OIDdes-EDE3-CBC ?
return null;
bt = binr.ReadByte();
if (bt != 0x04) //expect octet string for IV
return null;
ivsize = binr.ReadByte(); // IV byte size should fit in one byte (24 expected for 3DES)
IV = binr.ReadBytes(ivsize);
bt = binr.ReadByte();
if (bt != 0x04) // expect octet string for encrypted PKCS8 data
return null;
bt = binr.ReadByte();
if (bt == 0x81)
encblobsize = binr.ReadByte(); // data size in next byte
else if (bt == 0x82)
encblobsize = 256 * binr.ReadByte() + binr.ReadByte();
else
encblobsize = bt; // we already have the data size
encryptedpkcs8 = binr.ReadBytes(encblobsize);
SecureString secpswd = new SecureString();
foreach (char c in pPassword)
secpswd.AppendChar(c);
pkcs8 = DecryptPBDK2(encryptedpkcs8, salt, IV, secpswd, iterations);
if (pkcs8 == null) // probably a bad pswd entered.
return null;
RSACryptoServiceProvider rsa = DecodePrivateKeyInfo(pkcs8);
return rsa;
}
catch (Exception)
{
return null;
}
finally { binr.Close(); }
}
public void CertificateData(string pCerFile, out string Certificate, out string CertificateNumber)
{
X509Certificate cert = new X509Certificate(pCerFile);
byte[] strcert = cert.GetRawCertData();
Certificate = Convert.ToBase64String(strcert);
strcert = cert.GetSerialNumber();
CertificateNumber = Reverse(System.Text.Encoding.UTF8.GetString(strcert));
}
public string Reverse(string Original)
{
string Reverse = "";
for (int i = Original.Length - 1; i >= 0; i--)
Reverse += Original.Substring(i, 1);
return Reverse;
}
private static byte[] GetFileBytes(String filename)
{
if (!File.Exists(filename))
return null;
Stream stream = new FileStream(filename, FileMode.Open);
int datalen = (int)stream.Length;
byte[] filebytes = new byte[datalen];
stream.Seek(0, SeekOrigin.Begin);
stream.Read(filebytes, 0, datalen);
stream.Close();
return filebytes;
}
private static bool CompareBytearrays(byte[] a, byte[] b)
{
if (a.Length != b.Length)
return false;
int i = 0;
foreach (byte c in a)
{
if (c != b[i])
return false;
i++;
}
return true;
}
public static byte[] DecryptPBDK2(byte[] edata, byte[] salt,
byte[] IV, SecureString secpswd, int iterations)
{
CryptoStream decrypt = null;
IntPtr unmanagedPswd = IntPtr.Zero;
byte[] psbytes = new byte[secpswd.Length];
unmanagedPswd = Marshal.SecureStringToGlobalAllocAnsi(secpswd);
Marshal.Copy(unmanagedPswd, psbytes, 0, psbytes.Length);
Marshal.ZeroFreeGlobalAllocAnsi(unmanagedPswd);
try
{
Rfc2898DeriveBytes kd = new Rfc2898DeriveBytes(psbytes, salt, iterations);
TripleDES decAlg = TripleDES.Create();
decAlg.Key = kd.GetBytes(24);
decAlg.IV = IV;
MemoryStream memstr = new MemoryStream();
decrypt = new CryptoStream(memstr, decAlg.CreateDecryptor(), CryptoStreamMode.Write);
decrypt.Write(edata, 0, edata.Length);
decrypt.Flush();
decrypt.Close(); // this is REQUIRED.
byte[] cleartext = memstr.ToArray();
return cleartext;
}
catch (Exception e)
{
Console.WriteLine("Problem decrypting: {0}", e.Message);
return null;
}
}
public static RSACryptoServiceProvider DecodePrivateKeyInfo(byte[] pkcs8)
{
// encoded OID sequence for PKCS #1 rsaEncryption szOID_RSA_RSA = "1.2.840.113549.1.1.1"
// this byte[] includes the sequence byte and terminal encoded null
byte[] SeqOID = { 0x30, 0x0D, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01, 0x05, 0x00 };
byte[] seq = new byte[15];
// --------- Set up stream to read the asn.1 encoded SubjectPublicKeyInfo blob ------
MemoryStream mem = new MemoryStream(pkcs8);
int lenstream = (int)mem.Length;
BinaryReader binr = new BinaryReader(mem); //wrap Memory Stream with BinaryReader for easy reading
byte bt = 0;
ushort twobytes = 0;
try
{
twobytes = binr.ReadUInt16();
if (twobytes == 0x8130) //data read as little endian order (actual data order for Sequence is 30 81)
binr.ReadByte(); //advance 1 byte
else if (twobytes == 0x8230)
binr.ReadInt16(); //advance 2 bytes
else
return null;
bt = binr.ReadByte();
if (bt != 0x02)
return null;
twobytes = binr.ReadUInt16();
if (twobytes != 0x0001)
return null;
seq = binr.ReadBytes(15); //read the Sequence OID
if (!CompareBytearrays(seq, SeqOID)) //make sure Sequence for OID is correct
return null;
bt = binr.ReadByte();
if (bt != 0x04) //expect an Octet string
return null;
bt = binr.ReadByte(); //read next byte, or next 2 bytes is 0x81 or 0x82; otherwise bt is the byte count
if (bt == 0x81)
binr.ReadByte();
else
if (bt == 0x82)
binr.ReadUInt16();
//------ at this stage, the remaining sequence should be the RSA private key
byte[] rsaprivkey = binr.ReadBytes((int)(lenstream - mem.Position));
RSACryptoServiceProvider rsacsp = DecodeRSAPrivateKey(rsaprivkey);
return rsacsp;
}
catch (Exception)
{
return null;
}
finally { binr.Close(); }
}
public static RSACryptoServiceProvider DecodeRSAPrivateKey(byte[] privkey)
{
byte[] MODULUS, E, D, P, Q, DP, DQ, IQ;
// --------- Set up stream to decode the asn.1 encoded RSA private key ------
MemoryStream mem = new MemoryStream(privkey);
BinaryReader binr = new BinaryReader(mem); //wrap Memory Stream with BinaryReader for easy reading
byte bt = 0;
ushort twobytes = 0;
int elems = 0;
try
{
twobytes = binr.ReadUInt16();
if (twobytes == 0x8130) //data read as little endian order (actual data order for Sequence is 30 81)
binr.ReadByte(); //advance 1 byte
else if (twobytes == 0x8230)
binr.ReadInt16(); //advance 2 bytes
else
return null;
twobytes = binr.ReadUInt16();
if (twobytes != 0x0102) //version number
return null;
bt = binr.ReadByte();
if (bt != 0x00)
return null;
//------ all private key components are Integer sequences ----
elems = GetIntegerSize(binr);
MODULUS = binr.ReadBytes(elems);
elems = GetIntegerSize(binr);
E = binr.ReadBytes(elems);
elems = GetIntegerSize(binr);
D = binr.ReadBytes(elems);
elems = GetIntegerSize(binr);
P = binr.ReadBytes(elems);
elems = GetIntegerSize(binr);
Q = binr.ReadBytes(elems);
elems = GetIntegerSize(binr);
DP = binr.ReadBytes(elems);
elems = GetIntegerSize(binr);
DQ = binr.ReadBytes(elems);
elems = GetIntegerSize(binr);
IQ = binr.ReadBytes(elems);
Console.WriteLine("showing components ..");
// ------- create RSACryptoServiceProvider instance and initialize with public key -----
RSACryptoServiceProvider RSA = new RSACryptoServiceProvider();
RSAParameters RSAparams = new RSAParameters();
RSAparams.Modulus = MODULUS;
RSAparams.Exponent = E;
RSAparams.D = D;
RSAparams.P = P;
RSAparams.Q = Q;
RSAparams.DP = DP;
RSAparams.DQ = DQ;
RSAparams.InverseQ = IQ;
RSA.ImportParameters(RSAparams);
return RSA;
}
catch (Exception)
{
return null;
}
finally { binr.Close(); }
}
private static int GetIntegerSize(BinaryReader binr)
{
byte bt = 0;
byte lowbyte = 0x00;
byte highbyte = 0x00;
int count = 0;
bt = binr.ReadByte();
if (bt != 0x02) //expect integer
return 0;
bt = binr.ReadByte();
if (bt == 0x81)
count = binr.ReadByte(); // data size in next byte
else
if (bt == 0x82)
{
highbyte = binr.ReadByte(); // data size in next 2 bytes
lowbyte = binr.ReadByte();
byte[] modint = { lowbyte, highbyte, 0x00, 0x00 };
count = BitConverter.ToInt32(modint, 0);
}
else
{
count = bt; // we already have the data size
}
while (binr.ReadByte() == 0x00)
{ //remove high order zeros in data
count -= 1;
}
binr.BaseStream.Seek(-1, SeekOrigin.Current);
//last ReadByte wasn't a removed zero, so back up a byte
return count;
}
}
}
And this is a sample:
//using code
public void Sample()
{
OpenSSL.opensslkey libssl = new OpenSSL.opensslkey();
string SignedString = libssl.SignString(@"c:\test\aaaa121213123123aaa_t.key",
"0123456789", "||3.2|test|cadena|original|");
string Certificate = "";
string CertificateNumber = "";
libssl.CertificateData(@"c:\test\aaaa121213123123aaa_t.cer", out Certificate, out CertificateNumber);
来源:https://stackoverflow.com/questions/55962625/how-to-read-key-file-on-c