如何读取 .pem 文件以获取私钥和​​公钥

Posted

技术标签:

【中文标题】如何读取 .pem 文件以获取私钥和​​公钥【英文标题】:How to read .pem file to get private and public key 【发布时间】:2012-08-01 00:30:20 【问题描述】:

我正在编写一小段代码来读取存储在 .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);
      

我觉得这是一种幼稚的做法。我无法通过互联网获得更好的方法。谁能建议我编写相同代码来处理通用案例的最佳方法是什么。我不想使用任何类型的第三方库。 我对唱歌/加密有非常基本的了解,几乎不使用任何 Java 安全 API。因此,如果我在某处没有意义,请指出。

【问题讨论】:

嗯...对我来说看起来不错。我认为 JCE 中没有更好的方法,它没有 PEM 处理功能。您已经回答了自己的问题,并为我们提供了很好的示例代码。 您应该将“getPemPublicKey”中的“privKeyPEM”更改为“pubKeyPEM”。 如何在不使用openssl -nocrypt 命令的情况下完成(或可以完成)。那部分也可以用Java完成吗? "openssl genrsa" 生成私钥,而不是密钥对? wiki.openssl.org/index.php/Manual:Genrsa(1) @iznt 链接已失效。 openssl.org/docs/man1.0.2/apps/genrsa.html 【参考方案1】:

嗯,我的代码和你的一样,差别不大……

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) 

【讨论】:

你能指出差异/解释为什么你的更好吗? 更好?更像(有点)不同:-) 但是......公共 X.509 的负载使用更少的代码。私钥的负载更便携(在标题“RSA”中指定了类似的密钥,例如:“-----BEGIN RSA PRIVATE KEY-----”)并且不要使用“Base64”(似乎是外部库);此代码仅使用 Jre6 -----BEGIN RSA PRIVATE KEY----- 是一种不同的格式,而不是 PKCS8,并且尝试将其读取为 PKCS8 将不起作用。此外,证书与原始公钥不同,因此这根本不适用于此 Q 中的数据。 @dave_thompson_085 此代码是从工作系统中提取的...您的确认来源是什么?¿? (0) 抱歉耽搁了,我很忙。 (1) 我知道 openssl 是如何工作的。 (2) 只是为了你,我使用 Q 中的第一个和第三个命令创建了新文件,为了完整起见,第三个更改为 PEM。正如我预期的那样,您的代码在这些文件上出现异常,但在 different 文件上工作正常(由 second 命令创建的 PKCS8-u 密钥,而由命令创建的证书完全不是在这个 Q 中使用)。如果您可以显示由 OpenSSL 创建的(仅测试)文件,其 PEM 类型为“RSA PRIVATE KEY”和/或“PUBLIC KEY”,您发布的代码可以读取这些文件,我将支付 100 美元。【参考方案2】:

一种选择是使用 bouncycastle 的PEMParser:

用于解析包含 X509 的 OpenSSL PEM 编码流的类 证书、PKCS8 编码密钥和 PKCS7 对象。

对于 PKCS7 对象,阅读器将返回 CMS ContentInfo 目的。公钥将按照格式返回 SubjectPublicKeyInfo 对象,也会返回私钥 形成 PrivateKeyInfo 对象。在私钥的情况下 如果编码同时包含 私钥和公钥定义。 CRL、证书、PKCS#10 请求,并且属性证书将生成适当的 BC 持有者类。

这里是一个使用Parser test code的例子:

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 架构文档中的 Using Password-Based Encryption。 @jww 作为一个好公民,您向 bouncycastle 团队提出了这个问题吗?【参考方案3】:

试试这个课程。

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"

【讨论】:

Only RSAPublicKeySpec and X509EncodedKeySpec supported for RSA public keys。切换到X509EncodedKeySpec,然后得到java.security.InvalidKeyException: IOException: DerInputStream.getLength(): lengthTag=111, too big.。这是由 AWS EC2 生成的 pem 文件 @Hooli 您需要使用RSAPublicKeySpec,因为您的公钥似乎是PKCS#1 格式而不是PKCS#8 格式。在后一种情况下,您需要使用X509EncodedKeySpec(而不是PKCS8EncodedKeySpec,因为目前的答案是)。您可以通过查看标题来区分两者,BEGIN RSA PUBLIC KEYBEGIN PUBLIC KEY 顺便说一句,我还没有找到以编程方式提取RSAPublicKeySpec 的构造函数所需的模数和公共指数的可能性。因此,使用openssl 或类似程序从PKCS#1 转换为PKCS#8 可能更容易...... 谢谢这确实有效。奇怪的是,Java 使读取公共证书很容易,但很难读取私钥。 这行得通,但是我必须将私钥转换为 pks8 格式:openssl pkcs8 -topk8 -inform PEM -outform PEM -nocrypt -in -out 【参考方案4】:

我认为在您的私钥定义中,您应该替换:

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 

【讨论】:

Smandoli - 我听从了你的建议,但两种方式都不起作用。请参考***.com/questions/39311157/…。我也提出了这个问题。【参考方案5】:

Java libs 几乎可以让读取由 openssl 生成的公共证书:

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

但是,天啊,读取私钥是有问题的:

    首先必须删除开始和结束标签,读取公钥时不需要这样做。 然后我必须删除所有新行,否则它会发出嘶哑的声音! 然后我必须使用字节 64 解码回字节 然后我可以生成一个RSAPrivateKey

看到这个:Final solution in kotlin

【讨论】:

【参考方案6】:

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

感谢 Daniel 注意到 API 兼容性问题

【讨论】:

令我惊讶的是,我们要么需要获取整个 bouncycastle 库,只是因为 java 无法手动读取开箱即用的 .pem 文件,要么我们需要这样做 :) 感谢解决方案虽然【参考方案7】:

要获取公钥,您只需执行以下操作:

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已经被使用了。【参考方案8】:

如果一个PEM只包含一个没有加密的RSA私钥,它必须是一个包含9个数字的ASN.1序列结构才能呈现一个中国剩余定理(CRT)密钥:

    版本(始终为 0) 模数 (n) 公共指数(e,始终为 65537) 私有指数 (d) 素数 素数 d mod (p - 1) (dp) d mod (q - 1) (dq) 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;
    

【讨论】:

可以为 PublicKey 修改相同的代码吗?上面的代码是否经过测试?尝试部分代码似乎没有返回任何值... RSAPublicKeyImpl(实现RSAPublicKey)是RSAPrivateCrtKeyImpl的一部分:getModulus返回ngetPublicExponent返回e 只需使用Cipher.getInstance("RSA")Signature.getInstance("RSA...") 加载RSAPublicKeyRSAPrivateCrtKey,JCE 将使用这些参数进行加密/解密/签名/验证。【参考方案9】:

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

【讨论】:

【参考方案10】:

Java 支持将 DER 用于开箱即用的公钥和私钥(这与 OP 要求的 PEM 基本相同,除了 PEM 文件包含 base 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

并通过以下方式获取 DER 中的公钥:

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

【讨论】:

感谢您为我节省了数小时的烦恼 - 如果采用 DER 形式,所有示例都有效

以上是关于如何读取 .pem 文件以获取私钥和​​公钥的主要内容,如果未能解决你的问题,请参考以下文章

在文件中读取私钥和公钥

微信V3支付通过p12获取pem文件

sh 将密钥从二进制(由生成密钥生成)转换为pem格式。适用于私钥和公钥

windows 下OPENSSL 生成秘钥和公钥的方法

android rsa加解密私钥和公钥怎么用

获取 X509Certificate2 私钥和公钥