【问题标题】:How to read .pem file to get private and public key如何读取 .pem 文件以获取私钥和​​公钥
【发布时间】: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)

标签: java openssl x509 pem pkcs#8


【解决方案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 

【讨论】:

【解决方案5】:

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

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

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

  1. 首先必须删除开始和结束标签,读取公钥时不需要这样做。
  2. 然后我必须删除所有新行,否则它会发出嘶哑的声音!
  3. 然后我必须使用字节 64 解码回字节
  4. 然后我可以生成一个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)密钥:

    1. 版本(始终为 0)
    2. 模数 (n)
    3. 公共指数(e,始终为 65537)
    4. 私有指数 (d)
    5. 素数
    6. 素数
    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;
        }
    }
    

    【讨论】:

    • 可以为 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 形式,所有示例都有效
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-11-18
      • 1970-01-01
      • 2017-09-20
      • 1970-01-01
      • 2017-04-02
      • 2010-09-19
      相关资源
      最近更新 更多