Get a PrivateKey from a RSA .pem file [duplicate] - java

This question already has answers here:
Decrypting an OpenSSL PEM Encoded RSA private key with Java?
(2 answers)
Closed 5 years ago.
Given this .pem file (generated with openssl and encrypted with a password):
-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: DES-EDE3-CBC,AC009672952033EB
2wegzxf3MtncXS1CY3c.....
....
....
-----END RSA PRIVATE KEY-----
How do I get a PrivateKey object in Java? I wrote the following code but I cannot find the right way to get a KeySpec:
PrivateKey readFromPem(File keyFile, String password){
PemReader r = new PemReader(new InputStreamReader(new FileInputStream(keyFile)));
PemObject pemObject = r.readPemObject();
byte[] encodedKey = pemObject.getContent();
KeySpec keySpec = ???? // how to get this?
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PrivateKey key = keyFactory.generatePrivate(keySpec);
return key;
}
I guess I should build a RSAPrivateKeySpec, but I don't know how. I tried the method from this answer and this other answer, but they both result in errors when parsing the byte array.

I'm using BouncyCastle 1.57 (bcprov-jdk15on, bcmail-jdk15on and bcpkix-jdk15on) and Java 7.
You can read the private key using the JcaPEMKeyConverter class.
The code below works for keys with and without a password:
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.jcajce.JcaPEMKeyConverter;
import org.bouncycastle.openssl.jcajce.JcePEMDecryptorProviderBuilder;
// don't forget to add the provider
Security.addProvider(new BouncyCastleProvider());
String password = "your password";
// reads your key file
PEMParser pemParser = new PEMParser(new FileReader(keyFile));
Object object = pemParser.readObject();
JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider("BC");
KeyPair kp;
if (object instanceof PEMEncryptedKeyPair) {
// Encrypted key - we will use provided password
PEMEncryptedKeyPair ckp = (PEMEncryptedKeyPair) object;
// uses the password to decrypt the key
PEMDecryptorProvider decProv = new JcePEMDecryptorProviderBuilder().build(password.toCharArray());
kp = converter.getKeyPair(ckp.decryptKeyPair(decProv));
} else {
// Unencrypted key - no password needed
PEMKeyPair ukp = (PEMKeyPair) object;
kp = converter.getKeyPair(ukp);
}
// RSA
KeyFactory keyFac = KeyFactory.getInstance("RSA");
RSAPrivateCrtKeySpec privateKey = keyFac.getKeySpec(kp.getPrivate(), RSAPrivateCrtKeySpec.class);
System.out.println(privateKey.getClass());
The privateKey's class will be java.security.spec.RSAPrivateCrtKeySpec (which extends RSAPrivateKeySpec).

Use Bouncy Castle's bcpkix dependency which knows how to handle OpenSSL keys.
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk14</artifactId>
<version>1.57</version>
</dependency>
and try it like this:
private PrivateKey readFromPem(File keyFile, String password) throws IOException {
Security.addProvider(new BouncyCastleProvider());
PEMParser pemParser = new PEMParser(new InputStreamReader(new FileInputStream(keyFile)));
PEMEncryptedKeyPair encryptedKeyPair = (PEMEncryptedKeyPair) pemParser.readObject();
PEMDecryptorProvider decryptorProvider = new JcePEMDecryptorProviderBuilder().build(password.toCharArray());
PEMKeyPair pemKeyPair = encryptedKeyPair.decryptKeyPair(decryptorProvider);
JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider("BC");
return converter.getPrivateKey(pemKeyPair.getPrivateKeyInfo());
}

Related

String to Generate Private Key [duplicate]

Is it possible to read the RSA private key of format PKCS1 in JAVA without converting to PKCS8? if yes, sample code is appreciated.
-----BEGIN RSA PRIVATE KEY-----
BASE64 ENCODED DATA
-----END RSA PRIVATE KEY-----
Java does not come with out-of-the-box support for PKCS1 keys. You can however use Bouncycastle
PEMParser pemParser = new PEMParser(new FileReader(privateKeyFile));
JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider("BC");
Object object = pemParser.readObject();
KeyPair kp = converter.getKeyPair((PEMKeyPair) object);
PrivateKey privateKey = kp.getPrivate();

Java/Android - ECDH Encryption - Create ECPublicKey from String

I have a little problem with the ECDH (Elliptic-curve Diffie-Hellman) encryption.
I use the BouncyCastle library.
Here is my function to generate keys :
public static KeyPair generateECKeys() {
try {
ECNamedCurveParameterSpec parameterSpec = ECNamedCurveTable.getParameterSpec("brainpoolp256r1");
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("ECDH", "BC");
keyPairGenerator.initialize(parameterSpec);
KeyPair keyPair = keyPairGenerator.generateKeyPair();
return keyPair;
} catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException | NoSuchProviderException e) {
Log.d("Error - ",e.getMessage());
e.printStackTrace();
return null;
}
}
I encode my public key in base64 with :
String keyPairA_public_base64 = Base64.getEncoder().encodeToString(keyPairA.getPublic().getEncoded());
Here is an example of the key received :
keyPairA_public_base64 = "MFowFAYHKoZIzj0CAQYJKyQDAwIIAQEHA0IABGuSxmgwVGLHwcVhSf7C4/BfxfL4pGixHht8rWjPMBMTH5Vav1RQnf/Ucv9rLpD3M6ad8hHotwP5IpFsQT3hRkg="
Now, I need to generate an ECPublicKey object with the public key (String).
ECNamedCurveParameterSpec spec = ECNamedCurveTable.getParameterSpec("brainpoolp256r1");
KeyFactory kf = KeyFactory.getInstance("ECDH", new BouncyCastleProvider());
ECNamedCurveSpec params = new ECNamedCurveSpec("brainpoolp256r1", spec.getCurve(), spec.getG(), spec.getN());
ECPoint point = ECPointUtil.decodePoint(params.getCurve(), keyPairA.getPublic().getEncoded()); // Error here : Invalid point encoding 0x30
ECPublicKeySpec pubKeySpec = new java.security.spec.ECPublicKeySpec(point, params);
ECPublicKey pk = (ECPublicKey) kf.generatePublic(pubKeySpec);
But, I have an error : Invalid point encoding 0x30 when I use ECPointUtil.decodePoint()
I don't understand how I can solve this error and if I use the right way to create an ECPublicKey object from a string.
Can you help me please ? :)
ECPointUtil.decodePoint() expects a raw public key. keyPairA_public_base64 on the other hand is a Base64 encoded public key in X.509/SPKI format (i.e. not a raw public key) and can be imported as follows:
import java.security.spec.X509EncodedKeySpec;
import java.security.interfaces.ECPublicKey;
import java.security.KeyFactory;
...
X509EncodedKeySpec x509EncodedKeySpecA = new X509EncodedKeySpec(Base64.getDecoder().decode(keyPairA_public_base64));
KeyFactory keyFactoryA = KeyFactory.getInstance("ECDH");
ECPublicKey publicKeyA = (ECPublicKey)keyFactoryA.generatePublic(x509EncodedKeySpecA);

How to load RSA public key from String for signature verification in Java?

I have the following public key, that is stored in the DB (PostgresSQL) as text. It's a String, in java:
-----BEGIN RSA PUBLIC KEY-----
MIICCgKCAgEA1ht0OqZpP7d/05373OE7pB7yCVGNGzkUEuCneyfOzps6iA03NbvI
1ZL0Jpp/N3AW73lGdhaoa3X3JE4GsI/bsToVLQwTKmIOC4yjTvBctmFEoyhhTfxW
s1UHZKl4XZ/7THbRlKHhRaTKyfDAbikkMAxNT/qutLAPjnN1qOwjb1oRq52NP6FJ
KWTTikz4UeOHroX+Xthn2fJSJDlQ4YMdBbgrZVx5JcHKNuPTKRf5gI8QQKMSA9Q9
QJRE5OGp7b6dG14ZmOUnUxb00Mp20LgcaGPcuWU+oFsbQaF6W4G4bdkSZRJJXhSg
d4Q7mahpar94/gnztJmth0GzqTWUYyZIWNqIFoMwuOgeaiDV43zb3uLsRVpRKYYy
esmzcOy/jTScVLRCD8QRyu9B2wgCkNAVztQOXPCOOa4O1LlVQWaecIs4WPhOqDhi
KTBhyVkpC1TrrBkp+QMqMqWll1OyVb6k/7uV0qE/i6rHJtjo5v9bcIgYzswyx9CD
9PKl2Q0L0Jg7TMG+yLDIrLfGeuSeEc4XYJzN7bJcCeiizzu5iU9dQUkrncOrq9jn
Ub2pM/+A+JqIsoPK3IY/pJKqH4JYpGKhO1iPQF6iXIZT1r3ZgJUSQtzSeyYqhkla
2uR2BsbPbDqebCuXm3lAsY5w+dujijcn96PKwYha1LsK5sACHuJ79AMCAwEAAQ==
-----END RSA PUBLIC KEY-----
I don't know how this key has been generated, I'm sorry. I have been told to take this key and verify the signature of another string that I'll call "object". I have been told that the algorithm that I have to use to verify "object" is SHA256withRSA.
So, I have written the following java method to read the key
private PublicKey getPublicKey(String publicKey) throws NoSuchAlgorithmException, InvalidKeySpecException, UnsupportedEncodingException {
publicKey = publicKey.replaceAll("\\n", "");
publicKey = publicKey.replace("-----BEGIN RSA PUBLIC KEY-----", "");
publicKey = publicKey.replace("-----END RSA PUBLIC KEY-----", "");
publicKey = publicKey.trim();
byte[] keyDecoded = Base64.getDecoder().decode(publicKey.getBytes());
X509EncodedKeySpec publicSpec = new X509EncodedKeySpec(keyDecoded);
KeyFactory kf = KeyFactory.getInstance("RSA");
PublicKey pubKey = kf.generatePublic(publicSpec);
return pubKey;
}
The point is that I get the following exception:
java.security.InvalidKeyException: IOException: algid parse error, not
a sequence
I have read plenty of qustions as mine in stackoverflow. The code written by other users is pretty similar (sometimes identical) to mine. So I definitely don't get why it doesn't work for me. Other developers (workmates) are doing the same in php and it works great, so I would discard the hypothesis of wrong public key. Maybe didn't I understood the process clearly? Do you have any clue, please?
I have also tried to cope with the problem using BouncyCastle library, as suggested here, but I get the same exception. The following is the code I have written:
private static PublicKey getPublicKey(String publicKey)
throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
Security.addProvider(new BouncyCastleProvider());
PemReader pp = new PemReader(new StringReader(publicKey));
PemObject pem = pp.readPemObject();
byte[] content = pem.getContent();
pp.close();
X509EncodedKeySpec spec = new X509EncodedKeySpec(content);
KeyFactory kf = KeyFactory.getInstance("RSA");
return kf.generatePublic(spec);
}
You can't load that key using an X509EncodedKeySpec. According to it's JavaDoc documentation it expects the following format:
SubjectPublicKeyInfo ::= SEQUENCE {
algorithm AlgorithmIdentifier,
subjectPublicKey BIT STRING }
Instead your key looks different. I used the data from your post, converted it to hex data and posted it into the online ASN.1 decoder.
The output is this:
SEQUENCE (2 elem)
INTEGER (4096 bit) 873481340827968071893572683200799871431146795599597693981565010037737…
INTEGER 65537
As you may recognize your key does not contain an AlgorithmIdentifier therefore it can not be loaded using X509EncodedKeySpec.
My suggestion would be to use the BouncyCastle library and it's PEMParser class for loading this key:
File pemFile = new File("test.pem");
try (PEMParser pp = new PEMParser(new InputStreamReader(new FileInputStream(pemFile)))) {
SubjectPublicKeyInfo subjPubKeyInfo = (SubjectPublicKeyInfo) pp.readObject();
RSAKeyParameters rsa = (RSAKeyParameters) PublicKeyFactory.createKey(subjPubKeyInfo);
RSAPublicKeySpec rsaSpec = new RSAPublicKeySpec(rsa.getModulus(), rsa.getExponent());
KeyFactory kf = KeyFactory.getInstance("RSA");
java.security.PublicKey publicKey = kf.generatePublic(rsaSpec);
System.out.println(publicKey);
}
Or you manually convert the key to PKCS#8 format via openssl.

RSA public key generated in Java is not valid in php

I'm creating a RSA key pair in Java and want to use it in PHP. Java code is as follows:
public static boolean keyGen() throws NoSuchAlgorithmException, IOException, OperatorCreationException, InvalidKeySpecException {
KeyPairGenerator kpGen = KeyPairGenerator.getInstance("RSA");
kpGen.initialize(2048, new SecureRandom());
KeyPair keyPair = kpGen.generateKeyPair();
PublicKey pub = keyPair.getPublic();
byte[] pubBytes = pub.getEncoded();
SubjectPublicKeyInfo spkInfo = SubjectPublicKeyInfo.getInstance(pubBytes);
ASN1Primitive primitive = spkInfo.parsePublicKey();
byte[] publicKeyPKCS1 = primitive.getEncoded();
PemObject pemObject = new PemObject("RSA PUBLIC KEY", publicKeyPKCS1);
StringWriter stringWriter = new StringWriter();
PemWriter pemWriter = new PemWriter(stringWriter);
pemWriter.writeObject(pemObject);
pemWriter.close();
String pemString = stringWriter.toString();
FileOutputStream fos2 = new FileOutputStream("pubk.key");
fos2.write(pemString.getBytes());
fos2.flush();
fos2.close();
}
The generated public key looks like follow:
-----BEGIN RSA PUBLIC KEY-----
MIIBCgKCAQEAh8cQcRitRdEIzcWDpgDhGTxU4e/4CnFcCi4pEi8Pitme4+9MlVOQ
EtwpiaH54nbxBLZX6m/Z0EETqE9hJm02L8cgvp6/T08atJ9NAayEkN5TFSgdmh3Y
CwGa0ckHlO1lzN3jghUTxLnYEHOvBXVaY1SpDEUlLUi6WKsyklqHK+r6fPa9X1sY
6847VPTQX8ORC13LEzdZrGSR39473HTBhR6SzyTET47AgHPy2Q+FMIvN7DeuX5dK
XtQUlvAjJ7KVQJIXuFEzNvHQfUzjJj+LO2MHX77KbGg6Ytz06CnsWS2f6YKBY3Bg
BQ2zqjE2ON1jDLUcika+2ihEzpfXFGLY9wIDAQAB
-----END RSA PUBLIC KEY-----
And I'm importing the saved key file using PHP as follows:
$keyString = file_get_contents($filePath);
openssl_pkey_get_public($keyString);
And when try to encrypt using openssl_public_encrypt it gives me the error
openssl_public_encrypt(): key parameter is not a valid public key
However I tried the same with a JavaScript generated key file and it works well. Any help?
The key evidently needs to be in SubjectPublicKeyInfo format, sometimes referred to as "X.509" format -- but not the same thing as an X.509 certificate -- just to add to the general sea of confusion. I got this info not from the documentation but from the user comments below.
Fortunately this takes even fewer lines of Java code to produce, as this little code fragment adapted from your code illustrates:
PublicKey pub = keyPair.getPublic();
byte[] pubBytes = pub.getEncoded();
PemObject pemObject = new PemObject("PUBLIC KEY", pubBytes);
StringWriter stringWriter = new StringWriter();
PemWriter pemWriter = new PemWriter(stringWriter);
pemWriter.writeObject(pemObject);
pemWriter.close();
System.out.println(stringWriter.toString());

Generating keyPair using Bouncy Castle

I have Java code for generating keypair using BC as follows:
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
keyGen.initialize(1024);
KeyPair key = keyGen.generateKeyPair();
PrivateKey priv = key.getPrivate();
PublicKey pub = key.getPublic();
String privateKey = new String(Base64.encode(priv.getEncoded(), 0,priv.getEncoded().length, Base64.NO_WRAP));
String publicKey1 = new String(Base64.encode(pub.getEncoded(), 0,pub.getEncoded().length, Base64.NO_WRAP));
String publicKey = new String(Base64.encode(publicKey1.getBytes(),0, publicKey1.getBytes().length, Base64.NO_WRAP));
Now I want to do same in C# using BC. I have downloaded WP8BouncyCastle library via nuget package manager. I have written as:
var kpgen = new RsaKeyPairGenerator();
kpgen.Init(new KeyGenerationParameters(new SecureRandom(new CryptoApiRandomGenerator()), 1024));
var keyPair = kpgen.GenerateKeyPair();
AsymmetricKeyParameter privateKey = keyPair.Private;
AsymmetricKeyParameter publicKey = keyPair.Public;
string prvKey = Convert.ToBase64String(UTF8Encoding.UTF8.GetBytes(privateKey.ToString()));
string pubKey = Convert.ToBase64String(UTF8Encoding.UTF8.GetBytes(publicKey.ToString()));
string pubKey1 = Convert.ToBase64String(UTF8Encoding.UTF8.GetBytes(pubKey.ToString()));
But I need getEncoded() method available in Java which is not available in BC library in C#. This getEncoded() method is used to convert given key into X.509 encoded key.In case of Java, public key getting twice converted (getencoded() and getBytes()) ,I am not able to do same in C#.
Is there any solution to it?
Use the following code for private key:
PrivateKeyInfo pkInfo = PrivateKeyInfoFactory.CreatePrivateKeyInfo(keyPair.Private);
String privateKey = Convert.ToBase64String(pkInfo.GetDerEncoded());
and following for public:
SubjectPublicKeyInfo info = SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(keyPair.Public);
String publicKey = Convert.ToBase64String(info.GetDerEncoded());

Categories

Resources