Cách đọc tệp .pem để lấy khóa riêng tư và khóa công khai


81

Tôi đang viết một đoạn mã nhỏ đọc khóa công khai và riêng tư được lưu trữ trong tệp .pem. Tôi đang sử dụng các lệnh sau để tạo các khóa.

Lệnh dưới đây để tạo cặp khóa.

   $openssl genrsa -out mykey.pem 2048

Lệnh này để tạo khóa riêng tư

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

và lệnh này để lấy khóa công khai.

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

Tôi đã viết hai phương thức đọc khóa cá nhân và khóa công khai tương ứng.

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

Tôi cảm thấy đây là một cách làm khá ngây thơ. Tôi không thể tìm được cách nào tốt hơn để làm điều đó qua internet. Bất cứ ai có thể gợi ý cho tôi cách tốt nhất để viết cùng một mã để xử lý các trường hợp chung chung. Tôi không muốn sử dụng bất kỳ loại thư viện nào của bên thứ ba.
Tôi có kiến ​​thức rất cơ bản về hát / mã hóa và hầu như không sử dụng bất kỳ API bảo mật java nào. Vì vậy, nếu tôi không có ý nghĩa ở đâu đó thì xin vui lòng chỉ ra.


2
Hmmm ... Trông tôi khá ổn. Tôi không nghĩ rằng có một cách tốt hơn trong JCE, không có chức năng xử lý PEM. Bạn đã trả lời câu hỏi của chính mình và cung cấp cho chúng tôi mã ví dụ tốt.
Eli Rosencruft

2
Bạn có thể nên thay đổi "privKeyPEM" trong "getPemPublicKey" thành "pubKeyPEM".
Eli Rosencruft

Làm thế nào điều này sẽ được thực hiện (hoặc nó có thể được thực hiện) mà không cần phải sử dụng openssl -nocryptlệnh. Phần đó cũng có thể được thực hiện trong Java?
David Blevins

1
"openssl genrsa" tạo khóa riêng, thay vì cặp khóa? wiki.openssl.org/index.php/Manual:Genrsa(1)
lznt

1

Câu trả lời:


39

Hãy thử lớp học này.

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. Đã chuyển sang X509EncodedKeySpecvà sau đó nhận được java.security.InvalidKeyException: IOException: DerInputStream.getLength(): lengthTag=111, too big.. Đây là tệp pem được tạo bởi AWS EC2
Hooli

1
@Hooli bạn cần sử dụng RSAPublicKeySpecvì khóa công khai của bạn dường như có PKCS#1định dạng và không có PKCS#8định dạng. Trong trường hợp thứ hai, bạn cần sử dụng X509EncodedKeySpec(và không phải PKCS8EncodedKeySpecnhư câu trả lời hiện tại đã nêu). Bạn có thể phân biệt giữa hai bằng cách lấy một cái nhìn tại các tiêu đề, BEGIN RSA PUBLIC KEYvs BEGIN PUBLIC KEY.
jansohn

BTW, tôi không tìm thấy khả năng trích xuất theo chương trình mô đun và số mũ công khai cần thiết cho hàm tạo của RSAPublicKeySpec. Vì vậy, nó có thể dễ dàng hơn để chỉ chuyển đổi từ PKCS#1tới PKCS#8với opensslhoặc chương trình tương tự ...
jansohn

Thanx điều này thực sự hoạt động. Điều kỳ lạ là Java giúp bạn dễ dàng đọc chứng chỉ công khai, nhưng khó đọc khóa riêng tư.
Andries

Điều này hoạt động, tuy nhiên tôi phải chuyển đổi khóa cá nhân sang định dạng pks8: openssl pkcs8 -topk8 -inform PEM -outform PEM -nocrypt -in <file-name> -out <file-name>
Deian

18

Một tùy chọn là sử dụng PEMParser của bouncycastle :

Lớp phân tích cú pháp các luồng được mã hóa OpenSSL PEM chứa chứng chỉ X509, khóa được mã hóa PKCS8 và đối tượng PKCS7.

Trong trường hợp các đối tượng PKCS7, trình đọc sẽ trả về một đối tượng CMS ContentInfo. Các khóa công khai sẽ được trả lại cũng như các đối tượng SubjectPublicKeyInfo đã được tạo thành, các khóa riêng sẽ được trả lại cũng như các đối tượng PrivateKeyInfo đã được tạo thành. Trong trường hợp khóa cá nhân, PEMKeyPair thường sẽ được trả về nếu mã hóa chứa cả định nghĩa khóa riêng tư và khóa công khai. Các yêu cầu CRL, Chứng chỉ, PKCS # 10 và Chứng chỉ thuộc tính sẽ tạo ra loại chủ sở hữu BC thích hợp.

Dưới đây là một ví dụ về việc sử dụng mã kiểm tra Trình phân tích cú pháp :

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

Mã không xử lý mật khẩu đúng cách. Mật khẩu phải được ghi đè bằng số 0 khi hoàn tất. Ví dụ, hãy xem Sử dụng Mã hóa Dựa trên Mật khẩu trong tài liệu Kiến trúc Java JCE.
jww

@jww với tư cách là một công dân tốt, bạn đã nêu vấn đề với nhóm bouncycastle chưa?
Chris Snow

17

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:

thay thế in.readAllBytes()cuộc gọi bằng một cuộc gọi tới:

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

cảm ơn Daniel vì đã nhận thấy các vấn đề tương thích với API


2
nó làm tôi ngạc nhiên rằng chúng tôi hoặc cần phải lấy toàn bộ lib BouncyCastle chỉ vì java không thể tự đọc .pemtập tin ra khỏi hộp, hoặc chúng ta cần phải làm điều này :) Cảm ơn các giải pháp mặc dù
Deniss M.

10

Chà, mã của tôi giống như mã của bạn, với một chút sai lệch ...

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
Bạn có thể chỉ ra sự khác biệt / giải thích tại sao của bạn tốt hơn?
Windle

4
tốt hơn? giống hơn (ít) khác nhau :-) nhưng ... tải của X.509 công khai sử dụng mã ít hơn. tải của khóa riêng tư ít di động hơn (có những khóa như thế chỉ định trong tiêu đề "RSA", như: "----- BEGIN RSA PRIVATE KEY -----") và không sử dụng "Base64" ( dường như lib bên ngoài); mã này chỉ sử dụng Jre6
ggrandes

2
-----BEGIN RSA PRIVATE KEY-----là một định dạng khác, KHÔNG PHẢI PKCS8 và cố gắng đọc nó như PKCS8 sẽ không hoạt động. Ngoài ra, chứng chỉ là một thứ khác với khóa công khai thô, vì vậy điều này sẽ không hoạt động đối với dữ liệu trong Q này.
dave_thompson_085

@ dave_thompson_085 mã này được lấy từ một hệ thống đang hoạt động ... nguồn khẳng định của bạn là gì? ¿?
ggrandes 22/1017

(0) Xin lỗi vì đã chậm trễ, tôi đang bận. (1) Tôi biết cách hoạt động của openssl. (2) Chỉ dành cho bạn, tôi đã tạo tệp mới bằng cách sử dụng lệnh đầu tiên và lệnh thứ ba trong Q, cộng với sự hoàn chỉnh, lệnh thứ ba được thay đổi thành PEM. Như tôi mong đợi, mã của bạn không thành công với các ngoại lệ trên các tệp này, nhưng hoạt động tốt trên các tệp khác nhau (khóa PKCS8-u được tạo bởi lệnh thứ hai và chứng chỉ được tạo bởi các lệnh hoàn toàn không được sử dụng trong Q này). Nếu bạn có thể hiển thị các tệp (chỉ thử nghiệm) được tạo bởi OpenSSL với loại PEM 'RSA PRIVATE KEY' và / hoặc 'PUBLIC KEY' có thể đọc được bằng mã bạn đã đăng, tôi sẽ trả $ 100.
dave_thompson_085

3

Tôi nghĩ trong định nghĩa khóa riêng tư của bạn, Bạn nên thay thế:

X509EncodedKeySpec spec = new X509EncodedKeySpec(decoded);

với:

PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(decoded);

Xem openssllệnh của bạn :

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

Và ngoại lệ java:

Only PCKS8 codification 

Smandoli - Tôi đã làm theo đề xuất của bạn nhưng cả hai cách đều không hoạt động. Vui lòng tham khảo stackoverflow.com/questions/39311157/… . Tôi cũng nêu vấn đề.
Pra_A

2

Java libs làm cho nó gần như là một lớp lót để đọc chứng chỉ công khai, như được tạo bởi openssl:

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

Nhưng, o quái, việc đọc khóa cá nhân có vấn đề:

  1. Đầu tiên phải loại bỏ các thẻ bắt đầu và kết thúc, điều này không phức tạp khi đọc khóa công khai.
  2. Sau đó, tôi phải loại bỏ tất cả các dòng mới, nếu không nó sẽ cong!
  3. Sau đó, tôi phải giải mã lại thành từng byte bằng cách sử dụng byte 64
  4. Sau đó, tôi đã có thể tạo ra một RSAPrivateKey.

xem điều này: Giải pháp cuối cùng trong kotlin


1

Để lấy khóa công khai, bạn chỉ cần làm:

public static PublicKey getPublicKeyFromCertFile(final String certfile){

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

Để lấy khóa cá nhân khó hơn, bạn có thể:

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

Chúng ta cần cài đặt "openssl" trong máy để sử dụng?
Malintha

Vâng, tôi đặt cách làm này vì trong câu hỏi, openssl đã được sử dụng.
bestrocker221

1

Đọc khóa công khai từ pem (PK hoặc Cert). Phụ thuộc vào 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;
}

Đọc khóa cá nhân từ 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 hỗ trợ sử dụng DER cho các khóa công khai và riêng tư ngoài hộp (về cơ bản giống như PEM, như OP yêu cầu, ngoại trừ tệp PEM chứa dữ liệu cơ sở 64 cộng với các dòng đầu trang và chân trang).

Bạn có thể dựa vào mã này (xử lý ngoại lệ modulo) mà không cần bất kỳ thư viện bên ngoài nào nếu bạn đang sử dụng Java 8+ (điều này giả sử các tệp khóa của bạn có sẵn trong classpath):

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

Đối với bản ghi, bạn có thể chuyển đổi khóa PEM thành khóa DER bằng lệnh sau:

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

Và nhận khóa công khai trong DER với:

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

0

Nếu PEM chỉ chứa một khóa riêng RSA không có mã hóa, thì nó phải là cấu trúc chuỗi ASN.1 bao gồm 9 số để trình bày khóa Định lý Phần dư Trung Quốc (CRT):

  1. phiên bản (luôn là 0)
  2. modulus (n)
  3. số mũ công khai (e, luôn là 65537)
  4. số mũ riêng (d)
  5. nguyên tố p
  6. nguyên tố q
  7. d mod (p - 1) (dp)
  8. d mod (q - 1) (dq)
  9. q ^ -1 mod p (qinv)

Chúng tôi có thể thực hiện một 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();
    }
}

Sau đó, đọc khóa cá nhân từ tệp 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;
    }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.