cómo obtener la clave privada del archivo PEM?

Tengo un archivo .PEM que incluye una clave pública y una clave privada para la transferencia de datos SSL como esta:

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

cuando quiero cargar el archivo .PEM con el siguiente código:

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

recibo una excepción que dice: “No se puede encontrar el objeto solicitado”. , con stack completa:

 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: 

si cambio el lugar de la sección de clave privada y la sección de clave pública, el código funciona y carga datos, y puedo obtener información de clave pública del objeto, por ej. IssuerName, y su HasPrivateKey es falso. ¿por qué? ¿Estoy mal entendido y estoy haciendo algo mal?

Hay un artículo sobre el Proyecto de Código que tiene todo el código que necesita para hacer esto. Son solo un par de clases, así que es una solución ligera.

Para obtener los bytes de un certificado o una clave del archivo PEM, funcionará el siguiente método, independientemente del orden de la clave y el certificado en el archivo.

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

Cargue el archivo PEM en una cadena y llame al método anterior para obtener los bytes que representan el certificado. A continuación, pasa los bytes obtenidos al constructor de un X509Certificate2:

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

Cargar la clave privada (RSA) del archivo PEM es un poco más complicado, pero también encontrará soporte para eso en el artículo mencionado utilizando el método Crypto.DecodeRsaPrivateKey .

AFAIK .NET Framework no admite PEM en ninguna parte.

Puede hackear esto fácilmente para la parte X509Certificate ya que puede extraer la cadena base64 entre las líneas —– BEGIN CERTIFICATE —– y —– END CERTIFICATE —– , convertirla en una byte[] y crea el X509Certificate partir de él.

Una solución fácil es copiar y pegar código de Mono.Security’s X509Certificate.cs para hacer esto.

Obtener la clave privada es un poco complicado ya que obtener el byte[] no será de mucha ayuda para reconstruir la instancia de RSA (que podemos suponer ya que el encabezado de PEM indica que es RSA).

Esta vez, es mejor que copie y pegue desde el archivo PKCS8.cs de Mono.Security y llame al método de deencoding.

Descargo de responsabilidad: soy el autor principal del código Mono discutido anteriormente y está todo disponible bajo la licencia MIT.X11

Tuve el mismo problema y, para que conste, publiqué aquí un ejemplo de código de trabajo completo (la clave se corta por razones conocidas). Es principalmente una comstackción de cosas que se encuentran en Internet y los requisitos de mi proyecto de origen.

Siguiendo las características del código

  • Carga un certificado PEM (“—– BEGIN CERTIFICATE —–“) de openssl que puede contener “—– BEGIN RSA PRIVATE KEY —–”
  • devuelve X509Certificate2
  • la clave privada para x509 se almacena en la tienda de la máquina (función de Windows), con una regla de acceso para todos
  • la clave privada no se puede exportar desde la tienda

El código:

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

Un enfoque diferente es convertir el certificado PEM del cliente al formato PFX compatible con Windows. Esto se puede hacer usando, por ejemplo, openssl, ejecutando:

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

(donde “cert.pfx” es el archivo de salida, “cert.key” contiene la clave privada, “cert.pem” contiene el certificado de entrada, y “ca.pem” contiene el certificado del firmante).

No sé .NET (pero Java) pero la respuesta debería ser la misma.
Su archivo pem contiene tanto el certificado como la clave privada.
Esta es una exportación habitual en OpenSSL.
Para instanciar un objeto de X509Certificate en Java, usaría solo la parte del archivo que dice:

—– BEGIN CERTIFICATE —–
datos de certificado
—– FIN CERTIFICADO —–

Debería ser lo mismo en .NET.
Simplemente cargue el archivo y cargue esa parte de PEM.

Haz lo mismo con la clave privada.
En java usarías el objeto correspondiente, es decir, PrivateKey para cargarlo.
Use el apropiado para .NET

Hay un código de muestra que puede encontrar en http://pages.infinit.net/ctech/20040812-0816.html y funciona para mí.