Подтвердить что ты не робот

Как получить закрытый ключ из файла PEM?

У меня есть .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 является ложным. Зачем? я неправильно понял и что-то не так?

4b9b3361

Ответ 1

В статье в проекте 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.

Ответ 2

AFAIK.NET framework не поддерживает PEM в любом месте.

Вы можете легко обмануть это для части X509Certificate, так как вы можете извлечь строку base64 между ----- BEGIN CERTIFICATE ----- и --- --END CERTIFICATE -----, преобразуйте его в byte[] и создайте из него X509Certificate.

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

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

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

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

Ответ 3

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

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

  • Загружает сертификат PEM ( "----- BEGIN CERTIFICATE -----" ) из openssl, который может содержать "----- BEGIN RSA PRIVATE KEY -----"
  • возвращает X509Certificate2
  • закрытый ключ для 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;
        }
    }
}

Ответ 4

Другой подход заключается в преобразовании клиентского сертификата 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" содержит сертификат подписывающего лица).

Ответ 5

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

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

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

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