Як прочитати файл .pem, щоб отримати приватний та відкритий ключ


82

Я пишу невеликий фрагмент коду, який читає відкритий та закритий ключ, що зберігається у файлі .pem. Я використовую такі команди для генерації ключів.

Нижче команда для створення пари ключів.

   $openssl genrsa -out mykey.pem 2048

Ця команда для створення приватного ключа

$openssl pkcs8 -topk8 -inform PEM -outform PEM -in mykey.pem \
    -out private_key.pem -nocrypt

і ця команда, щоб отримати відкритий ключ.

$ openssl rsa -in mykey.pem -pubout -outform DER -out public_key.der

Я написав два методи, які читають приватний ключ і відкритий ключ відповідно.

   public  PrivateKey getPemPrivateKey(String filename, String algorithm) throws Exception {
      File f = new File(filename);
      FileInputStream fis = new FileInputStream(f);
      DataInputStream dis = new DataInputStream(fis);
      byte[] keyBytes = new byte[(int) f.length()];
      dis.readFully(keyBytes);
      dis.close();

      String temp = new String(keyBytes);
      String privKeyPEM = temp.replace("-----BEGIN PRIVATE KEY-----\n", "");
      privKeyPEM = privKeyPEM.replace("-----END PRIVATE KEY-----", "");
      //System.out.println("Private key\n"+privKeyPEM);

      Base64 b64 = new Base64();
      byte [] decoded = b64.decode(privKeyPEM);

      PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(decoded);
      KeyFactory kf = KeyFactory.getInstance(algorithm);
      return kf.generatePrivate(spec);
      }

   public  PublicKey getPemPublicKey(String filename, String algorithm) throws Exception {
      File f = new File(filename);
      FileInputStream fis = new FileInputStream(f);
      DataInputStream dis = new DataInputStream(fis);
      byte[] keyBytes = new byte[(int) f.length()];
      dis.readFully(keyBytes);
      dis.close();

      String temp = new String(keyBytes);
      String publicKeyPEM = temp.replace("-----BEGIN PUBLIC KEY-----\n", "");
      publicKeyPEM = publicKeyPEM.replace("-----END PUBLIC KEY-----", "");


      Base64 b64 = new Base64();
      byte [] decoded = b64.decode(publicKeyPEM);

      X509EncodedKeySpec spec =
            new X509EncodedKeySpec(decoded);
      KeyFactory kf = KeyFactory.getInstance(algorithm);
      return kf.generatePublic(spec);
      }

Я відчуваю, що це наївний спосіб зробити це. Я не міг отримати кращого способу зробити це через Інтернет. Хтось може підказати мені, який найкращий спосіб писати той самий код для обробки загальних випадків. Я не хочу користуватися будь-якою сторонньою бібліотекою.
Я маю дуже базові знання співу / шифрування і навряд чи використовую будь-які API безпеки Java. Тож якщо я десь не маю сенсу, будь ласка, вкажіть.


2
Хммм ... мені це виглядає досить добре. Я не думаю, що існує кращий спосіб у JCE, який не має функцій обробки PEM. Ви відповіли на власне запитання та надали нам хороший приклад коду.
Елі Розенкруфт,

2
Ймовірно, вам слід змінити "privKeyPEM" у "getPemPublicKey" на "pubKeyPEM".
Елі Розенкруфт,

Як це можна зробити (або це можна зробити) без використання openssl -nocryptкоманди. Чи може ця частина також виконуватися на Java?
Девід Блевінс,

1
"openssl genrsa" генерує приватний ключ, а не пару ключів? wiki.openssl.org/index.php/Manual:Genrsa(1)
lznt

Відповіді:


40

Спробуйте цей клас.

package groovy;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.GeneralSecurityException;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.SignatureException;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

import javax.crypto.Cipher;

import org.apache.commons.codec.binary.Base64;

public class RSA {

private static String getKey(String filename) throws IOException {
    // Read key from file
    String strKeyPEM = "";
    BufferedReader br = new BufferedReader(new FileReader(filename));
    String line;
    while ((line = br.readLine()) != null) {
        strKeyPEM += line + "\n";
    }
    br.close();
    return strKeyPEM;
}
public static RSAPrivateKey getPrivateKey(String filename) throws IOException, GeneralSecurityException {
    String privateKeyPEM = getKey(filename);
    return getPrivateKeyFromString(privateKeyPEM);
}

public static RSAPrivateKey getPrivateKeyFromString(String key) throws IOException, GeneralSecurityException {
    String privateKeyPEM = key;
    privateKeyPEM = privateKeyPEM.replace("-----BEGIN PRIVATE KEY-----\n", "");
    privateKeyPEM = privateKeyPEM.replace("-----END PRIVATE KEY-----", "");
    byte[] encoded = Base64.decodeBase64(privateKeyPEM);
    KeyFactory kf = KeyFactory.getInstance("RSA");
    PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encoded);
    RSAPrivateKey privKey = (RSAPrivateKey) kf.generatePrivate(keySpec);
    return privKey;
}


public static RSAPublicKey getPublicKey(String filename) throws IOException, GeneralSecurityException {
    String publicKeyPEM = getKey(filename);
    return getPublicKeyFromString(publicKeyPEM);
}

public static RSAPublicKey getPublicKeyFromString(String key) throws IOException, GeneralSecurityException {
    String publicKeyPEM = key;
    publicKeyPEM = publicKeyPEM.replace("-----BEGIN PUBLIC KEY-----\n", "");
    publicKeyPEM = publicKeyPEM.replace("-----END PUBLIC KEY-----", "");
    byte[] encoded = Base64.decodeBase64(publicKeyPEM);
    KeyFactory kf = KeyFactory.getInstance("RSA");
    RSAPublicKey pubKey = (RSAPublicKey) kf.generatePublic(new X509EncodedKeySpec(encoded));
    return pubKey;
}

public static String sign(PrivateKey privateKey, String message) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException, UnsupportedEncodingException {
    Signature sign = Signature.getInstance("SHA1withRSA");
    sign.initSign(privateKey);
    sign.update(message.getBytes("UTF-8"));
    return new String(Base64.encodeBase64(sign.sign()), "UTF-8");
}


public static boolean verify(PublicKey publicKey, String message, String signature) throws SignatureException, NoSuchAlgorithmException, UnsupportedEncodingException, InvalidKeyException {
    Signature sign = Signature.getInstance("SHA1withRSA");
    sign.initVerify(publicKey);
    sign.update(message.getBytes("UTF-8"));
    return sign.verify(Base64.decodeBase64(signature.getBytes("UTF-8")));
}

public static String encrypt(String rawText, PublicKey publicKey) throws IOException, GeneralSecurityException {
    Cipher cipher = Cipher.getInstance("RSA");
    cipher.init(Cipher.ENCRYPT_MODE, publicKey);
    return Base64.encodeBase64String(cipher.doFinal(rawText.getBytes("UTF-8")));
}

public static String decrypt(String cipherText, PrivateKey privateKey) throws IOException, GeneralSecurityException {
    Cipher cipher = Cipher.getInstance("RSA");
    cipher.init(Cipher.DECRYPT_MODE, privateKey);
    return new String(cipher.doFinal(Base64.decodeBase64(cipherText)), "UTF-8");
}
}


Required jar library "common-codec-1.6"

5
Only RSAPublicKeySpec and X509EncodedKeySpec supported for RSA public keys. Переключився на, X509EncodedKeySpecа потім дістався java.security.InvalidKeyException: IOException: DerInputStream.getLength(): lengthTag=111, too big.. Це з файлом pem, створеним AWS EC2
Хулі,

1
@Hooli, який потрібно використовувати, RSAPublicKeySpecоскільки ваш відкритий ключ здається у PKCS#1форматі, а не у PKCS#8форматі. В останньому випадку вам потрібно використовувати X509EncodedKeySpec(а не так, PKCS8EncodedKeySpecяк зазначено у відповіді). Ви можете розрізнити ці два, подивившись на заголовок,BEGIN RSA PUBLIC KEY проти BEGIN PUBLIC KEY.
jansohn

До речі, я не знайшов можливості програмно витягти модуль та загальний показник, необхідні для конструктора RSAPublicKeySpec . Тож, мабуть, простіше просто перетворити з PKCS#1на PKCS#8на opensslабо подібні програми ...
jansohn

Подяку це насправді працює. Дивно те, що Java полегшує читання відкритого сертифіката, але важко читає закритий ключ.
Андріс,

Це працює, однак мені довелося перетворити приватний ключ у формат pks8: openssl pkcs8 -topk8 -inform PEM -outform PEM -nocrypt -in <name-file> -out <file-name>
Deian

19

Java 9+:

private byte[] loadPEM(String resource) throws IOException {
    URL url = getClass().getResource(resource);
    InputStream in = url.openStream();
    String pem = new String(in.readAllBytes(), StandardCharsets.ISO_8859_1);
    Pattern parse = Pattern.compile("(?m)(?s)^---*BEGIN.*---*$(.*)^---*END.*---*$.*");
    String encoded = parse.matcher(pem).replaceFirst("$1");
    return Base64.getMimeDecoder().decode(encoded);
}

@Test
public void test() throws Exception {
    KeyFactory kf = KeyFactory.getInstance("RSA");
    CertificateFactory cf = CertificateFactory.getInstance("X.509");
    PrivateKey key = kf.generatePrivate(new PKCS8EncodedKeySpec(loadPEM("test.key")));
    PublicKey pub = kf.generatePublic(new X509EncodedKeySpec(loadPEM("test.pub")));
    Certificate crt = cf.generateCertificate(getClass().getResourceAsStream("test.crt"));
}

Java 8:

замініть in.readAllBytes()дзвінок на виклик до цього:

byte[] readAllBytes(InputStream in) throws IOException {
    ByteArrayOutputStream baos= new ByteArrayOutputStream();
    byte[] buf = new byte[1024];
    for (int read=0; read != -1; read = in.read(buf)) { baos.write(buf, 0, read); }
    return baos.toByteArray();
}

дякую Даніелю за те, що він помітив проблеми із сумісністю API


2
мене дивує, що нам або потрібно взяти цілу підкачувальну бібліотеку лише тому, що java не може вручну прочитати .pemфайл із коробки, або нам потрібно це зробити :) Хоча дякую за рішення
Deniss M.

18

Одним із варіантів є використання PEMParser від bouncycastle :

Клас для аналізу потоків, кодованих OpenSSL PEM, що містять сертифікати X509, ключі, закодовані PKCS8, та об'єкти PKCS7.

У разі об'єктів PKCS7 зчитувач поверне об'єкт CMS ContentInfo. Відкриті ключі будуть повернуті як добре сформовані об'єкти SubjectPublicKeyInfo, приватні ключі будуть повернуті як добре сформовані об'єкти PrivateKeyInfo. У випадку приватного ключа PEMKeyPair зазвичай повертається, якщо кодування містить як приватний, так і відкритий ключ. CRL, сертифікати, запити PKCS # 10 та сертифікати атрибутів генерують відповідний клас власника BC.

Ось приклад використання тестового коду Parser :

package org.bouncycastle.openssl.test;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.math.BigInteger;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Security;
import java.security.Signature;
import java.security.interfaces.DSAPrivateKey;
import java.security.interfaces.RSAPrivateCrtKey;
import java.security.interfaces.RSAPrivateKey;

import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.cms.CMSObjectIdentifiers;
import org.bouncycastle.asn1.cms.ContentInfo;
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.asn1.x9.ECNamedCurveTable;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openssl.PEMDecryptorProvider;
import org.bouncycastle.openssl.PEMEncryptedKeyPair;
import org.bouncycastle.openssl.PEMKeyPair;
import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.openssl.PEMWriter;
import org.bouncycastle.openssl.PasswordFinder;
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
import org.bouncycastle.openssl.jcajce.JceOpenSSLPKCS8DecryptorProviderBuilder;
import org.bouncycastle.openssl.jcajce.JcePEMDecryptorProviderBuilder;
import org.bouncycastle.operator.InputDecryptorProvider;
import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo;
import org.bouncycastle.util.test.SimpleTest;

/**
 * basic class for reading test.pem - the password is "secret"
 */
public class ParserTest
    extends SimpleTest
{
    private static class Password
        implements PasswordFinder
    {
        char[]  password;

        Password(
            char[] word)
        {
            this.password = word;
        }

        public char[] getPassword()
        {
            return password;
        }
    }

    public String getName()
    {
        return "PEMParserTest";
    }

    private PEMParser openPEMResource(
        String          fileName)
    {
        InputStream res = this.getClass().getResourceAsStream(fileName);
        Reader fRd = new BufferedReader(new InputStreamReader(res));
        return new PEMParser(fRd);
    }

    public void performTest()
        throws Exception
    {
        PEMParser       pemRd = openPEMResource("test.pem");
        Object          o;
        PEMKeyPair      pemPair;
        KeyPair         pair;

        while ((o = pemRd.readObject()) != null)
        {
            if (o instanceof KeyPair)
            {
                //pair = (KeyPair)o;

                //System.out.println(pair.getPublic());
                //System.out.println(pair.getPrivate());
            }
            else
            {
                //System.out.println(o.toString());
            }
        }

        // test bogus lines before begin are ignored.
        pemRd = openPEMResource("extratest.pem");

        while ((o = pemRd.readObject()) != null)
        {
            if (!(o instanceof X509CertificateHolder))
            {
                fail("wrong object found");
            }
        }

        //
        // pkcs 7 data
        //
        pemRd = openPEMResource("pkcs7.pem");
        ContentInfo d = (ContentInfo)pemRd.readObject();

        if (!d.getContentType().equals(CMSObjectIdentifiers.envelopedData))
        {
            fail("failed envelopedData check");
        }

        //
        // ECKey
        //
        pemRd = openPEMResource("eckey.pem");
        ASN1ObjectIdentifier ecOID = (ASN1ObjectIdentifier)pemRd.readObject();
        X9ECParameters ecSpec = ECNamedCurveTable.getByOID(ecOID);

        if (ecSpec == null)
        {
            fail("ecSpec not found for named curve");
        }

        pemPair = (PEMKeyPair)pemRd.readObject();

        pair = new JcaPEMKeyConverter().setProvider("BC").getKeyPair(pemPair);

        Signature sgr = Signature.getInstance("ECDSA", "BC");

        sgr.initSign(pair.getPrivate());

        byte[] message = new byte[] { (byte)'a', (byte)'b', (byte)'c' };

        sgr.update(message);

        byte[]  sigBytes = sgr.sign();

        sgr.initVerify(pair.getPublic());

        sgr.update(message);

        if (!sgr.verify(sigBytes))
        {
            fail("EC verification failed");
        }

        if (!pair.getPublic().getAlgorithm().equals("ECDSA"))
        {
            fail("wrong algorithm name on public got: " + pair.getPublic().getAlgorithm());
        }

        if (!pair.getPrivate().getAlgorithm().equals("ECDSA"))
        {
            fail("wrong algorithm name on private");
        }

        //
        // ECKey -- explicit parameters
        //
        pemRd = openPEMResource("ecexpparam.pem");
        ecSpec = (X9ECParameters)pemRd.readObject();

        pemPair = (PEMKeyPair)pemRd.readObject();

        pair = new JcaPEMKeyConverter().setProvider("BC").getKeyPair(pemPair);

        sgr = Signature.getInstance("ECDSA", "BC");

        sgr.initSign(pair.getPrivate());

        message = new byte[] { (byte)'a', (byte)'b', (byte)'c' };

        sgr.update(message);

        sigBytes = sgr.sign();

        sgr.initVerify(pair.getPublic());

        sgr.update(message);

        if (!sgr.verify(sigBytes))
        {
            fail("EC verification failed");
        }

        if (!pair.getPublic().getAlgorithm().equals("ECDSA"))
        {
            fail("wrong algorithm name on public got: " + pair.getPublic().getAlgorithm());
        }

        if (!pair.getPrivate().getAlgorithm().equals("ECDSA"))
        {
            fail("wrong algorithm name on private");
        }

        //
        // writer/parser test
        //
        KeyPairGenerator      kpGen = KeyPairGenerator.getInstance("RSA", "BC");

        pair = kpGen.generateKeyPair();

        keyPairTest("RSA", pair);

        kpGen = KeyPairGenerator.getInstance("DSA", "BC");
        kpGen.initialize(512, new SecureRandom());
        pair = kpGen.generateKeyPair();

        keyPairTest("DSA", pair);

        //
        // PKCS7
        //
        ByteArrayOutputStream bOut = new ByteArrayOutputStream();
        PEMWriter             pWrt = new PEMWriter(new OutputStreamWriter(bOut));

        pWrt.writeObject(d);

        pWrt.close();

        pemRd = new PEMParser(new InputStreamReader(new ByteArrayInputStream(bOut.toByteArray())));
        d = (ContentInfo)pemRd.readObject();

        if (!d.getContentType().equals(CMSObjectIdentifiers.envelopedData))
        {
            fail("failed envelopedData recode check");
        }


        // OpenSSL test cases (as embedded resources)
        doOpenSslDsaTest("unencrypted");
        doOpenSslRsaTest("unencrypted");

        doOpenSslTests("aes128");
        doOpenSslTests("aes192");
        doOpenSslTests("aes256");
        doOpenSslTests("blowfish");
        doOpenSslTests("des1");
        doOpenSslTests("des2");
        doOpenSslTests("des3");
        doOpenSslTests("rc2_128");

        doOpenSslDsaTest("rc2_40_cbc");
        doOpenSslRsaTest("rc2_40_cbc");
        doOpenSslDsaTest("rc2_64_cbc");
        doOpenSslRsaTest("rc2_64_cbc");

        doDudPasswordTest("7fd98", 0, "corrupted stream - out of bounds length found");
        doDudPasswordTest("ef677", 1, "corrupted stream - out of bounds length found");
        doDudPasswordTest("800ce", 2, "unknown tag 26 encountered");
        doDudPasswordTest("b6cd8", 3, "DEF length 81 object truncated by 56");
        doDudPasswordTest("28ce09", 4, "DEF length 110 object truncated by 28");
        doDudPasswordTest("2ac3b9", 5, "DER length more than 4 bytes: 11");
        doDudPasswordTest("2cba96", 6, "DEF length 100 object truncated by 35");
        doDudPasswordTest("2e3354", 7, "DEF length 42 object truncated by 9");
        doDudPasswordTest("2f4142", 8, "DER length more than 4 bytes: 14");
        doDudPasswordTest("2fe9bb", 9, "DER length more than 4 bytes: 65");
        doDudPasswordTest("3ee7a8", 10, "DER length more than 4 bytes: 57");
        doDudPasswordTest("41af75", 11, "unknown tag 16 encountered");
        doDudPasswordTest("1704a5", 12, "corrupted stream detected");
        doDudPasswordTest("1c5822", 13, "unknown object in getInstance: org.bouncycastle.asn1.DERUTF8String");
        doDudPasswordTest("5a3d16", 14, "corrupted stream detected");
        doDudPasswordTest("8d0c97", 15, "corrupted stream detected");
        doDudPasswordTest("bc0daf", 16, "corrupted stream detected");
        doDudPasswordTest("aaf9c4d",17, "corrupted stream - out of bounds length found");

        doNoPasswordTest();

        // encrypted private key test
        InputDecryptorProvider pkcs8Prov = new JceOpenSSLPKCS8DecryptorProviderBuilder().build("password".toCharArray());
        pemRd = openPEMResource("enckey.pem");

        PKCS8EncryptedPrivateKeyInfo encPrivKeyInfo = (PKCS8EncryptedPrivateKeyInfo)pemRd.readObject();
        JcaPEMKeyConverter   converter = new JcaPEMKeyConverter().setProvider("BC");

        RSAPrivateCrtKey privKey = (RSAPrivateCrtKey)converter.getPrivateKey(encPrivKeyInfo.decryptPrivateKeyInfo(pkcs8Prov));

        if (!privKey.getPublicExponent().equals(new BigInteger("10001", 16)))
        {
            fail("decryption of private key data check failed");
        }

        // general PKCS8 test

        pemRd = openPEMResource("pkcs8test.pem");

        Object privInfo;

        while ((privInfo = pemRd.readObject()) != null)
        {
            if (privInfo instanceof PrivateKeyInfo)
            {
                privKey = (RSAPrivateCrtKey)converter.getPrivateKey(PrivateKeyInfo.getInstance(privInfo));
            }
            else
            {
                privKey = (RSAPrivateCrtKey)converter.getPrivateKey(((PKCS8EncryptedPrivateKeyInfo)privInfo).decryptPrivateKeyInfo(pkcs8Prov));
            }
            if (!privKey.getPublicExponent().equals(new BigInteger("10001", 16)))
            {
                fail("decryption of private key data check failed");
            }
        }
    }

    private void keyPairTest(
        String   name,
        KeyPair pair) 
        throws IOException
    {
        PEMParser pemRd;
        ByteArrayOutputStream bOut = new ByteArrayOutputStream();
        PEMWriter             pWrt = new PEMWriter(new OutputStreamWriter(bOut));

        pWrt.writeObject(pair.getPublic());

        pWrt.close();

        pemRd = new PEMParser(new InputStreamReader(new ByteArrayInputStream(bOut.toByteArray())));

        SubjectPublicKeyInfo pub = SubjectPublicKeyInfo.getInstance(pemRd.readObject());
        JcaPEMKeyConverter   converter = new JcaPEMKeyConverter().setProvider("BC");

        PublicKey k = converter.getPublicKey(pub);

        if (!k.equals(pair.getPublic()))
        {
            fail("Failed public key read: " + name);
        }

        bOut = new ByteArrayOutputStream();
        pWrt = new PEMWriter(new OutputStreamWriter(bOut));

        pWrt.writeObject(pair.getPrivate());

        pWrt.close();

        pemRd = new PEMParser(new InputStreamReader(new ByteArrayInputStream(bOut.toByteArray())));

        KeyPair kPair = converter.getKeyPair((PEMKeyPair)pemRd.readObject());
        if (!kPair.getPrivate().equals(pair.getPrivate()))
        {
            fail("Failed private key read: " + name);
        }

        if (!kPair.getPublic().equals(pair.getPublic()))
        {
            fail("Failed private key public read: " + name);
        }
    }

    private void doOpenSslTests(
        String baseName)
        throws IOException
    {
        doOpenSslDsaModesTest(baseName);
        doOpenSslRsaModesTest(baseName);
    }

    private void doOpenSslDsaModesTest(
        String baseName)
        throws IOException
    {
        doOpenSslDsaTest(baseName + "_cbc");
        doOpenSslDsaTest(baseName + "_cfb");
        doOpenSslDsaTest(baseName + "_ecb");
        doOpenSslDsaTest(baseName + "_ofb");
    }

    private void doOpenSslRsaModesTest(
        String baseName)
        throws IOException
    {
        doOpenSslRsaTest(baseName + "_cbc");
        doOpenSslRsaTest(baseName + "_cfb");
        doOpenSslRsaTest(baseName + "_ecb");
        doOpenSslRsaTest(baseName + "_ofb");
    }

    private void doOpenSslDsaTest(
        String name)
        throws IOException
    {
        String fileName = "dsa/openssl_dsa_" + name + ".pem";

        doOpenSslTestFile(fileName, DSAPrivateKey.class);
    }

    private void doOpenSslRsaTest(
        String name)
        throws IOException
    {
        String fileName = "rsa/openssl_rsa_" + name + ".pem";

        doOpenSslTestFile(fileName, RSAPrivateKey.class);
    }

    private void doOpenSslTestFile(
        String  fileName,
        Class   expectedPrivKeyClass)
        throws IOException
    {
        JcaPEMKeyConverter   converter = new JcaPEMKeyConverter().setProvider("BC");
        PEMDecryptorProvider decProv = new JcePEMDecryptorProviderBuilder().setProvider("BC").build("changeit".toCharArray());
        PEMParser pr = openPEMResource("data/" + fileName);
        Object o = pr.readObject();

        if (o == null || !((o instanceof PEMKeyPair) || (o instanceof PEMEncryptedKeyPair)))
        {
            fail("Didn't find OpenSSL key");
        }

        KeyPair kp = (o instanceof PEMEncryptedKeyPair) ?
            converter.getKeyPair(((PEMEncryptedKeyPair)o).decryptKeyPair(decProv)) : converter.getKeyPair((PEMKeyPair)o);

        PrivateKey privKey = kp.getPrivate();

        if (!expectedPrivKeyClass.isInstance(privKey))
        {
            fail("Returned key not of correct type");
        }
    }

    private void doDudPasswordTest(String password, int index, String message)
    {
        // illegal state exception check - in this case the wrong password will
        // cause an underlying class cast exception.
        try
        {
            PEMDecryptorProvider decProv = new JcePEMDecryptorProviderBuilder().setProvider("BC").build(password.toCharArray());

            PEMParser pemRd = openPEMResource("test.pem");
            Object o;

            while ((o = pemRd.readObject()) != null)
            {
                if (o instanceof PEMEncryptedKeyPair)
                {
                    ((PEMEncryptedKeyPair)o).decryptKeyPair(decProv);
                }
            }

            fail("issue not detected: " + index);
        }
        catch (IOException e)
        {
            if (e.getCause() != null && !e.getCause().getMessage().endsWith(message))
            {
               fail("issue " + index + " exception thrown, but wrong message");
            }
            else if (e.getCause() == null && !e.getMessage().equals(message))
            {
                               e.printStackTrace();
               fail("issue " + index + " exception thrown, but wrong message");
            }
        }
    }

    private void doNoPasswordTest()
        throws IOException
    {
        PEMDecryptorProvider decProv = new JcePEMDecryptorProviderBuilder().setProvider("BC").build("".toCharArray());

        PEMParser pemRd = openPEMResource("smimenopw.pem");
        Object o;
        PrivateKeyInfo key = null;

        while ((o = pemRd.readObject()) != null)
        {
             key = (PrivateKeyInfo)o;
        }

        if (key == null)
        {
            fail("private key not detected");
        }
    }

    public static void main(
        String[]    args)
    {
        Security.addProvider(new BouncyCastleProvider());

        runTest(new ParserTest());
    }
}

Код неправильно обробляє пароль. Пароль слід замінити на 0, коли закінчите з ним. Див., Наприклад, Використання шифрування на основі пароля в документах Java JCE Architecture.
jww

@jww, як добрий громадянин, чи ви порушували цю проблему з командою баусікасла?
Кріс Сноу,

9

Ну, мій код схожий на ваш, з невеликими відмінностями ...

public static X509Certificate loadPublicX509(String fileName) 
        throws GeneralSecurityException {
    InputStream is = null;
    X509Certificate crt = null;
    try {
        is = fileName.getClass().getResourceAsStream("/" + fileName);
        CertificateFactory cf = CertificateFactory.getInstance("X.509");
        crt = (X509Certificate)cf.generateCertificate(is);
    } finally {
        closeSilent(is);
    }
    return crt;
}

public static PrivateKey loadPrivateKey(String fileName) 
        throws IOException, GeneralSecurityException {
    PrivateKey key = null;
    InputStream is = null;
    try {
        is = fileName.getClass().getResourceAsStream("/" + fileName);
        BufferedReader br = new BufferedReader(new InputStreamReader(is));
        StringBuilder builder = new StringBuilder();
        boolean inKey = false;
        for (String line = br.readLine(); line != null; line = br.readLine()) {
            if (!inKey) {
                if (line.startsWith("-----BEGIN ") && 
                        line.endsWith(" PRIVATE KEY-----")) {
                    inKey = true;
                }
                continue;
            }
            else {
                if (line.startsWith("-----END ") && 
                        line.endsWith(" PRIVATE KEY-----")) {
                    inKey = false;
                    break;
                }
                builder.append(line);
            }
        }
        //
        byte[] encoded = DatatypeConverter.parseBase64Binary(builder.toString());
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encoded);
        KeyFactory kf = KeyFactory.getInstance("RSA");
        key = kf.generatePrivate(keySpec);
    } finally {
        closeSilent(is);
    }
    return key;
}

public static void closeSilent(final InputStream is) {
    if (is == null) return;
    try { is.close(); } catch (Exception ign) {}
}

1
Чи не могли б Ви вказати на відмінності / пояснити, чому Ваш краще?
Віндл

4
краще? більше схоже на (мало) різне :-) але ... завантаження загальнодоступного X.509 використовує менше коду. навантаження приватного ключа є трохи портативнішим (є такі ключі, як це вказується в заголовку "RSA", наприклад: "----- НАЧАТИ РСА ПРИВАТНИЙ КЛЮЧ -----") і не використовуйте "Base64" ( здаються зовнішніми lib); цей код використовує лише Jre6
ggrandes

2
-----BEGIN RSA PRIVATE KEY------ це інший формат, НЕ PKCS8, і намагатися прочитати його як PKCS8 не буде працювати. Також сертифікат - це інша річ, ніж необроблений публічний ключ, тому це взагалі не працюватиме для даних у цьому Q.
dave_thompson_085

@ dave_thompson_085 цей код витягнуто з робочої системи ... що є джерелом вашого підтвердження? ¿?
ggrandes

(0) Вибачте за затримку, я був зайнятий. (1) Я знаю, як працює openssl. (2) Тільки для вас я створив нові файли, використовуючи першу та третю команди в Q, плюс для повноти третю змінив на PEM. Як я і очікував, ваш код не працює з винятками для цих файлів, але чудово працює з різними файлами (ключ PKCS8-u, створений другою командою, і сертифікат, створений командами, повністю не використаними в цьому запитанні). Якщо ви можете показати (лише для тестування) файли, створені OpenSSL, із типом PEM „RSA PRIVATE KEY” та / або „PUBLIC KEY”, який читається за кодом, який ви опублікували, я заплачу 100 доларів.
dave_thompson_085

3

Я думаю, у визначенні Вашого приватного ключа Ви повинні замінити:

X509EncodedKeySpec spec = new X509EncodedKeySpec(decoded);

з:

PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(decoded);

Подивіться свою opensslкоманду:

$openssl **pkcs8** -topk8 -inform PEM -outform PEM -in mykey.pem \ -out private_key.pem -nocrypt

І виняток Java:

Only PCKS8 codification 

Смандолі - Я наслідував ваші пропозиції, але в обох напрямках це не працює. Будь ласка, зверніться до stackoverflow.com/questions/39311157/… . Я теж порушую цю проблему.
Pra_A

2

Java libs робить його майже одним вкладишем для читання загальнодоступного сертифіката, згенерованого openssl:

val certificate: X509Certificate = ByteArrayInputStream(
        publicKeyCert.toByteArray(Charsets.US_ASCII))
        .use {
            CertificateFactory.getInstance("X.509")
                    .generateCertificate(it) as X509Certificate
        }

Але, блін, читати закритий ключ було проблематично:

  1. Спочатку довелося видалити теги start і end, що не обов'язково при читанні відкритого ключа.
  2. Тоді мені довелося видалити всі нові рядки, інакше це каркає!
  3. Потім мені довелося декодувати назад до байтів, використовуючи байт 64
  4. Тоді я зміг виготовити RSAPrivateKey.

дивіться це: Остаточне рішення в kotlin


1

Щоб отримати відкритий ключ, ви можете просто зробити:

public static PublicKey getPublicKeyFromCertFile(final String certfile){

     return new X509CertImpl(new FileInputStream(new File(certfile))).getPublicKey();

Щоб отримати приватний ключ складніше, ви можете:

public static PrivateKey getPrivateKeyFromKeyFile(final String keyfile){
    try {
        Process p;
        p = Runtime.getRuntime().exec("openssl pkcs8 -nocrypt -topk8 -inform PEM " +
                "-in " + keyfile + " -outform DER -out " + keyfile + ".der");

        p.waitFor();
        System.out.println("Command executed" + (p.exitValue() == 0 ? " successfully" : " with error" ));
    } catch ( IOException | InterruptedException e) {
        e.printStackTrace();
        System.exit(1);
    }

    PrivateKey myPrivKey = null;
    try {
        byte[] keyArray = Files.readAllBytes(Paths.get(keyfile + ".der"));
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyArray);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        myPrivKey = keyFactory.generatePrivate(keySpec);
    } catch (IOException | NoSuchAlgorithmException | InvalidKeySpecException e){
        e.printStackTrace();
        System.exit(1);
    }

    return myPrivKey;
}

Нам потрібно встановити "openssl" в машині, щоб використовувати це?
Малінта

Так, я поставив такий спосіб зробити це, тому що у питанні openssl вже був використаний.
bestrocker221

1

Зчитування відкритого ключа з pem (PK або Cert). Залежить від Bouncycastle.

private static PublicKey getPublicKeyFromPEM(Reader reader) throws IOException {

    PublicKey key;

    try (PEMParser pem = new PEMParser(reader)) {
        JcaPEMKeyConverter jcaPEMKeyConverter = new JcaPEMKeyConverter();
        Object pemContent = pem.readObject();
        if (pemContent instanceof PEMKeyPair) {
            PEMKeyPair pemKeyPair = (PEMKeyPair) pemContent;
            KeyPair keyPair = jcaPEMKeyConverter.getKeyPair(pemKeyPair);
            key = keyPair.getPublic();
        } else if (pemContent instanceof SubjectPublicKeyInfo) {
            SubjectPublicKeyInfo keyInfo = (SubjectPublicKeyInfo) pemContent;
            key = jcaPEMKeyConverter.getPublicKey(keyInfo);
        } else if (pemContent instanceof X509CertificateHolder) {
            X509CertificateHolder cert = (X509CertificateHolder) pemContent;
            key = jcaPEMKeyConverter.getPublicKey(cert.getSubjectPublicKeyInfo());
        } else {
            throw new IllegalArgumentException("Unsupported public key format '" +
                pemContent.getClass().getSimpleName() + '"');
        }
    }

    return key;
}

Зчитування приватного ключа з PEM:

private static PrivateKey getPrivateKeyFromPEM(Reader reader) throws IOException {

    PrivateKey key;

    try (PEMParser pem = new PEMParser(reader)) {
        JcaPEMKeyConverter jcaPEMKeyConverter = new JcaPEMKeyConverter();
        Object pemContent = pem.readObject();
        if (pemContent instanceof PEMKeyPair) {
            PEMKeyPair pemKeyPair = (PEMKeyPair) pemContent;
            KeyPair keyPair = jcaPEMKeyConverter.getKeyPair(pemKeyPair);
            key = keyPair.getPrivate();
        } else if (pemContent instanceof PrivateKeyInfo) {
            PrivateKeyInfo privateKeyInfo = (PrivateKeyInfo) pemContent;
            key = jcaPEMKeyConverter.getPrivateKey(privateKeyInfo);
        } else {
            throw new IllegalArgumentException("Unsupported private key format '" +
                pemContent.getClass().getSimpleName() + '"');
        }
    }

    return key;
}

1

Java підтримує використання DER для загальнодоступних та приватних ключів (що в основному є однаковим з PEM, як вимагає OP, за винятком того, що файли PEM містять базові 64 дані, а також рядки верхнього та нижнього колонтитулів).

Ви можете покластися на цей код (обробка винятків за модулем) без будь-якої зовнішньої бібліотеки, якщо ви використовуєте Java 8+ (це передбачає, що ваші ключові файли доступні в шляху до класу):

class Signer {
    private KeyFactory keyFactory;

    public Signer() {
        this.keyFactory = KeyFactory.getInstance("RSA");
    }

    public PublicKey getPublicKey() {
        byte[] publicKey = readFileAsBytes("public-key.der");

        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKey);

        return keyFactory.generatePublic(keySpec);
    }

    public PrivateKey getPrivateKey() {
        byte[] privateKey = readFileAsBytes("private-key.der");

        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKey);
    
        return keyFactory.generatePrivate(keySpec);
    }

    private URI readFileAsBytes(String name) {
        URI fileUri = getClass().getClassLoader().getResource(name).toURI();

        return Files.readAllBytes(Paths.get(fileUri));
    }
}

Для запису ви можете перетворити ключ PEM у ключ DER за допомогою такої команди:

$ openssl pkcs8 -topk8 -inform PEM -outform DER -in private-key.pem -out private-key.der -nocrypt

І отримайте відкритий ключ у ПЕД за допомогою:

$ openssl rsa -in private-key.pem -pubout -outform DER -out public-key.der

0

Якщо PEM містить лише один приватний ключ RSA без шифрування, він повинен мати структуру послідовності ASN.1, що включає 9 чисел, щоб представити ключ китайської теореми залишків (ЕЛТ):

  1. версія (завжди 0)
  2. модуль (n)
  3. публічний показник (e, завжди 65537)
  4. приватний показник (d)
  5. основний р
  6. просте q
  7. d mod (p - 1) (dp)
  8. d mod (q - 1) (dq)
  9. q ^ -1 mod p (qinv)

Ми можемо реалізувати RSAPrivateCrtKey:

class RSAPrivateCrtKeyImpl implements RSAPrivateCrtKey {
    private static final long serialVersionUID = 1L;

    BigInteger n, e, d, p, q, dp, dq, qinv;

    @Override
    public BigInteger getModulus() {
        return n;
    }

    @Override
    public BigInteger getPublicExponent() {
        return e;
    }

    @Override
    public BigInteger getPrivateExponent() {
        return d;
    }

    @Override
    public BigInteger getPrimeP() {
        return p;
    }

    @Override
    public BigInteger getPrimeQ() {
        return q;
    }

    @Override
    public BigInteger getPrimeExponentP() {
        return dp;
    }

    @Override
    public BigInteger getPrimeExponentQ() {
        return dq;
    }

    @Override
    public BigInteger getCrtCoefficient() {
        return qinv;
    }

    @Override
    public String getAlgorithm() {
        return "RSA";
    }

    @Override
    public String getFormat() {
        throw new UnsupportedOperationException();
    }

    @Override
    public byte[] getEncoded() {
        throw new UnsupportedOperationException();
    }
}

Потім прочитайте закритий ключ із файлу PEM:

import sun.security.util.DerInputStream;
import sun.security.util.DerValue;

static RSAPrivateCrtKey getRSAPrivateKey(String keyFile) {
    RSAPrivateCrtKeyImpl prvKey = new RSAPrivateCrtKeyImpl();
    try (BufferedReader in = new BufferedReader(new FileReader(keyFile))) {
        StringBuilder sb = new StringBuilder();
        String line;
        while ((line = in.readLine()) != null) {
            // skip "-----BEGIN/END RSA PRIVATE KEY-----"
            if (!line.startsWith("--") || !line.endsWith("--")) {
                sb.append(line);
            }
        }
        DerInputStream der = new DerValue(Base64.
                getDecoder().decode(sb.toString())).getData();
        der.getBigInteger(); // 0
        prvKey.n = der.getBigInteger();
        prvKey.e = der.getBigInteger(); // 65537
        prvKey.d = der.getBigInteger();
        prvKey.p = der.getBigInteger();
        prvKey.q = der.getBigInteger();
        prvKey.dp = der.getBigInteger();
        prvKey.dq = der.getBigInteger();
        prvKey.qinv = der.getBigInteger();
    } catch (IllegalArgumentException | IOException e) {
        logger.warn(keyFile + ": " + e.getMessage());
        return null;
    }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.