web-dev-qa-db-ja.com

PEM BASE64エンコードされた秘密キーファイルからのRSA秘密キーの取得

秘密キーファイル(PEM BASE64エンコード)があります。 Javaを使用して、ファイルを読み取り、その中のBASE64エンコードデータをデコードしようとしました。これは、試したコードスニペットです。

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

}

次のエラーが表示されます

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)

ファイル「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-----

同様の質問がここに投稿されましたが、それらは私には役に立ちませんでした。それらのほとんどすべてが、FIPS準拠のプロバイダーを使用することになっており、BCプロバイダーがFIPS準拠であるかどうかわからないので、使用したくないBouncycastleプロバイダーの使用を提案しました。

これから私を手伝う助けは非常に高く評価されるでしょう...事前に感謝します。

28
Venkat Madhav

PKCS1(AndroidではPKCS8形式のみ)の解析キーは、ASN1サポートがないためにAndroidで退屈なタスクであることが判明しましたが、含めると解決可能です DER Integersを読み取るためのスポンジ状の城 jar。

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

あなたはその秘密鍵を公開したばかりなので、今では全世界がそれを知っています。うまくいけば、それはテストのためだけだった。

編集: その他 公開されたキーのopensslテキストヘッダー----- BEGIN RSA PRIVATE KEY -----は、PKCS#1であることを示しています。ただし、問題のキーの実際のBase64コンテンツはPKCS#8です。明らかに、OPは何らかの理由でPKCS#1キーのヘッダーとトレーラをPKCS#8キーにコピーして貼り付けました。以下に提供するサンプルコードは、PKCS#8秘密キーで動作します。

そのデータから秘密鍵を作成するコードを次に示します。 Base64デコードを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

これは秘密鍵のPKCS#1形式です。このコードを試してください。 Bouncy Castleやその他のサードパーティの暗号プロバイダーは使用しません。 DERシーケンス解析のためのJava.securityとSun.securityのみ。また、PKCS#8形式の秘密キーの解析もサポートしています(ヘッダー「----- BEGIN PRIVATE KEY -----」を持つPEMファイル)。

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.

直面する問題は、PKCS8とSSLeayの2種類のPEM形式のキーがあることです。コマンドによっては、OpenSSLが両方を使用しているように見えることは役に立ちません。

通常のopenssl genrsaコマンドは、SSLeay形式のPEMを生成します。 openssl pkcs12 -in file.p12を使用してPKCS12ファイルからエクスポートすると、PKCS8ファイルが作成されます。

後者のPKCS8形式は、Java PKCS8EncodedKeySpecを使用してネイティブで開くことができます。SSLeay形式のキーは、ネイティブで開くことはできません。

SSLeay秘密鍵を開くには、以前と同様にBouncyCastleプロバイダーを使用するか、Not-Yet-Commons-SSLがBouncyCastleから最小限の必要なコードを借用してPKCS8およびSSLeay鍵のPEMおよびDER形式での解析をサポートします:- http://juliusdavies.ca/commons-ssl/pkcs8.html 。 (Not-Yet-Commons-SSLがFIPS準拠)になるかどうかはわかりません)

キー形式の識別

OpenSSLのマニュアルページから推測すると、2つの形式のキーヘッダーは次のとおりです。

PKCS8フォーマット

暗号化されていない:-----BEGIN PRIVATE KEY-----
暗号化:-----BEGIN ENCRYPTED PRIVATE KEY-----

SSLeay形式

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

(これらは他の答えと矛盾しているようですが、PKCS8EncodedKeySpecを使用してOpenSSLの出力をテストしました。PKCS8キーのみ、----BEGIN PRIVATE KEY-----をネイティブに表示します)

23

他の人が応答したように、解析しようとしているキーには、Oracleの_PKCS8EncodedKeySpec_が理解する必要がある適切なPKCS#8ヘッダーがありません。 _openssl pkcs8_を使用してキーを変換したり、JDK内部APIを使用して解析したりしたくない場合は、次のようにPKCS#8ヘッダーを追加できます。

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

そのメソッドが配置されると、その出力を次のように_PKCS8EncodedKeySpec_コンストラクターに渡すことができます:new PKCS8EncodedKeySpec(buildPKCS8Key(privateKey));

5
Noa Resare

以下に、読み取り用のコードunencryptedがあります。RSAキーは次の形式でエンコードされています。

  • PKCS#1 PEM(-----BEGIN RSA PRIVATE KEY-----
  • PKCS#8 PEM(-----BEGIN PRIVATE KEY-----
  • PKCS#8 DER(バイナリ)

Java 7+(および9以降)で動作し、サードパーティのライブラリ(BouncyCastleなど)または内部のJava API(DerInputStreamまたはDerValue)。

private static final String PKCS_1_PEM_HEADER = "-----BEGIN RSA PRIVATE KEY-----";
private static final String PKCS_1_PEM_FOOTER = "-----END RSA PRIVATE KEY-----";
private static final String PKCS_8_PEM_HEADER = "-----BEGIN PRIVATE KEY-----";
private static final String PKCS_8_PEM_FOOTER = "-----END PRIVATE KEY-----";

public static PrivateKey loadKey(String keyFilePath) throws GeneralSecurityException, IOException {
    byte[] keyDataBytes = Files.readAllBytes(Paths.get(keyFilePath));
    String keyDataString = new String(keyDataBytes, StandardCharsets.UTF_8);

    if (keyDataString.contains(PKCS_1_PEM_HEADER)) {
        // OpenSSL / PKCS#1 Base64 PEM encoded file
        keyDataString = keyDataString.replace(PKCS_1_PEM_HEADER, "");
        keyDataString = keyDataString.replace(PKCS_1_PEM_FOOTER, "");
        return readPkcs1PrivateKey(Base64.decodeBase64(keyDataString));
    }

    if (keyDataString.contains(PKCS_8_PEM_HEADER)) {
        // PKCS#8 Base64 PEM encoded file
        keyDataString = keyDataString.replace(PKCS_8_PEM_HEADER, "");
        keyDataString = keyDataString.replace(PKCS_8_PEM_FOOTER, "");
        return readPkcs8PrivateKey(Base64.decodeBase64(keyDataString));
    }

    // We assume it's a PKCS#8 DER encoded binary file
    return readPkcs8PrivateKey(Files.readAllBytes(Paths.get(keyFilePath)));
}

private static PrivateKey readPkcs8PrivateKey(byte[] pkcs8Bytes) throws GeneralSecurityException {
    KeyFactory keyFactory = KeyFactory.getInstance("RSA", "SunRsaSign");
    PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(pkcs8Bytes);
    try {
        return keyFactory.generatePrivate(keySpec);
    } catch (InvalidKeySpecException e) {
        throw new IllegalArgumentException("Unexpected key format!", e);
    }
}

private static PrivateKey readPkcs1PrivateKey(byte[] pkcs1Bytes) throws GeneralSecurityException {
    // We can't use Java internal APIs to parse ASN.1 structures, so we build a PKCS#8 key Java can understand
    int pkcs1Length = pkcs1Bytes.length;
    int totalLength = pkcs1Length + 22;
    byte[] pkcs8Header = new byte[] {
            0x30, (byte) 0x82, (byte) ((totalLength >> 8) & 0xff), (byte) (totalLength & 0xff), // Sequence + total length
            0x2, 0x1, 0x0, // Integer (0)
            0x30, 0xD, 0x6, 0x9, 0x2A, (byte) 0x86, 0x48, (byte) 0x86, (byte) 0xF7, 0xD, 0x1, 0x1, 0x1, 0x5, 0x0, // Sequence: 1.2.840.113549.1.1.1, NULL
            0x4, (byte) 0x82, (byte) ((pkcs1Length >> 8) & 0xff), (byte) (pkcs1Length & 0xff) // Octet string + length
    };
    byte[] pkcs8bytes = join(pkcs8Header, pkcs1Bytes);
    return readPkcs8PrivateKey(pkcs8bytes);
}

private static byte[] join(byte[] byteArray1, byte[] byteArray2){
    byte[] bytes = new byte[byteArray1.length + byteArray2.length];
    System.arraycopy(byteArray1, 0, bytes, 0, byteArray1.length);
    System.arraycopy(byteArray2, 0, bytes, byteArray1.length, byteArray2.length);
    return bytes;
}

ソース: https://github.com/Mastercard/client-encryption-Java/blob/master/src/main/Java/com/mastercard/developer/utils/EncryptionUtils.Java

Id_rsaファイルに.txtや.rtfなどの拡張子がないことを確認してください。リッチテキスト形式はファイルに追加の文字を追加し、それらはバイト配列に追加されます。最終的に無効な秘密鍵エラーが発生します。要するに、コンテンツではなくファイルをコピーしてください。

0
Rahil