как получить закрытый ключ из файла PEM? - PullRequest
29 голосов
/ 13 сентября 2011

У меня есть файл .PEM, который включает в себя открытый ключ и закрытый ключ для передачи данных SSL, например:

-----BEGIN RSA PRIVATE KEY-----
      private key data
-----END RSA PRIVATE KEY-----
-----BEGIN CERTIFICATE-----
      public key data
-----END CERTIFICATE-----

, когда я хочу загрузить файл .PEM с помощью следующего кода:

X509Certificate2 xx = new X509Certificate2("c:\\myKey.pem");

я получаю исключение, которое говорит: «Не удается найти запрошенный объект»., с полным стеком:

System.Security.Cryptography.CryptographicException was unhandled
  Message=Cannot find the requested object.

  Source=mscorlib
  StackTrace:
       at System.Security.Cryptography.CryptographicException.ThrowCryptographicException(Int32 hr)
       at System.Security.Cryptography.X509Certificates.X509Utils._QueryCertFileType(String fileName)
       at System.Security.Cryptography.X509Certificates.X509Certificate.LoadCertificateFromFile(String fileName, Object password, X509KeyStorageFlags keyStorageFlags)
       at System.Security.Cryptography.X509Certificates.X509Certificate..ctor(String fileName)
       at System.Security.Cryptography.X509Certificates.X509Certificate2..ctor(String fileName)
       at DLLTest.SSL_Test.test() in E:\Projects\DLLTest\DLLTest\SSL_Test.cs:line 165
       at DLLTest.SSL_Test.Run() in E:\Projects\DLLTest\DLLTest\SSL_Test.cs:line 21
       at DLLTest.Program.Main(String[] args) in E:\Projects\DLLTest\DLLTest\Program.cs:line 21
       at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)
       at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
       at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
       at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
       at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean ignoreSyncCtx)
       at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
       at System.Threading.ThreadHelper.ThreadStart()
  InnerException: 

, если я поменяю местами раздел с закрытым ключом и раздел с открытым ключом, код работает и загружает данные, и я могу получить только информацию открытого ключа из объекта, напримерIssuerName и его HasPrivateKey ложны.Зачем?я неправильно понял и что-то не так делаю?

Ответы [ 7 ]

31 голосов
/ 08 мая 2012

В Project Code есть статья , в которой есть весь код, необходимый для этого.Это всего лишь пара классов, поэтому это облегченное решение.

Чтобы получить байты для сертификата или ключа из файла PEM, будет работать следующий метод, независимо от порядка ключа и сертификата.в файле.

 byte[] GetBytesFromPEM( string pemString, string section )
 {
     var header = String.Format("-----BEGIN {0}-----", section);
     var footer = String.Format("-----END {0}-----", section);

     var start= pemString.IndexOf(header, StringComparison.Ordinal);
     if( start < 0 )
        return null;

     start += header.Length;
     var end = pemString.IndexOf(footer, start, StringComparison.Ordinal) - start;

     if( end < 0 )
        return null;

     return Convert.FromBase64String( pemString.Substring( start, end ) );
 }

Загрузите файл PEM в строку и вызовите метод выше, чтобы получить байты, которые представляют сертификат.Затем вы передаете полученные байты конструктору X509Certificate2:

 var pem = System.IO.File.ReadAllText( "c:\\myKey.pem" )
 byte[] certBuffer = GetBytesFromPEM( pem, "CERTIFICATE" );
 var certificate = new X509Certificate2( certBuffer );

Загрузка личного ключа (RSA) из файла PEM немного сложнее, но вы найдете поддержку для этого в вышеупомянутомстатья также с использованием метода Crypto.DecodeRsaPrivateKey.

16 голосов
/ 13 октября 2011

AFAIK .NET Framework нигде не поддерживает PEM.

Вы можете легко взломать это для части X509Certificate, поскольку вы можете извлечь строку base64 между ----- НАЧАТЬ СЕРТИФИКАТ ----- и ----- КОНЕЦ СЕРТИФИКАТА ----- строк, преобразовать его в byte[] и создать из него X509Certificate.

Простое решение - скопировать и вставить код из X509Certificate.cs Mono.Security для этого.

Получение секретного ключа немного сложнее, поскольку получение byte[] не очень поможет в восстановлении экземпляра RSA (что можно предположить, поскольку заголовок PEM утверждает, что это RSA).

На этот раз лучше скопировать-вставить из файла PKCS8.cs из Mono.Security и вызвать метод декодирования sioply.

Отказ от ответственности: Я являюсь основным автором кода Mono, рассмотренного выше, и все это доступно под лицензией MIT.X11

10 голосов
/ 17 ноября 2014

У меня была та же проблема и - для записи - я выкладываю здесь полный, рабочий пример кода (ключ вырезан по известным причинам). В основном это подборка материалов, найденных в Интернете, и требований моего домашнего проекта.

Следующие функции кода

  • Загружает сертификат PEM ("----- BEGIN CERTIFICATE -----") из openssl, который может содержать "----- BEGIN RSA PRIVATE KEY -----"
  • возвращает X509Сертификат2
  • закрытый ключ для x509 хранится в машинном магазине (функция windows), с правилом доступа для всех
  • закрытый ключ нельзя экспортировать из магазина

код:

using System;
using System.IO;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Security.Principal;
using System.Security.AccessControl;

namespace Test1
{
    public static class Test
    {
        public static int Main()
        {
            string pemCertWithPrivateKeyText = @"-----BEGIN CERTIFICATE-----
...
bjEdMBsGA1UEChQUVGV4YXMgQSZNIFV5jZTESMBAGA1UEAxMJVXNlciBOYW1lMSA
...
YXMgQSZNIFV5jZTESMBAGA1e2yX28ERsgBD6xx7mJDrPxkqWyV/a9tCF8W6jGSs=
-----END CERTIFICATE-----
-----BEGIN RSA PRIVATE KEY-----
MIIEow..................
jZMxBWg+imTpbGb+TpR2kxBWctnzFOWRuVYdSQIDAQABAoIBAFSKz/RLtkmZKE1d
....
BWctnzFOWRuVYdSdsf+WDqNxEzrL08SU1w5WuSxIsbxchUvG4
-----END RSA PRIVATE KEY-----
"; // just an example

            X509Certificate2 cert = PEMToX509.Convert(pemCertWithPrivateKeyText);

            return (cert.HasPrivateKey ? 1 : -1);
        }
    }

    internal static class PEMToX509
    {
        const string KEY_HEADER = "-----BEGIN RSA PRIVATE KEY-----";
        const string KEY_FOOTER = "-----END RSA PRIVATE KEY-----";

        internal static X509Certificate2 Convert(string pem)
        {
            try
            {
                byte[] pemCertWithPrivateKey = System.Text.Encoding.ASCII.GetBytes(pem);

                RSACryptoServiceProvider rsaPK = GetRSA(pem);

                X509Certificate2 cert = new X509Certificate2();
                cert.Import(pemCertWithPrivateKey, "", X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet);

                if (rsaPK != null)
                {
                    cert.PrivateKey = rsaPK;
                }

                return cert;
            }
            catch
            {
                return null;
            }
        }

        private static RSACryptoServiceProvider GetRSA(string pem)
        {
            RSACryptoServiceProvider rsa = null;

            if (IsPrivateKeyAvailable(pem))
            {
                RSAParameters privateKey = DecodeRSAPrivateKey(pem);

                SecurityIdentifier everyoneSI = new SecurityIdentifier(WellKnownSidType.WorldSid, null);
                CryptoKeyAccessRule rule = new CryptoKeyAccessRule(everyoneSI, CryptoKeyRights.FullControl, AccessControlType.Allow);

                CspParameters cspParameters = new CspParameters();
                cspParameters.KeyContainerName = "MY_C_NAME";
                cspParameters.ProviderName = "Microsoft Strong Cryptographic Provider";
                cspParameters.ProviderType = 1;
                cspParameters.Flags = CspProviderFlags.UseNonExportableKey | CspProviderFlags.UseMachineKeyStore;

                cspParameters.CryptoKeySecurity = new CryptoKeySecurity();
                cspParameters.CryptoKeySecurity.SetAccessRule(rule);

                rsa = new RSACryptoServiceProvider(cspParameters);
                rsa.PersistKeyInCsp = true;
                rsa.ImportParameters(privateKey);
            }

            return rsa;
        }

        private static bool IsPrivateKeyAvailable(string privateKeyInPEM)
        {
            return (privateKeyInPEM != null && privateKeyInPEM.Contains(KEY_HEADER)
                && privateKeyInPEM.Contains(KEY_FOOTER));
        }

        private static RSAParameters DecodeRSAPrivateKey(string privateKeyInPEM)
        {
            if (IsPrivateKeyAvailable(privateKeyInPEM) == false)
                throw new ArgumentException("bad format");

            string keyFormatted = privateKeyInPEM;

            int cutIndex = keyFormatted.IndexOf(KEY_HEADER);
            keyFormatted = keyFormatted.Substring(cutIndex, keyFormatted.Length - cutIndex);
            cutIndex = keyFormatted.IndexOf(KEY_FOOTER);
            keyFormatted = keyFormatted.Substring(0, cutIndex + KEY_FOOTER.Length);
            keyFormatted = keyFormatted.Replace(KEY_HEADER, "");
            keyFormatted = keyFormatted.Replace(KEY_FOOTER, "");
            keyFormatted = keyFormatted.Replace("\r", "");
            keyFormatted = keyFormatted.Replace("\n", "");
            keyFormatted = keyFormatted.Trim();

            byte[] privateKeyInDER = System.Convert.FromBase64String(keyFormatted);

            byte[] paramModulus;
            byte[] paramDP;
            byte[] paramDQ;
            byte[] paramIQ;
            byte[] paramE;
            byte[] paramD;
            byte[] paramP;
            byte[] paramQ;

            MemoryStream memoryStream = new MemoryStream(privateKeyInDER);
            BinaryReader binaryReader = new BinaryReader(memoryStream);

            ushort twobytes = 0;
            int elements = 0;
            byte bt = 0;

            try
            {
                twobytes = binaryReader.ReadUInt16();
                if (twobytes == 0x8130) 
                    binaryReader.ReadByte();
                else if (twobytes == 0x8230) 
                    binaryReader.ReadInt16();
                else 
                    throw new CryptographicException("Wrong data");

                twobytes = binaryReader.ReadUInt16();
                if (twobytes != 0x0102) 
                    throw new CryptographicException("Wrong data");

                bt = binaryReader.ReadByte();
                if (bt != 0x00) 
                    throw new CryptographicException("Wrong data");

                elements = GetIntegerSize(binaryReader);
                paramModulus = binaryReader.ReadBytes(elements);

                elements = GetIntegerSize(binaryReader);
                paramE = binaryReader.ReadBytes(elements);

                elements = GetIntegerSize(binaryReader);
                paramD = binaryReader.ReadBytes(elements);

                elements = GetIntegerSize(binaryReader);
                paramP = binaryReader.ReadBytes(elements);

                elements = GetIntegerSize(binaryReader);
                paramQ = binaryReader.ReadBytes(elements);

                elements = GetIntegerSize(binaryReader);
                paramDP = binaryReader.ReadBytes(elements);

                elements = GetIntegerSize(binaryReader);
                paramDQ = binaryReader.ReadBytes(elements);

                elements = GetIntegerSize(binaryReader);
                paramIQ = binaryReader.ReadBytes(elements);

                EnsureLength(ref paramD, 256);
                EnsureLength(ref paramDP, 128);
                EnsureLength(ref paramDQ, 128);
                EnsureLength(ref paramE, 3);
                EnsureLength(ref paramIQ, 128);
                EnsureLength(ref paramModulus, 256);
                EnsureLength(ref paramP, 128);
                EnsureLength(ref paramQ, 128);

                RSAParameters rsaParameters = new RSAParameters();
                rsaParameters.Modulus = paramModulus;
                rsaParameters.Exponent = paramE;
                rsaParameters.D = paramD;
                rsaParameters.P = paramP;
                rsaParameters.Q = paramQ;
                rsaParameters.DP = paramDP;
                rsaParameters.DQ = paramDQ;
                rsaParameters.InverseQ = paramIQ;

                return rsaParameters;
            }
            finally
            {
                binaryReader.Close();
            }
        }

        private static int GetIntegerSize(BinaryReader binary)
        {
            byte bt = 0;
            byte lowbyte = 0x00;
            byte highbyte = 0x00;
            int count = 0;

            bt = binary.ReadByte();

            if (bt != 0x02) 
                return 0;

            bt = binary.ReadByte();

            if (bt == 0x81) 
                count = binary.ReadByte();
            else if (bt == 0x82)
            {
                highbyte = binary.ReadByte();
                lowbyte = binary.ReadByte();
                byte[] modint = { lowbyte, highbyte, 0x00, 0x00 };
                count = BitConverter.ToInt32(modint, 0);
            }
            else 
                count = bt;

            while (binary.ReadByte() == 0x00)
                count -= 1;

            binary.BaseStream.Seek(-1, SeekOrigin.Current);

            return count;
        }

        private static void EnsureLength(ref byte[] data, int desiredLength)
        {
            if (data == null || data.Length >= desiredLength)
                return;

            int zeros = desiredLength - data.Length;

            byte[] newData = new byte[desiredLength];
            Array.Copy(data, 0, newData, zeros, data.Length);

            data = newData;
        }
    }
}
6 голосов
/ 01 октября 2015

Другой подход заключается в преобразовании сертификата PEM клиента в формат PFX, поддерживаемый Windows.Это можно сделать, используя, например, openssl, запустив:

openssl pkcs12 -export -out cert.pfx -inkey cert.key -in cert.pem -certfile ca.pem

(где «cert.pfx» - выходной файл, «cert.key» содержит закрытый ключ «cert.pem»)содержит сертификат ввода, а «ca.pem» содержит сертификат подписавшего).

0 голосов
/ 05 марта 2019

Я столкнулся с той же проблемой и нашел решение, как показано ниже:

Сначала преобразуйте prkey.pem в prkey.xml с помощью этого инструмента // https://superdry.apphb.com/tools/online-rsa-key-converter

         var dataString = "test";

        byte[] dataToEncrypt = Encoding.UTF8.GetBytes(dataString);

        RSACryptoServiceProvider provider = new RSACryptoServiceProvider();
        provider.FromXmlString(File.ReadAllText("C:\prkey.xml"));
        byte[] signedBytes = provider.SignData(dataToEncrypt, new SHA256CryptoServiceProvider());

        textBox3.Text = BitConverter.ToString(signedBytes);
0 голосов
/ 31 июля 2012

Пример кода вы можете найти на http://pages.infinit.net/ctech/20040812-0816.html, и он работает для меня.

0 голосов
/ 14 сентября 2011

Я не знаю .NET (но Java), но ответ должен быть таким же.
Ваш файл pem содержит как сертификат, так и закрытый ключ.
Это обычный экспорт в OpenSSL.
Чтобы создать экземпляр объекта X509Certificate в Java, вы должны использовать только часть файла, которая говорит:

----- НАЧАТЬ СЕРТИФИКАТ -----
данные сертификата
----- КОНЕЦ СЕРТИФИКАТА -----

В .NET должно быть то же самое.
Просто загрузите файл и загрузите эту часть PEM.

Сделайте то же самое для личного ключа.
В Java вы должны использовать соответствующий объект, то есть PrivateKey, чтобы загрузить его.
Используйте соответствующий для .NET

...