Obtención de la clave privada RSA desde PEM BASE64 Archivo de clave privada codificada

Tengo un archivo de clave privada (PEM BASE64 codificado). Quiero usarlo para descifrar algunos otros datos. Utilizando Java intenté leer el archivo y decodificar los datos codificados BASE64 en él … Este es el fragmento de código que probé …

import java.io.*; import java.nio.ByteBuffer; import java.security.*; import java.security.spec.PKCS8EncodedKeySpec; import com.ibm.crypto.fips.provider.RSAPrivateKey; import com.ibm.misc.BASE64Decoder; public class GetPrivateKey { public static RSAPrivateKey get() throws Exception { File privateKeyFile = new File("privatekey.key"); byte[] encodedKey = new byte[(int) privateKeyFile.length()]; new FileInputStream(privateKeyFile).read(encodedKey); ByteBuffer keyBytes = new BASE64Decoder().decodeBufferToByteBuffer(encodedKey.toString()); PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(keyBytes.array()); KeyFactory kf = KeyFactory.getInstance("RSA", "IBMJCEFIPS"); RSAPrivateKey pk = (RSAPrivateKey) kf.generatePrivate(privateKeySpec); return pk; } public static void main(String[] args) throws Exception { PrivateKey privKey = FormatMePlease.get(); System.out.println(privKey.toString()); } } 

Estoy recibiendo los siguientes errores

 Exception in thread "main" java.security.spec.InvalidKeySpecException: Inappropriate key specification: DerInputStream.getLength(): lengthTag=127, too big. at com.ibm.crypto.fips.provider.RSAKeyFactory.b(Unknown Source) at com.ibm.crypto.fips.provider.RSAKeyFactory.engineGeneratePrivate(Unknown Source) at java.security.KeyFactory.generatePrivate(Unknown Source) at GetPrivateKey.get(GetPrivateKey.java:24) at GetPrivateKey.main(GetPrivateKey.java:29) 

El contenido del archivo “privatekey.key”

 -----BEGIN RSA PRIVATE KEY----- MIIEuwIBADANBgkqhkiG9w0BAQEFAASCBKUwggShAgEAAoIBAF53wUbKmDHtvfOb8u1HPqEBFNNF csnOMjIcSEhAwIQMbgrOuQ+vH/YgXuuDJaURS85H8P4UTt6lYOJn+SFnXvS82E7LHJpVrWwQzbh2 QKh13/akPe90DlNTUGEYO7rHaPLqTlld0jkLFSytwqfwqn9yrYpM1ncUOpCciK5j8t8MzO71LJoJ g24CFxpjIS0tBrJvKzrRNcxWSRDLmu2kNmtsh7yyJouE6XoizVmBmNVltHhFaDMmqjugMQA2CZfL rxiR1ep8TH8IBvPqysqZI1RIpB/e0engP4/1KLrOt+6gGS0JEDh1kG2fJObl+N4n3sCOtgaz5Uz8 8jpwbmZ3Se8CAwEAAQKCAQAdOsSs2MbavAsIM3qo/GBehO0iqdxooMpbQvECmjZ3JTlvUqNkPPWQ vFdiW8PsHTvtackhdLsqnNUreKxXL5rr8vqi9qm0/0mXpGNi7gP3m/FeaVdYnfpIwgCe6lag5k6M yv7PG/6N8+XrWyBdwlOe96bGohvB4Jp2YFjSTM67QONQ8CdmfqokqJ8/3RyrpDvGN3iX3yzBqXGO jPkoJQv3I4lsYdR0nl4obHHnMSeWCQCYvJoZ7ZOliu/Dd0ksItlodG6s8r/ujkSa8VIhe0fnXTf0 i7lqa55CAByGN4MOR0bAkJwIB7nZzQKurBPcTAYJFFvAc5hgMnWT0XW83TehAoGBALVPGnznScUw O50OXKI5yhxGf/XDT8g28L8Oc4bctRzI+8YfIFfLJ57uDGuojO/BpqtYmXmgORru0jYR8idEkZrx gf62czOiJrCWTkBCEMtrNfFHQJQCQrjfbHofp7ODnEHbHFm7zdlbfNnEBBaKXxd2rVv4UTEhgftv wsHcimbXAoGBAIViWrHWElMeQT0datqlThE/u51mcK4VlV7iRWXVa1/gAP85ZAu44VvvDlkpYVkF zSRR+lHSOzsubDMN45OBQW6UA3RPg4TCvrTOmhQUeF5XPuSdcD0R2At6pdaLwAKnOtILg13Ha6ym Igjv8glodvem3hWLmpHIhNBiaXtf8wqpAoGADH5a8OhvKOtd8EChGXyp9LDW+HRw9vbyN/gi9dQX ltgyoUBb1jDllgoJSRHgRFUvyvbb/ImR5c03JwqtiQ8siWTC9G5WGeS+jcSNt9fVmG7W1L14MbrG Jj8fFns/7xrOlasnlPdgA+5N+CONtI/sZY2D/KZr0drhPhZBcWJlFxkCgYAn+4SOPEo/6hjKNhA6 vER7fSxDEVsDg+rDh3YgAWpvUdlaqBxqOyAqi600YugQZGHK2lv7vNYOdmrunuIx7BPuDqY+bjtR R4Mc9bVQAZbXSLXMl7j2RWwKfNhLSJbk9LX4EoVtTgLjvOUE4tAdq9fFgpqdwLwzqPTO9kECP4++ CQKBgH6tO/xcNxG/uXUideluAn3H2KeyyznZMJ7oCvzf26/XpTAMI243OoeftiKVMgxuZ7hjwqfn /VHXABc4i5gchr9RzSb1hZ/IqFzq2YGmbppg5Ok2cgwalDoDBi21bRf8aDRweL62mO+7aPnCQZ58 j5W72PB8BAr6xg0Oro25O4os -----END RSA PRIVATE KEY----- 

Preguntas similares han sido publicadas aquí, pero no sirvieron para nada. Casi todos sugirieron usar el proveedor de Bouncycastle que no estoy dispuesto a usar, ya que se supone que debo usar un proveedor que sea compatible con FIPS y no estoy seguro si el proveedor de BC cumple con FIPS.

Una ayuda para sacarme de esto sería muy apreciada … Gracias de antemano.

El análisis de PKCS1 (solo el formato PKCS8 funciona de la caja en Android) resultó ser una tarea tediosa en Android debido a la falta de compatibilidad con ASN1, aunque puede resolverse si incluye el flask Spongy castle para leer DER Integers.

 String privKeyPEM = key.replace( "-----BEGIN RSA PRIVATE KEY-----\n", "") .replace("-----END RSA PRIVATE KEY-----", ""); // Base64 decode the data byte[] encodedPrivateKey = Base64.decode(privKeyPEM, Base64.DEFAULT); try { ASN1Sequence primitive = (ASN1Sequence) ASN1Sequence .fromByteArray(encodedPrivateKey); Enumeration< ?> e = primitive.getObjects(); BigInteger v = ((DERInteger) e.nextElement()).getValue(); int version = v.intValue(); if (version != 0 && version != 1) { throw new IllegalArgumentException("wrong version for RSA private key"); } /** * In fact only modulus and private exponent are in use. */ BigInteger modulus = ((DERInteger) e.nextElement()).getValue(); BigInteger publicExponent = ((DERInteger) e.nextElement()).getValue(); BigInteger privateExponent = ((DERInteger) e.nextElement()).getValue(); BigInteger prime1 = ((DERInteger) e.nextElement()).getValue(); BigInteger prime2 = ((DERInteger) e.nextElement()).getValue(); BigInteger exponent1 = ((DERInteger) e.nextElement()).getValue(); BigInteger exponent2 = ((DERInteger) e.nextElement()).getValue(); BigInteger coefficient = ((DERInteger) e.nextElement()).getValue(); RSAPrivateKeySpec spec = new RSAPrivateKeySpec(modulus, privateExponent); KeyFactory kf = KeyFactory.getInstance("RSA"); PrivateKey pk = kf.generatePrivate(spec); } catch (IOException e2) { throw new IllegalStateException(); } catch (NoSuchAlgorithmException e) { throw new IllegalStateException(e); } catch (InvalidKeySpecException e) { throw new IllegalStateException(e); } 

Acaba de publicar esa clave privada, por lo que ahora todo el mundo sabe de qué se trata. Con suerte eso fue solo para probar.

EDITAR: Otros han notado que el encabezado de texto openssl de la clave publicada, —– BEGIN RSA PRIVATE KEY —–, indica que es PKCS # 1. Sin embargo, el contenido real de Base64 de la clave en cuestión es PKCS # 8. Evidentemente, OP copió y pegó el encabezado y el avance de una clave PKCS # 1 en la clave PKCS # 8 por algún motivo desconocido. El código de muestra que he proporcionado a continuación funciona con las claves privadas PKCS # 8.

Aquí hay un código que creará la clave privada a partir de esos datos. Tendrá que reemplazar la deencoding Base64 con su decodificador IBM Base64.

 public class RSAToy { private static final String BEGIN_RSA_PRIVATE_KEY = "-----BEGIN RSA PRIVATE KEY-----\n" + "MIIEuwIBADAN ...skipped the rest\n" // + ... // + ... skipped the rest // + ... + "-----END RSA PRIVATE KEY-----"; public static void main(String[] args) throws Exception { // Remove the first and last lines String privKeyPEM = BEGIN_RSA_PRIVATE_KEY.replace("-----BEGIN RSA PRIVATE KEY-----\n", ""); privKeyPEM = privKeyPEM.replace("-----END RSA PRIVATE KEY-----", ""); System.out.println(privKeyPEM); // Base64 decode the data byte [] encoded = Base64.decode(privKeyPEM); // PKCS8 decode the encoded RSA private key PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encoded); KeyFactory kf = KeyFactory.getInstance("RSA"); PrivateKey privKey = kf.generatePrivate(keySpec); // Display the results System.out.println(privKey); } } 

Este es el formato PKCS # 1 de una clave privada. Prueba este código No utiliza Bouncy Castle u otros proveedores de criptografía de terceros. Solo java.security y sun.security para el análisis de la secuencia DER. También es compatible con el análisis de una clave privada en formato PKCS # 8 (archivo PEM que tiene un encabezado “—– BEGIN PRIVATE KEY —–“).

 import sun.security.util.DerInputStream; import sun.security.util.DerValue; import java.io.File; import java.io.IOException; import java.math.BigInteger; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.security.GeneralSecurityException; import java.security.KeyFactory; import java.security.PrivateKey; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.RSAPrivateCrtKeySpec; import java.util.Base64; public static PrivateKey pemFileLoadPrivateKeyPkcs1OrPkcs8Encoded(File pemFileName) throws GeneralSecurityException, IOException { // PKCS#8 format final String PEM_PRIVATE_START = "-----BEGIN PRIVATE KEY-----"; final String PEM_PRIVATE_END = "-----END PRIVATE KEY-----"; // PKCS#1 format final String PEM_RSA_PRIVATE_START = "-----BEGIN RSA PRIVATE KEY-----"; final String PEM_RSA_PRIVATE_END = "-----END RSA PRIVATE KEY-----"; Path path = Paths.get(pemFileName.getAbsolutePath()); String privateKeyPem = new String(Files.readAllBytes(path)); if (privateKeyPem.indexOf(PEM_PRIVATE_START) != -1) { // PKCS#8 format privateKeyPem = privateKeyPem.replace(PEM_PRIVATE_START, "").replace(PEM_PRIVATE_END, ""); privateKeyPem = privateKeyPem.replaceAll("\\s", ""); byte[] pkcs8EncodedKey = Base64.getDecoder().decode(privateKeyPem); KeyFactory factory = KeyFactory.getInstance("RSA"); return factory.generatePrivate(new PKCS8EncodedKeySpec(pkcs8EncodedKey)); } else if (privateKeyPem.indexOf(PEM_RSA_PRIVATE_START) != -1) { // PKCS#1 format privateKeyPem = privateKeyPem.replace(PEM_RSA_PRIVATE_START, "").replace(PEM_RSA_PRIVATE_END, ""); privateKeyPem = privateKeyPem.replaceAll("\\s", ""); DerInputStream derReader = new DerInputStream(Base64.getDecoder().decode(privateKeyPem)); DerValue[] seq = derReader.getSequence(0); if (seq.length < 9) { throw new GeneralSecurityException("Could not parse a PKCS1 private key."); } // skip version seq[0]; BigInteger modulus = seq[1].getBigInteger(); BigInteger publicExp = seq[2].getBigInteger(); BigInteger privateExp = seq[3].getBigInteger(); BigInteger prime1 = seq[4].getBigInteger(); BigInteger prime2 = seq[5].getBigInteger(); BigInteger exp1 = seq[6].getBigInteger(); BigInteger exp2 = seq[7].getBigInteger(); BigInteger crtCoef = seq[8].getBigInteger(); RSAPrivateCrtKeySpec keySpec = new RSAPrivateCrtKeySpec(modulus, publicExp, privateExp, prime1, prime2, exp1, exp2, crtCoef); KeyFactory factory = KeyFactory.getInstance("RSA"); return factory.generatePrivate(keySpec); } throw new GeneralSecurityException("Not supported format of a private key"); } 

El problema que enfrentará es que hay dos tipos de claves formateadas PEM: PKCS8 y SSLeay. No ayuda que OpenSSL parezca usar ambos dependiendo del comando:

El comando usual openssl genrsa generará un formato SSLeay PEM. Una exportación desde un archivo PKCS12 con openssl pkcs12 -in file.p12 creará un archivo PKCS8.

El último formato PKCS8 se puede abrir de forma nativa en Java usando PKCS8EncodedKeySpec . Por otro lado, las claves con formato SSLeay no se pueden abrir de forma nativa.

Para abrir claves privadas SSLeay, puede utilizar el proveedor BouncyCastle como muchos lo han hecho anteriormente o Not-Yet-Commons-SSL ha pedido prestada una cantidad mínima de código necesario de BouncyCastle para admitir el análisis de claves PKCS8 y SSLeay en formato PEM y DER: http: //juliusdavies.ca/commons-ssl/pkcs8.html . (No estoy seguro de si Not-Yet-Commons-SSL cumplirá con FIPS)

Identificación del formato clave

Por deducción de las páginas man de OpenSSL, los encabezados de las teclas para dos formatos son los siguientes:

Formato PKCS8

No cifrado: -----BEGIN PRIVATE KEY-----
Encriptado: -----BEGIN ENCRYPTED PRIVATE KEY-----

Formato SSLeay

-----BEGIN RSA PRIVATE KEY-----

(Esto parece estar en contradicción con otras respuestas, pero he probado la salida de OpenSSL utilizando PKCS8EncodedKeySpec . Solo las teclas PKCS8, que muestran ----BEGIN PRIVATE KEY----- funcionan de forma nativa)

Como otros han respondido, la clave que está tratando de analizar no tiene los encabezados PKCS # 8 correctos que PKCS8EncodedKeySpec de Oracle necesita para comprenderlo. Si no desea convertir la clave utilizando openssl pkcs8 o analizarla mediante las API internas de JDK, puede anteponer el encabezado PKCS # 8 de la siguiente manera:

 static final Base64.Decoder DECODER = Base64.getMimeDecoder(); private static byte[] buildPKCS8Key(File privateKey) throws IOException { final String s = new String(Files.readAllBytes(privateKey.toPath())); if (s.contains("--BEGIN PRIVATE KEY--")) { return DECODER.decode(s.replaceAll("-----\\w+ PRIVATE KEY-----", "")); } if (!s.contains("--BEGIN RSA PRIVATE KEY--")) { throw new RuntimeException("Invalid cert format: "+ s); } final byte[] innerKey = DECODER.decode(s.replaceAll("-----\\w+ RSA PRIVATE KEY-----", "")); final byte[] result = new byte[innerKey.length + 26]; System.arraycopy(DECODER.decode("MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKY="), 0, result, 0, 26); System.arraycopy(BigInteger.valueOf(result.length - 4).toByteArray(), 0, result, 2, 2); System.arraycopy(BigInteger.valueOf(innerKey.length).toByteArray(), 0, result, 24, 2); System.arraycopy(innerKey, 0, result, 26, innerKey.length); return result; } 

Una vez que ese método esté en su lugar, puede alimentar su salida al constructor PKCS8EncodedKeySpec la PKCS8EncodedKeySpec manera: new PKCS8EncodedKeySpec(buildPKCS8Key(privateKey));

Asegúrese de que su archivo id_rsa no tenga ninguna extensión como .txt o .rtf. El formato de texto enriquecido agrega caracteres adicionales a su archivo y los agrega a la matriz de bytes. Lo que eventualmente causa un error de clave privada no válida. En resumen, copia el archivo, no el contenido.