web-dev-qa-db-fra.com

Obtenir la clé privée RSA à partir du fichier de clé privée codée PEM BASE64

J'ai un fichier de clé privée (PEM BASE64 codé). Je veux l'utiliser ailleurs pour décrypter d'autres données.En utilisant Java, j'ai essayé de lire le fichier et de décoder les données codées BASE64 qu'il contient ... C'est le fragment de code que j'ai essayé ... 

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

}

Je reçois les erreurs suivantes

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)

Le contenu du fichier "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-----

Des questions similaires ont été postées ici, mais elles n’ont servi à rien. Presque tous ont suggéré d'utiliser un fournisseur Bouncycastle qui n'est pas disposé à l'utiliser comme si j'étais censé utiliser un fournisseur conforme à FIPS et je ne sais pas si le fournisseur de BC est conforme à FIPS.

Une aide pour me sortir de cela serait très appréciée ... Merci d'avance.

27
Venkat Madhav

L'analyse de la clé PKCS1 (seul le format PKCS8 fonctionne immédiatement sous Android) s'est avérée être une tâche fastidieuse sous Android en raison de l'absence de suport ASN1, mais elle peut être résolue si vous incluez Spongy castle jar pour lire 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);
}
16
Boris Treukhov

Vous venez de publier cette clé privée. Le monde entier sait maintenant ce que c'est. J'espère que c'était juste pour les tests.

EDIT: D'autres ont noté que l'en-tête texte OpenSL de la clé publiée, ----- BEGIN RSA PRIVATE KEY -----, indique qu'il s'agit de PKCS # 1. Toutefois, le contenu réel en Base64 de la clé en question est PKCS # 8. De toute évidence, l’opérateur a copié et collé l’en-tête et la fin d’une clé PKCS # 1 sur la clé PKCS # 8 pour une raison inconnue. L'exemple de code que j'ai fourni ci-dessous fonctionne avec les clés privées PKCS # 8.

Voici un code qui créera la clé privée à partir de ces données. Vous devrez remplacer le décodage Base64 par votre décodeur 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);
    }
}
36
James K Polk

C'est le format PKCS # 1 d'une clé privée. Essayez ce code. Il n'utilise pas Bouncy Castle ou d'autres fournisseurs de chiffrement tiers. Juste Java.security et Sun.security pour l'analyse séquentielle DER. En outre, il prend en charge l'analyse d'une clé privée au format PKCS # 8 (fichier PEM avec un en-tête "----- 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");
    }
28
Yuri G.

Le problème que vous allez rencontrer est qu'il existe deux types de clés au format PEM: PKCS8 et SSLeay. Cela n'aide pas qu'OpenSSL semble utiliser les deux en fonction de la commande:

La commande habituelle openssl genrsa génère un fichier PEM au format SSLeay. Une exportation à partir d'un fichier PKCS12 avec openssl pkcs12 -in file.p12 créera un fichier PKCS8.

Le dernier format PKCS8 peut être ouvert de manière native en Java avec PKCS8EncodedKeySpec. Les clés formatées SSLeay, en revanche, ne peuvent pas être ouvertes de manière native. 

Pour ouvrir les clés privées SSLeay, vous pouvez utiliser le fournisseur BouncyCastle comme beaucoup l'ont fait auparavant ou bien Not-Yet-Commons-SSL a emprunté une quantité minimale de code nécessaire à BouncyCastle pour prendre en charge l'analyse des clés PKCS8 et SSLeay au format PEM et DER: http://juliusdavies.ca/commons-ssl/pkcs8.html . (Je ne sais pas si Non-Yet-Commons-SSL sera conforme à FIPS)

Identification du format de clé

Par déduction à partir des pages de manuel OpenSSL, les en-têtes de clé pour deux formats sont les suivants:

Format PKCS8

Non chiffré: -----BEGIN PRIVATE KEY-----
Crypté: -----BEGIN ENCRYPTED PRIVATE KEY-----

Format SSLeay

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

(Celles-ci semblent être en contradiction avec d'autres réponses mais j'ai testé la sortie d'OpenSSL en utilisant PKCS8EncodedKeySpec. Seules les clés PKCS8, montrant que ----BEGIN PRIVATE KEY----- fonctionne de manière native)

22

Comme d’autres ont répondu, la clé que vous essayez d’analyser n’a pas les en-têtes PKCS # 8 appropriés que le PKCS8EncodedKeySpec d’Oracle a besoin de comprendre. Si vous ne souhaitez pas convertir la clé à l'aide de openssl pkcs8 ni l'analyser à l'aide des API internes du JDK, vous pouvez ajouter l'en-tête PKCS # 8 comme suit:

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

Une fois que cette méthode est en place, vous pouvez envoyer sa sortie au constructeur PKCS8EncodedKeySpec comme ceci: new PKCS8EncodedKeySpec(buildPKCS8Key(privateKey));

4
Noa Resare

Assurez-vous que votre fichier id_rsa n’a aucune extension comme .txt ou .rtf. Le format RTF ajoute des caractères supplémentaires à votre fichier et ceux-ci sont ajoutés au tableau d'octets. Ce qui finit par provoquer une erreur de clé privée non valide. Longue histoire courte, copiez le fichier, pas le contenu. 

0
Rahil