Как прочитать файл.pem, чтобы получить закрытый и открытый ключ
Я пишу небольшой фрагмент кода, который читает открытый и закрытый ключи, хранящиеся в файле.pem. Я использую следующие команды для генерации ключей.
Ниже команда для генерации пары ключей.
$openssl genrsa -out mykey.pem 2048
Эта команда для генерации закрытого ключа
$openssl pkcs8 -topk8 -inform PEM -outform PEM -in mykey.pem \
-out private_key.pem -nocrypt
и эта команда, чтобы получить открытый ключ.
$ openssl rsa -in mykey.pem -pubout -outform DER -out public_key.der
Я написал два метода, которые считывают закрытый ключ и открытый ключ соответственно.
public PrivateKey getPemPrivateKey(String filename, String algorithm) throws Exception {
File f = new File(filename);
FileInputStream fis = new FileInputStream(f);
DataInputStream dis = new DataInputStream(fis);
byte[] keyBytes = new byte[(int) f.length()];
dis.readFully(keyBytes);
dis.close();
String temp = new String(keyBytes);
String privKeyPEM = temp.replace("-----BEGIN PRIVATE KEY-----\n", "");
privKeyPEM = privKeyPEM.replace("-----END PRIVATE KEY-----", "");
//System.out.println("Private key\n"+privKeyPEM);
Base64 b64 = new Base64();
byte [] decoded = b64.decode(privKeyPEM);
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(decoded);
KeyFactory kf = KeyFactory.getInstance(algorithm);
return kf.generatePrivate(spec);
}
public PublicKey getPemPublicKey(String filename, String algorithm) throws Exception {
File f = new File(filename);
FileInputStream fis = new FileInputStream(f);
DataInputStream dis = new DataInputStream(fis);
byte[] keyBytes = new byte[(int) f.length()];
dis.readFully(keyBytes);
dis.close();
String temp = new String(keyBytes);
String publicKeyPEM = temp.replace("-----BEGIN PUBLIC KEY-----\n", "");
publicKeyPEM = publicKeyPEM.replace("-----END PUBLIC KEY-----", "");
Base64 b64 = new Base64();
byte [] decoded = b64.decode(publicKeyPEM);
X509EncodedKeySpec spec =
new X509EncodedKeySpec(decoded);
KeyFactory kf = KeyFactory.getInstance(algorithm);
return kf.generatePublic(spec);
}
Я чувствую, что это наивный способ сделать это. Я не мог найти лучшего способа сделать это через Интернет. Может кто-нибудь предложить мне, каков наилучший способ написания того же кода для обработки общих случаев. Я не хочу использовать какую-либо стороннюю библиотеку.
Я обладаю базовыми знаниями в области пения / шифрования и почти не использую API-интерфейсы безопасности Java. Так что, если я не вижу смысла где-то, пожалуйста, укажите.
11 ответов
Попробуйте этот класс.
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"
Java 9+:
private byte[] loadPEM(String resource) throws IOException {
URL url = getClass().getResource(resource);
InputStream in = url.openStream();
String pem = new String(in.readAllBytes(), ISO_8859_1);
Pattern parse = Pattern.compile("(?m)(?s)^---*BEGIN.*---*$(.*)^---*END.*---*$.*");
String encoded = parse.matcher(pem).replaceFirst("$1");
return Base64.getMimeDecoder().decode(encoded);
}
@Test
public void test() throws Exception {
KeyFactory kf = KeyFactory.getInstance("RSA");
CertificateFactory cf = CertificateFactory.getInstance("X.509");
PrivateKey key = kf.generatePrivate(new PKCS8EncodedKeySpec(loadPEM("test.key")));
PublicKey pub = kf.generatePublic(new X509EncodedKeySpec(loadPEM("test.pub")));
Certificate crt = cf.generateCertificate(getClass().getResourceAsStream("test.crt"));
}
Java 8:
заменить in.readAllBytes()
позвонить с призывом к этому:
byte[] readAllBytes(InputStream in) throws IOException {
ByteArrayOutputStream baos= new ByteArrayOutputStream();
byte[] buf = new byte[1024];
for (int read=0; read != -1; read = in.read(buf)) { baos.write(buf, 0, read); }
return baos.toByteArray();
}
спасибо Дэниелу за то, что он заметил проблемы совместимости API
Один из вариантов - использовать PEMParser от bouncycastle:
Класс для анализа потоков, закодированных в формате OpenSSL PEM, содержащих сертификаты X509, ключи, закодированные PKCS8, и объекты PKCS7.
В случае объектов PKCS7 читатель вернет объект CMS ContentInfo. Открытые ключи будут возвращены как хорошо сформированные объекты SubjectPublicKeyInfo, закрытые ключи будут возвращены как хорошо сформированные объекты PrivateKeyInfo. В случае закрытого ключа PEMKeyPair обычно возвращается, если кодировка содержит определение как закрытого, так и открытого ключа. CRL, сертификаты, запросы PKCS#10 и сертификаты атрибутов будут генерировать соответствующий класс держателей BC.
Вот пример использования тестового кода Parser:
package org.bouncycastle.openssl.test;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.math.BigInteger;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Security;
import java.security.Signature;
import java.security.interfaces.DSAPrivateKey;
import java.security.interfaces.RSAPrivateCrtKey;
import java.security.interfaces.RSAPrivateKey;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.cms.CMSObjectIdentifiers;
import org.bouncycastle.asn1.cms.ContentInfo;
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.asn1.x9.ECNamedCurveTable;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openssl.PEMDecryptorProvider;
import org.bouncycastle.openssl.PEMEncryptedKeyPair;
import org.bouncycastle.openssl.PEMKeyPair;
import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.openssl.PEMWriter;
import org.bouncycastle.openssl.PasswordFinder;
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
import org.bouncycastle.openssl.jcajce.JceOpenSSLPKCS8DecryptorProviderBuilder;
import org.bouncycastle.openssl.jcajce.JcePEMDecryptorProviderBuilder;
import org.bouncycastle.operator.InputDecryptorProvider;
import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo;
import org.bouncycastle.util.test.SimpleTest;
/**
* basic class for reading test.pem - the password is "secret"
*/
public class ParserTest
extends SimpleTest
{
private static class Password
implements PasswordFinder
{
char[] password;
Password(
char[] word)
{
this.password = word;
}
public char[] getPassword()
{
return password;
}
}
public String getName()
{
return "PEMParserTest";
}
private PEMParser openPEMResource(
String fileName)
{
InputStream res = this.getClass().getResourceAsStream(fileName);
Reader fRd = new BufferedReader(new InputStreamReader(res));
return new PEMParser(fRd);
}
public void performTest()
throws Exception
{
PEMParser pemRd = openPEMResource("test.pem");
Object o;
PEMKeyPair pemPair;
KeyPair pair;
while ((o = pemRd.readObject()) != null)
{
if (o instanceof KeyPair)
{
//pair = (KeyPair)o;
//System.out.println(pair.getPublic());
//System.out.println(pair.getPrivate());
}
else
{
//System.out.println(o.toString());
}
}
// test bogus lines before begin are ignored.
pemRd = openPEMResource("extratest.pem");
while ((o = pemRd.readObject()) != null)
{
if (!(o instanceof X509CertificateHolder))
{
fail("wrong object found");
}
}
//
// pkcs 7 data
//
pemRd = openPEMResource("pkcs7.pem");
ContentInfo d = (ContentInfo)pemRd.readObject();
if (!d.getContentType().equals(CMSObjectIdentifiers.envelopedData))
{
fail("failed envelopedData check");
}
//
// ECKey
//
pemRd = openPEMResource("eckey.pem");
ASN1ObjectIdentifier ecOID = (ASN1ObjectIdentifier)pemRd.readObject();
X9ECParameters ecSpec = ECNamedCurveTable.getByOID(ecOID);
if (ecSpec == null)
{
fail("ecSpec not found for named curve");
}
pemPair = (PEMKeyPair)pemRd.readObject();
pair = new JcaPEMKeyConverter().setProvider("BC").getKeyPair(pemPair);
Signature sgr = Signature.getInstance("ECDSA", "BC");
sgr.initSign(pair.getPrivate());
byte[] message = new byte[] { (byte)'a', (byte)'b', (byte)'c' };
sgr.update(message);
byte[] sigBytes = sgr.sign();
sgr.initVerify(pair.getPublic());
sgr.update(message);
if (!sgr.verify(sigBytes))
{
fail("EC verification failed");
}
if (!pair.getPublic().getAlgorithm().equals("ECDSA"))
{
fail("wrong algorithm name on public got: " + pair.getPublic().getAlgorithm());
}
if (!pair.getPrivate().getAlgorithm().equals("ECDSA"))
{
fail("wrong algorithm name on private");
}
//
// ECKey -- explicit parameters
//
pemRd = openPEMResource("ecexpparam.pem");
ecSpec = (X9ECParameters)pemRd.readObject();
pemPair = (PEMKeyPair)pemRd.readObject();
pair = new JcaPEMKeyConverter().setProvider("BC").getKeyPair(pemPair);
sgr = Signature.getInstance("ECDSA", "BC");
sgr.initSign(pair.getPrivate());
message = new byte[] { (byte)'a', (byte)'b', (byte)'c' };
sgr.update(message);
sigBytes = sgr.sign();
sgr.initVerify(pair.getPublic());
sgr.update(message);
if (!sgr.verify(sigBytes))
{
fail("EC verification failed");
}
if (!pair.getPublic().getAlgorithm().equals("ECDSA"))
{
fail("wrong algorithm name on public got: " + pair.getPublic().getAlgorithm());
}
if (!pair.getPrivate().getAlgorithm().equals("ECDSA"))
{
fail("wrong algorithm name on private");
}
//
// writer/parser test
//
KeyPairGenerator kpGen = KeyPairGenerator.getInstance("RSA", "BC");
pair = kpGen.generateKeyPair();
keyPairTest("RSA", pair);
kpGen = KeyPairGenerator.getInstance("DSA", "BC");
kpGen.initialize(512, new SecureRandom());
pair = kpGen.generateKeyPair();
keyPairTest("DSA", pair);
//
// PKCS7
//
ByteArrayOutputStream bOut = new ByteArrayOutputStream();
PEMWriter pWrt = new PEMWriter(new OutputStreamWriter(bOut));
pWrt.writeObject(d);
pWrt.close();
pemRd = new PEMParser(new InputStreamReader(new ByteArrayInputStream(bOut.toByteArray())));
d = (ContentInfo)pemRd.readObject();
if (!d.getContentType().equals(CMSObjectIdentifiers.envelopedData))
{
fail("failed envelopedData recode check");
}
// OpenSSL test cases (as embedded resources)
doOpenSslDsaTest("unencrypted");
doOpenSslRsaTest("unencrypted");
doOpenSslTests("aes128");
doOpenSslTests("aes192");
doOpenSslTests("aes256");
doOpenSslTests("blowfish");
doOpenSslTests("des1");
doOpenSslTests("des2");
doOpenSslTests("des3");
doOpenSslTests("rc2_128");
doOpenSslDsaTest("rc2_40_cbc");
doOpenSslRsaTest("rc2_40_cbc");
doOpenSslDsaTest("rc2_64_cbc");
doOpenSslRsaTest("rc2_64_cbc");
doDudPasswordTest("7fd98", 0, "corrupted stream - out of bounds length found");
doDudPasswordTest("ef677", 1, "corrupted stream - out of bounds length found");
doDudPasswordTest("800ce", 2, "unknown tag 26 encountered");
doDudPasswordTest("b6cd8", 3, "DEF length 81 object truncated by 56");
doDudPasswordTest("28ce09", 4, "DEF length 110 object truncated by 28");
doDudPasswordTest("2ac3b9", 5, "DER length more than 4 bytes: 11");
doDudPasswordTest("2cba96", 6, "DEF length 100 object truncated by 35");
doDudPasswordTest("2e3354", 7, "DEF length 42 object truncated by 9");
doDudPasswordTest("2f4142", 8, "DER length more than 4 bytes: 14");
doDudPasswordTest("2fe9bb", 9, "DER length more than 4 bytes: 65");
doDudPasswordTest("3ee7a8", 10, "DER length more than 4 bytes: 57");
doDudPasswordTest("41af75", 11, "unknown tag 16 encountered");
doDudPasswordTest("1704a5", 12, "corrupted stream detected");
doDudPasswordTest("1c5822", 13, "unknown object in getInstance: org.bouncycastle.asn1.DERUTF8String");
doDudPasswordTest("5a3d16", 14, "corrupted stream detected");
doDudPasswordTest("8d0c97", 15, "corrupted stream detected");
doDudPasswordTest("bc0daf", 16, "corrupted stream detected");
doDudPasswordTest("aaf9c4d",17, "corrupted stream - out of bounds length found");
doNoPasswordTest();
// encrypted private key test
InputDecryptorProvider pkcs8Prov = new JceOpenSSLPKCS8DecryptorProviderBuilder().build("password".toCharArray());
pemRd = openPEMResource("enckey.pem");
PKCS8EncryptedPrivateKeyInfo encPrivKeyInfo = (PKCS8EncryptedPrivateKeyInfo)pemRd.readObject();
JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider("BC");
RSAPrivateCrtKey privKey = (RSAPrivateCrtKey)converter.getPrivateKey(encPrivKeyInfo.decryptPrivateKeyInfo(pkcs8Prov));
if (!privKey.getPublicExponent().equals(new BigInteger("10001", 16)))
{
fail("decryption of private key data check failed");
}
// general PKCS8 test
pemRd = openPEMResource("pkcs8test.pem");
Object privInfo;
while ((privInfo = pemRd.readObject()) != null)
{
if (privInfo instanceof PrivateKeyInfo)
{
privKey = (RSAPrivateCrtKey)converter.getPrivateKey(PrivateKeyInfo.getInstance(privInfo));
}
else
{
privKey = (RSAPrivateCrtKey)converter.getPrivateKey(((PKCS8EncryptedPrivateKeyInfo)privInfo).decryptPrivateKeyInfo(pkcs8Prov));
}
if (!privKey.getPublicExponent().equals(new BigInteger("10001", 16)))
{
fail("decryption of private key data check failed");
}
}
}
private void keyPairTest(
String name,
KeyPair pair)
throws IOException
{
PEMParser pemRd;
ByteArrayOutputStream bOut = new ByteArrayOutputStream();
PEMWriter pWrt = new PEMWriter(new OutputStreamWriter(bOut));
pWrt.writeObject(pair.getPublic());
pWrt.close();
pemRd = new PEMParser(new InputStreamReader(new ByteArrayInputStream(bOut.toByteArray())));
SubjectPublicKeyInfo pub = SubjectPublicKeyInfo.getInstance(pemRd.readObject());
JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider("BC");
PublicKey k = converter.getPublicKey(pub);
if (!k.equals(pair.getPublic()))
{
fail("Failed public key read: " + name);
}
bOut = new ByteArrayOutputStream();
pWrt = new PEMWriter(new OutputStreamWriter(bOut));
pWrt.writeObject(pair.getPrivate());
pWrt.close();
pemRd = new PEMParser(new InputStreamReader(new ByteArrayInputStream(bOut.toByteArray())));
KeyPair kPair = converter.getKeyPair((PEMKeyPair)pemRd.readObject());
if (!kPair.getPrivate().equals(pair.getPrivate()))
{
fail("Failed private key read: " + name);
}
if (!kPair.getPublic().equals(pair.getPublic()))
{
fail("Failed private key public read: " + name);
}
}
private void doOpenSslTests(
String baseName)
throws IOException
{
doOpenSslDsaModesTest(baseName);
doOpenSslRsaModesTest(baseName);
}
private void doOpenSslDsaModesTest(
String baseName)
throws IOException
{
doOpenSslDsaTest(baseName + "_cbc");
doOpenSslDsaTest(baseName + "_cfb");
doOpenSslDsaTest(baseName + "_ecb");
doOpenSslDsaTest(baseName + "_ofb");
}
private void doOpenSslRsaModesTest(
String baseName)
throws IOException
{
doOpenSslRsaTest(baseName + "_cbc");
doOpenSslRsaTest(baseName + "_cfb");
doOpenSslRsaTest(baseName + "_ecb");
doOpenSslRsaTest(baseName + "_ofb");
}
private void doOpenSslDsaTest(
String name)
throws IOException
{
String fileName = "dsa/openssl_dsa_" + name + ".pem";
doOpenSslTestFile(fileName, DSAPrivateKey.class);
}
private void doOpenSslRsaTest(
String name)
throws IOException
{
String fileName = "rsa/openssl_rsa_" + name + ".pem";
doOpenSslTestFile(fileName, RSAPrivateKey.class);
}
private void doOpenSslTestFile(
String fileName,
Class expectedPrivKeyClass)
throws IOException
{
JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider("BC");
PEMDecryptorProvider decProv = new JcePEMDecryptorProviderBuilder().setProvider("BC").build("changeit".toCharArray());
PEMParser pr = openPEMResource("data/" + fileName);
Object o = pr.readObject();
if (o == null || !((o instanceof PEMKeyPair) || (o instanceof PEMEncryptedKeyPair)))
{
fail("Didn't find OpenSSL key");
}
KeyPair kp = (o instanceof PEMEncryptedKeyPair) ?
converter.getKeyPair(((PEMEncryptedKeyPair)o).decryptKeyPair(decProv)) : converter.getKeyPair((PEMKeyPair)o);
PrivateKey privKey = kp.getPrivate();
if (!expectedPrivKeyClass.isInstance(privKey))
{
fail("Returned key not of correct type");
}
}
private void doDudPasswordTest(String password, int index, String message)
{
// illegal state exception check - in this case the wrong password will
// cause an underlying class cast exception.
try
{
PEMDecryptorProvider decProv = new JcePEMDecryptorProviderBuilder().setProvider("BC").build(password.toCharArray());
PEMParser pemRd = openPEMResource("test.pem");
Object o;
while ((o = pemRd.readObject()) != null)
{
if (o instanceof PEMEncryptedKeyPair)
{
((PEMEncryptedKeyPair)o).decryptKeyPair(decProv);
}
}
fail("issue not detected: " + index);
}
catch (IOException e)
{
if (e.getCause() != null && !e.getCause().getMessage().endsWith(message))
{
fail("issue " + index + " exception thrown, but wrong message");
}
else if (e.getCause() == null && !e.getMessage().equals(message))
{
e.printStackTrace();
fail("issue " + index + " exception thrown, but wrong message");
}
}
}
private void doNoPasswordTest()
throws IOException
{
PEMDecryptorProvider decProv = new JcePEMDecryptorProviderBuilder().setProvider("BC").build("".toCharArray());
PEMParser pemRd = openPEMResource("smimenopw.pem");
Object o;
PrivateKeyInfo key = null;
while ((o = pemRd.readObject()) != null)
{
key = (PrivateKeyInfo)o;
}
if (key == null)
{
fail("private key not detected");
}
}
public static void main(
String[] args)
{
Security.addProvider(new BouncyCastleProvider());
runTest(new ParserTest());
}
}
Ну, мой код похож на ваш, с небольшими отличиями...
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) {}
}
Java поддерживает использование DER для открытых и закрытых ключей из коробки (что в основном совпадает с PEM, как запрашивает OP, за исключением того, что файлы 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
Прочтите открытый ключ из 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;
}
Я думаю, что в вашем определении частного ключа, вы должны заменить:
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
Java libs делает почти один вкладыш для чтения публичного сертификата, генерируемого openssl:
val certificate: X509Certificate = ByteArrayInputStream(
publicKeyCert.toByteArray(Charsets.US_ASCII))
.use {
CertificateFactory.getInstance("X.509")
.generateCertificate(it) as X509Certificate
}
Но, черт возьми, чтение секретного ключа было проблематичным:
- Сначала пришлось удалить начальный и конечный теги, что не обязательно при чтении открытого ключа.
- Затем мне пришлось убрать все новые строки, иначе это каркает!
- Затем мне пришлось декодировать обратно в байты, используя байт 64
- Тогда я смог произвести
RSAPrivateKey
,
увидеть это: окончательное решение в kotlin
Если PEM содержит только один закрытый ключ RSA без шифрования, это должна быть структура последовательности ASN.1, включающая 9 чисел, чтобы представить ключ китайской теоремы об остатках (CRT):
- версия (всегда 0)
- модуль (n)
- публичная экспонента (e, всегда 65537)
- частный показатель (d)
- простое число p
- простое число q
- d мод (p - 1) (dp)
- d мод (q - 1) (dq)
- q^-1 мод 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;
}
}
Чтобы получить открытый ключ, вы можете просто сделать:
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;
}
Вы можете использовать https://github.com/KarlScheibelhofer/java-crypto-tools/blob/main/README.md . Он очень мал и не имеет других зависимостей.
Чтение файлов PEM аналогично чтению любого другого хранилища ключей в Java.
Например:
import dev.scheibelhofer.crypto.provider.JctProvider;
KeyStore ks = KeyStore.getInstance("pem", JctProvider.getInstance());
ks.load(new FileInputStream("webserver-key-and-certificate-chain.pem"), password);