【问题标题】:How To Read .Key file on C#?如何在 C# 上读取 .Key 文件?
【发布时间】:2019-05-03 02:39:12
【问题描述】:

我读取.der文件如下。

byte[] byteKey = File.ReadAllBytes(openFileDialog1.FileName);
X509Certificate2 cert = new X509Certificate2(byteKey);

但它没有私钥。它只有公钥。

cert.HasPrivateKey 返回 false。

当我搜索它时,我发现'.der文件没有私钥,私钥在.key文件中'。

我用记事本++打开了一个和.der文件同路径的.key文件,会打印断掉的文字。

第一个问题,如何在 C# 上从 .key 文件中读取私钥?

第二,如何在 C# 上将 .key 文件转换为 .pem 文件? 只是用openssl吗?

感谢您的教导。

【问题讨论】:

  • 是什么让您认为.key 文件是证书。请创建文件的人给你一个格式。很可能是带有密码的 PKCS #8。

标签: c# file pem file-read der


【解决方案1】:

.NET 的当前版本在这里没有很好的故事。 .NET Core 3.0 有一个更好的故事。如果您愿意使用名称中带有“Experimental”的 NuGet 包,那么还有一个更好的故事。

注意:在整个答案中,我不会考虑像 BouncyCastle 这样的第三方库。它可能会完美地满足您的需求,但这不是我的专业领域。当我知道 NuGet 包时,我会考虑由同一组人开发的 .NET 收件箱库。

解释 1:“我调用什么单一方法来从文件中加载私钥?”

当前版本

没有办法。

.NET Core 3.0

没有一个单一的答案,您需要知道您拥有什么样的文件(或者只是尝试所有答案)。

  • RSA
    • 导入RSAPrivateKey
      • 当数据为 PKCS#1 RSAPrivateKey 格式时(PEM 开放标头:BEGIN RSA PRIVATE KEY)
    • 导入Pkcs8PrivateKey
      • 当数据为 PKCS#8 PrivateKeyInfo 格式时(PEM 开放标头:BEGIN PRIVATE KEY)
    • ImportEncryptedPkcs8PrivateKey
      • 当数据为 PKCS#8 EncryptedPrivateKeyInfo 格式时(PEM 开放标头:BEGIN ENCRYPTED PRIVATE KEY)
  • ECDSA
    • 导入ECPrivateKey
      • 当数据采用 RFC 5915 ECPrivateKey 格式时(PEM 开放标头:BEGIN EC PRIVATE KEY)
    • 导入Pkcs8PrivateKey
    • ImportEncryptedPkcs8PrivateKey
  • ECDiffieHellman
    • 导入ECPrivateKey
    • 导入Pkcs8PrivateKey
    • ImportEncryptedPkcs8PrivateKey
  • DSA
    • 导入Pkcs8PrivateKey
    • ImportEncryptedPkcs8PrivateKey

这些方法需要注意的是,它们仅了解 BER/DER 数据,而不了解 PEM 数据。因此,如果您的文件是 PEM 格式(这使得识别负载应该是什么最容易),您首先需要将其转换为 BER/DER。

对于大多数 PEM 文件来说,这很简单:您只需找到 BEGIN 和 END 标记之间的内容,通过 Convert.FromBase64String 运行它,然后瞧。从技术上讲,PEM 支持属性,并且处理这些属性更难(并且超出了此答案的范围)。

所以,你最终可能会得到类似的东西

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();
...

忽略的out 值是输入字节中使用的字节数。它主要只在从文件中间读取时才相关。

System.Security.Cryptography.Asn1.Experimental

没办法,这个库比那个低很多。

解读2:“在实践中我如何理解这些文件?”

好吧,这并不是问题的真正解释方式,而是一种转义。

加密密钥文件总是(根据我的经验)DER-(尽管偶尔放松为 BER-)编码的 ASN.1 数据结构。要完全理解它们,您需要阅读和理解

  • ITU-T REC X.680:ASN.1 语言
  • ITU-T REC X.690 ASN.1 数据的基本编码规则 (BER) 字节布局(以及很少使用的限制规范编码规则 (CER) 和常用的限制区分编码规则 (DER)) .
  • 描述特定格式的任何事物,以及它们可能引用的事物。
    • RSAPrivateKey:公钥加密标准 #1 (PKCS#1) 或 RFC 8017
    • ECPrivateKey:RFC 5915
    • PKCS#8 PrivateKeyInfo: PKCS#8 / RFC 5208
    • PKCS#8 EncryptedPrivateKeyInfo: PKCS#8 / RFC 5208(和 PKCS#5,至少,作为依赖项)

这些结构有时会使用隐私增强邮件 (PEM) 语法转换为文本表示,这本质上是

  • 5 个连字符减号
  • 全部大写 BEGIN 后跟一个空格
  • 格式标识符,不以空格结尾
  • 5 个连字符减号
  • 换行符(CRLF 或 LF)
  • BER/DER 数据的 base64 编码版本,每行包含 64 个字符
  • base64 数据最后一部分末尾的换行符(CRLF 或 LF)
  • 5 个连字符减号
  • 全部大写 END 后跟一个空格
  • 与 BEGIN 中使用的格式标识符相同
  • 5 个连字符减号
  • (理想情况下是换行符或文件结尾)

更多信息请参见RFC 7468

解读3:“如何在代码中读取这些文件的各个部分?”

当前版本

没有办法。

.NET Core 3.0

没有办法。

System.Security.Cryptography.Asn1.Experimental

这个 NuGet 包是 .NET Core 2.1 / 3.0 公开的 ASN.1 阅读器(想法是在一些可用性反馈后从 .NET Core 公开)。

读取一个 RSAPrivateKey,例如:

// 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();

其他格式也是如此。

【讨论】:

    【解决方案2】:

    我已经解决了这个问题,但我只能回答你一半。你应该使用openssl打开.key,这是代码(注意:这个代码来自这个codeproject帖子):

    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;
        }
    
    }
    }
    

    这是一个示例:

    //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);
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-05-06
      • 2016-04-17
      • 2021-07-06
      • 1970-01-01
      相关资源
      最近更新 更多