How to generate PublicKey object from a file in Java - java

I have a file containing a public RSA key (generated with ssh-keygen). I'd like to read the file and generate a PublicKey object.
Prior to that I converted the file, since reading the original files seems to be impossible:
# http://unix.stackexchange.com/questions/220354/how-to-convert-public-key-from-pem-to-der-format/220356#220356
ssh-keygen -f ~/.ssh/id_rsa.pub -e -m PEM > ~/.ssh/id_rsa.pub.pem
openssl rsa -RSAPublicKey_in -in ~/.ssh/id_rsa.pub.pem -inform PEM -outform DER -out ~/.ssh/id_rsa.pub.der -RSAPublicKey_out
From Java - Encrypt String with existing public key file I defined the function readFileBytes:
public static byte[] readFileBytes(String filename) throws IOException {
Path path = Paths.get(System.getProperty("user.home") + filename);
return Files.readAllBytes(path);
}
Now I'd like to read the file and generate the PublicKey object, but I could not find a way to do that; java.security.spec.RSAPublicKeySpec does not provide a fitting constructor and java.security.spec.X509EncodedKeySpec throws an error java.security.spec.InvalidKeySpecException: java.security.InvalidKeyException: IOException: algid parse error, not a sequence:
//RSAPublicKeySpec publicSpec = new RSAPublicKeySpec(readFileBytes("/.ssh/id_rsa.pub.der"));
// No fitting construktor
X509EncodedKeySpec publicSpec = new X509EncodedKeySpec(readFileBytes("/.ssh/id_rsa.pub.der"));
// Gives: "algid parse error, not a sequence"

I had a project in which (RSA) encryption was necessary, this is how I reconstructed the publicKey given the publicKey's byte array, that was just read from the file.
public PublicKey reconstruct_public_key(String algorithm, byte[] pub_key) {
PublicKey public_key = null;
try {
KeyFactory kf = KeyFactory.getInstance(algorithm);
EncodedKeySpec pub_key_spec = new X509EncodedKeySpec(pub_key);
public_key = kf.generatePublic(pub_key_spec);
} catch(NoSuchAlgorithmException e) {
System.out.println("Could not reconstruct the public key, the given algorithm oculd not be found.");
} catch(InvalidKeySpecException e) {
System.out.println("Could not reconstruct the public key");
}
return public_key;
}
Then you could call the procedure similar to this call, reconstruct_public_key("RSA", readFileBytes("path/to/your/publicKey/file"));
EDIT : I tried to do it myself (write the public key to a file, read that file and reconstruct the key). This works :
public static void main(String args[]) {
String path = "./pub_key_test.txt";
// Generate a keypair to write to file
KeyPair kp = generate_key();
PublicKey pub_key = kp.getPublic();
File file = new File(path);
try {
// Write to file
file.createNewFile();
FileOutputStream out = new FileOutputStream(path);
out.write(pub_key.getEncoded()); // Write public key to the file
out.close();
// Read from file
FileInputStream in = new FileInputStream(path);
byte[] pub_key_arr = new byte[in.available()];
in.read(pub_key_arr, 0, in.available());
in.close();
// Reconstruct public key
PublicKey reconstructed_pub_key = reconstruct_public_key("RSA", pub_key_arr);
} catch(IOException e) {
System.out.println("Could not open the file : " + e.getStackTrace());
}
}
And this is the generate_key procedure :
public KeyPair generate_key() {
while(true) { // Else the compiler will complain that this procedure does not always return a "KeyPair"
try {
final KeyPairGenerator key_generator = KeyPairGenerator.getInstance("RSA");
key_generator.initialize(2048); // Keys of 2048 bits (minimum key length for RSA keys) are safe enough (according to the slides 128bit keys > 16 years to brute force it)
final KeyPair keys = key_generator.generateKeyPair();
return keys;
} catch(NoSuchAlgorithmException e) {
System.out.println("The given encryption algorithm (RSA) does not exist. -- generate_key() - Cryptography.");
}
}
}
If you test this, you will see that the publicKey is reconstructed successfully.
EDIT : I tried doing it myself, using the ssh-keygen tool. This is what i did :
First I generated a RSA private key (.PEM format)
Outputted the public key portion to .DER format, so it can be used by Java.
This is how I did the conversion, which is a bit different of yours :
openssl rsa -in private_key_file.pem -pubout -outform DER -out java_readable_file.der
And I did the file reading like here, which doesn't differ much of yours. I tested this and Java successfully reconstructed the public key.

Creating RSA Private Key
openssl genrsa -out rsaprivkey.pem 1024
Generates the public key in DER format.
openssl rsa -in rsaprivkey.pem -pubout -outform DER -out rsapubkey.der
We use this code extracts the public key RSA OR DSA from the X.509 certificate.
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
/**
* This class is capable of extracting a public key from a X.509 certficate
* and returning the PublicKey representation from a referenced byte array.
*
*/
public class ExtractPublicKey {
// Certificate Filename (Including Path Info)
private static final String certFilename = "cacert.pem";
// Public Key Filename (Including Path Info)
private static final String pubKeyFilename = "rsapublic.key";
public static PublicKey generatePublicKey(byte[] encodedKey)
throws NoSuchAlgorithmException, InvalidKeySpecException {
X509EncodedKeySpec pubSpec = new X509EncodedKeySpec(encodedKey);
boolean isSupportedKey = false;
KeyFactory factory;
PublicKey retKey = null;
//first try the DSA alg
try {
factory = KeyFactory.getInstance("DSA");
retKey = factory.generatePublic(pubSpec);
isSupportedKey = true;
} catch (InvalidKeySpecException e) {
System.out.println("Could not create DSA Public Key: " + e.toString());
}
//if DSA didnt work, then try RSA
if (!isSupportedKey) {
try {
factory = KeyFactory.getInstance("RSA");
retKey = factory.generatePublic(pubSpec);
isSupportedKey = true;
} catch (InvalidKeySpecException e) {
System.out.println("Could not create RSA Public Key: " + e.toString());
}
}
// if not DSA or RSA
if (!isSupportedKey) {
throw new InvalidKeySpecException("Unsupported key spec: Not RSA or DSA");
}
return retKey;
}
}

Related

JWT, How can we use same RSA key pairs for both Java & NodeJS

I am trying to understand Jason Web Token with RSA Public Key & Private Key pairs.
I generated a RSA key pairs using this websites - https://travistidwell.com/jsencrypt/demo/
, and saved those in two different files-
private key "my_key" is as
-----BEGIN RSA PRIVATE KEY-----
MIICWgIBAAKBgFrSlpibGae4QZjcQp20b9+go22JF9WHJU4TLfdwJbumIgHlukD9
S/W7Dr1j4AXtJ0yJopdnf0bY4294SjjMO4DFHrLDEAB8ZeZkMhfyparYWKHECV5e
szvR6dIHGeDc1CoJolAsPmpO+0qfK4LfkesmLu+diIy9I+B2KJAAka6hAgMBAAEC
gYAlsQuqnYOSJVej1pUW2dEr34CzbpejmAiVVERZUgN20sV+QBaB7hzeCBlf49kO
3JLYoq4FY4BgqJYKpsM2uxteIC8dLJdayyNze1mnPiPW3wLR2bzedOJdjcY0H3Ju
M7lZlk9NVnBI7PNXw4hanifCGY2XPKyrOwrqrMgFsC382QJBAKlKAErRU9N8LZgq
TNAhMTaTPvpZuK4LqqV8RCqPFuNz1tVsO/cjGHfrU3R1pM619K6xCUjvwYvXL0MM
6vm/CKsCQQCJV6/bFuc4L+Uyik0Q+zaRz3fKi5h/AWoN1vmBcXQ01F5GtTAlZDc6
lk/rBk4M69k899I95ReMlIfc68S51P3jAkAp5eLEoaI5iVZPfsicClr/wtmnZGVM
zh5h7quATQHBMX5OPAdrVwhLRbbV7/fmISp5wd8mahBg59UOpzfQr/MZAkAtgC9y
lhPkOXnlVIxTo+ZgSCuXnsfWy9Em5KGkkMG+/tx88GoS+TCS6Flxs5UIEtrVqASv
HMbAfDTGrBVwu2+hAkAnrAHRhFlC8O9zAhoFkWcaYeFu+y1pBcF3pzFfQBpn6uK8
Xd0Ln+eMNaYl6lAECG36jtUvnKLkyfWMHzqdkERj
-----END RSA PRIVATE KEY-----
public key "my_key.pub" is as
-----BEGIN PUBLIC KEY-----
MIGeMA0GCSqGSIb3DQEBAQUAA4GMADCBiAKBgFrSlpibGae4QZjcQp20b9+go22J
F9WHJU4TLfdwJbumIgHlukD9S/W7Dr1j4AXtJ0yJopdnf0bY4294SjjMO4DFHrLD
EAB8ZeZkMhfyparYWKHECV5eszvR6dIHGeDc1CoJolAsPmpO+0qfK4LfkesmLu+d
iIy9I+B2KJAAka6hAgMBAAE=
-----END PUBLIC KEY-----
When I am using this key in NodeJS as -
var fs = require('fs');
var JWT = require('jsonwebtoken');
var privateKey = fs.readFileSync('./my_key', 'utf8');
var publicKey = fs.readFileSync('./my_key.pub', 'utf8');
var jwtOptions = {
issuer : 'Chittaranjan Sardar',
subject: 'JWT Cross Platforms',
audience: 'https://github.com/crsardar',
expiresIn: '5m',
algorithm: "RS256"
};
let token = JWT.sign({user: 'CRSARDAR'}, privateKey, jwtOptions);
console.log("generated token = " + token);
var verifyResult = JWT.verify(token, publicKey, jwtOptions);
console.log("Verification has passed : " + JSON.stringify(verifyResult));
It is Working Fine.
But, when I am trying to use in Java, as follows -
package com.crsardar.handson.java.springboot.jwt.controller;
import java.io.File;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.time.Duration;
import java.time.Instant;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.JwtParser;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
public class JWTJavaWithPublicPrivateKey
{
public static void main(String[] args)
{
System.out.println("generating keys");
Map<String, Object> rsaKeys = null;
try
{
rsaKeys = getRSAKeys();
}
catch (Exception e)
{
e.printStackTrace();
}
PublicKey publicKey = (PublicKey) rsaKeys.get("public");
PrivateKey privateKey = (PrivateKey) rsaKeys.get("private");
System.out.println("generated keys");
String token = generateToken(privateKey);
System.out.println("Generated Token:\n" + token);
verifyToken(token, publicKey);
}
public static String generateToken(PrivateKey privateKey)
{
String token = null;
try
{
Instant now = Instant.now();
Instant after = now.plus(Duration.ofMinutes(1));
Date date = Date.from(after);
Claims claims = Jwts.claims();
claims.put("user", "CRSARDAR");
claims.put("issuer", "Chittaranjan Sardar");
claims.put("subject", "JWT Cross Platforms");
claims.put("audience", "https://github.com/crsardar");
claims.put("created", new Date());
JwtBuilder jwtBuilder = Jwts.builder();
jwtBuilder.setClaims(claims);
jwtBuilder.setExpiration(date);
jwtBuilder.signWith(SignatureAlgorithm.RS512, privateKey);
token = jwtBuilder.compact();
}
catch (Exception e)
{
e.printStackTrace();
}
return token;
}
// verify and get claims using public key
private static Claims verifyToken(String token, PublicKey publicKey)
{
Claims claims;
try
{
JwtParser jwtParser = Jwts.parser();
jwtParser.setSigningKey(publicKey);
Jws<Claims> claimsJws = jwtParser.parseClaimsJws(token);
claims = claimsJws.getBody();
System.out.println("verifyToken : issuer = " + claims.get("issuer"));
}
catch (Exception e)
{
claims = null;
}
return claims;
}
private static Map<String, Object> getRSAKeys() throws Exception
{
Map<String, Object> keys = new HashMap();
PrivateKey privateKey = getPrivateKey();
PublicKey publicKey = getPublicKey();
keys.put("private", privateKey);
keys.put("public", publicKey);
return keys;
}
private static PrivateKey getPrivateKey() throws Exception
{
ClassLoader classLoader = JWTJavaWithPublicPrivateKey.class.getClassLoader();
URL resource = classLoader.getResource("my_key");
File file = new File(resource.getFile());
byte[] keyBytes = Files.readAllBytes(Paths.get(file.toURI()));
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return keyFactory.generatePrivate(spec);
}
private static PublicKey getPublicKey() throws Exception
{
ClassLoader classLoader = JWTJavaWithPublicPrivateKey.class.getClassLoader();
URL resource = classLoader.getResource("my_key.pub");
File file = new File(resource.getFile());
byte[] keyBytes = Files.readAllBytes(Paths.get(file.toURI()));
X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes);
KeyFactory kf = KeyFactory.getInstance("RSA");
return kf.generatePublic(spec);
}
}
It is giving me following errors -
java.security.spec.InvalidKeySpecException: java.security.InvalidKeyException: invalid key format
at sun.security.rsa.RSAKeyFactory.engineGeneratePrivate(RSAKeyFactory.java:217)
at java.security.KeyFactory.generatePrivate(KeyFactory.java:372)
at com.crsardar.handson.java.springboot.jwt.controller.JWTJavaWithPublicPrivateKey.getPrivateKey(JWTJavaWithPublicPrivateKey.java:126)
at com.crsardar.handson.java.springboot.jwt.controller.JWTJavaWithPublicPrivateKey.getRSAKeys(JWTJavaWithPublicPrivateKey.java:110)
at com.crsardar.handson.java.springboot.jwt.controller.JWTJavaWithPublicPrivateKey.main(JWTJavaWithPublicPrivateKey.java:35)
Caused by: java.security.InvalidKeyException: invalid key format
at sun.security.pkcs.PKCS8Key.decode(PKCS8Key.java:331)
at sun.security.pkcs.PKCS8Key.decode(PKCS8Key.java:357)
at sun.security.rsa.RSAPrivateCrtKeyImpl.<init>(RSAPrivateCrtKeyImpl.java:91)
at sun.security.rsa.RSAPrivateCrtKeyImpl.newKey(RSAPrivateCrtKeyImpl.java:75)
at sun.security.rsa.RSAKeyFactory.generatePrivate(RSAKeyFactory.java:316)
at sun.security.rsa.RSAKeyFactory.engineGeneratePrivate(RSAKeyFactory.java:213)

How read a PKCS8 encrypted Private key which is also encoded in DER with bouncycastle?

I have tried answers of these questions:
Bouncy Castle : PEMReader => PEMParser
Read an encrypted private key with bouncycastle/spongycastle
However as my encrypted key is encoded in DER when I call
Object object = pemParser.readObject();
object is null.
I can convert it to PEM with this openssl's command (it decrypts the key too)
openssl pkcs8 -inform der -in pkey.key -out pkey.pem
but I need to read the key in its original file
Both those Qs are about parsing, and decrypting, files using OpenSSL's 'legacy PEM' encryption. You are using PKCS8 encryption which is different though similar, so Reading PKCS8 in PEM format: Cannot find provider is closer. You can use most of the approach there, but skipping the PEM parse:
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.pkcs.EncryptedPrivateKeyInfo; // NOT the
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; // javax ones!
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
import org.bouncycastle.openssl.jcajce.JceOpenSSLPKCS8DecryptorProviderBuilder;
import org.bouncycastle.operator.InputDecryptorProvider;
import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo;
// args[0] = filename args[1] = password
FileInputStream fis = new FileInputStream(args[0]);
byte[] buff = new byte[9999]; int len = fis.read(buff); fis.close();
// could use File.readAllBytes in j8 but my dev machine is old
// create what PEMParser would have
ASN1Sequence derseq = ASN1Sequence.getInstance (Arrays.copyOf(buff,len));
PKCS8EncryptedPrivateKeyInfo encobj = new PKCS8EncryptedPrivateKeyInfo(EncryptedPrivateKeyInfo.getInstance(derseq));
// decrypt and convert key
JcaPEMKeyConverter converter = new JcaPEMKeyConverter();
InputDecryptorProvider decryptionProv = new JceOpenSSLPKCS8DecryptorProviderBuilder().build(args[1].toCharArray());
PrivateKeyInfo keyInfo = encobj.decryptPrivateKeyInfo(decryptionProv);
PrivateKey key = converter.getPrivateKey(keyInfo);
// now actually use key, this is just a dummy
System.out.println (key.getAlgorithm());
By using SpongyCastle we can get good results. I made some code that will get the desired result.
SpongyCastle Jars can be downloaded from below:-
core, prov, pkix
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.security.PrivateKey;
import java.security.Provider;
import java.security.Security;
// need core.jar, prov.jar, bcpkix-jdk15on.jar
import org.spongycastle.asn1.ASN1Sequence;
import org.spongycastle.asn1.pkcs.EncryptedPrivateKeyInfo;
import org.spongycastle.asn1.pkcs.PrivateKeyInfo;
import org.spongycastle.openssl.jcajce.JcaPEMKeyConverter;
import org.spongycastle.openssl.jcajce.JceOpenSSLPKCS8DecryptorProviderBuilder;
import org.spongycastle.operator.InputDecryptorProvider;
import org.spongycastle.pkcs.PKCS8EncryptedPrivateKeyInfo;
class Decrypt {
private static void usage() {
System.err.println(" Usage: encrypted.pk8 password out_decrypted.pk8");
System.exit(2);
}
public static byte[] copyOf(byte[] original, int newLength) {
byte[] copy = new byte[newLength];
System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength));
return copy;
}
public static void loadProvider(String providerClassName)
throws ClassNotFoundException, IllegalAccessException, InstantiationException {
Class providerClass = Class.forName(providerClassName);
Provider provider = (Provider)providerClass.newInstance();
// Security.insertProviderAt(provider, 1);
Security.addProvider(provider);
}
public static void main(String[] args) throws Exception {
if (args.length != 3) usage();
loadProvider("org.spongycastle.jce.provider.BouncyCastleProvider");
FileInputStream fis = new FileInputStream(args[0]);
byte[] buff = new byte[fis.available()];
int len = fis.read(buff);
fis.close();
// create what PEMParser would have
ASN1Sequence derseq = ASN1Sequence.getInstance(copyOf(buff, len));
EncryptedPrivateKeyInfo epkInfo = EncryptedPrivateKeyInfo.getInstance(derseq);
PKCS8EncryptedPrivateKeyInfo encobj = new PKCS8EncryptedPrivateKeyInfo(epkInfo);
// decrypt and convert key
InputDecryptorProvider decryptionProv = new JceOpenSSLPKCS8DecryptorProviderBuilder().build(args[1].toCharArray());
PrivateKeyInfo keyInfo = encobj.decryptPrivateKeyInfo(decryptionProv);
JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider("SC");
PrivateKey key = converter.getPrivateKey(keyInfo);
// now actually use key, this is just a dummy
System.out.println(key.getAlgorithm());
FileOutputStream fos = new FileOutputStream(args[2]);
fos.write(key.getEncoded());
fos.close();
}
}

How to base64 encode openssl generated key pem files? [duplicate]

I am writing a small piece of code which reads public and private key stored in .pem file. I am using the following commands to generate the keys.
Below command to generate pair of key.
$openssl genrsa -out mykey.pem 2048
This command to generate the private key
$openssl pkcs8 -topk8 -inform PEM -outform PEM -in mykey.pem \
-out private_key.pem -nocrypt
and this command to get the public key.
$ openssl rsa -in mykey.pem -pubout -outform DER -out public_key.der
I have written two methods which reads the private key and public key respectively.
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);
}
I feel like this is a naive way of doing it. I couldn't get any better way of doing it over internet. Can anyone suggest me what is the best way of writing the same code to handle the generic cases. I don' want to use any kind of third party library.
I have very basic knowledge of singing/encrypting and hardly use any java security APIs. So if I am not making sense somewhere then please point out.
Try this class.
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(), 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:
replace the in.readAllBytes() call with a call to this:
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();
}
thanks to Daniel for noticing API compatibility issues
One option is to use bouncycastle's PEMParser:
Class for parsing OpenSSL PEM encoded streams containing X509
certificates, PKCS8 encoded keys and PKCS7 objects.
In the case of PKCS7 objects the reader will return a CMS ContentInfo
object. Public keys will be returned as well formed
SubjectPublicKeyInfo objects, private keys will be returned as well
formed PrivateKeyInfo objects. In the case of a private key a
PEMKeyPair will normally be returned if the encoding contains both the
private and public key definition. CRLs, Certificates, PKCS#10
requests, and Attribute Certificates will generate the appropriate BC
holder class.
Here is an example of using the 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());
}
}
Well, my code is like yours, with little diferences...
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 supports using DER for public and private keys out of the box (which is basically the same as PEM, as the OP asks, except PEM files contain base 64 data plus header and footer lines).
You can rely on this code (modulo exception handling) without any external library if you are on Java 8+ (this assumes your key files are available in the classpath):
class Signer {
private KeyFactory keyFactory;
public Signer() {
this.keyFactory = KeyFactory.getInstance("RSA");
}
public PublicKey getPublicKey() {
byte[] publicKey = readFileAsBytes("public-key.der");
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKey);
return keyFactory.generatePublic(keySpec);
}
public PrivateKey getPrivateKey() {
byte[] privateKey = readFileAsBytes("private-key.der");
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKey);
return keyFactory.generatePrivate(keySpec);
}
private URI readFileAsBytes(String name) {
URI fileUri = getClass().getClassLoader().getResource(name).toURI();
return Files.readAllBytes(Paths.get(fileUri));
}
}
For the record, you can convert a PEM key to a DER key with the following command:
$ openssl pkcs8 -topk8 -inform PEM -outform DER -in private-key.pem -out private-key.der -nocrypt
And get the public key in DER with:
$ openssl rsa -in private-key.pem -pubout -outform DER -out public-key.der
I think in your private key definition, You should replace:
X509EncodedKeySpec spec = new X509EncodedKeySpec(decoded);
with:
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(decoded);
Look your openssl command:
$openssl **pkcs8** -topk8 -inform PEM -outform PEM -in mykey.pem \ -out private_key.pem -nocrypt
And the java Exception:
Only PCKS8 codification
Java libs makes it almost a one liner to read the public cert, as generated by openssl:
val certificate: X509Certificate = ByteArrayInputStream(
publicKeyCert.toByteArray(Charsets.US_ASCII))
.use {
CertificateFactory.getInstance("X.509")
.generateCertificate(it) as X509Certificate
}
But, o hell, reading the private key was problematic:
First had to remove the begin and end tags, which is not nessarry when reading the public key.
Then I had to remove all the new lines, otherwise it croaks!
Then I had to decode back to bytes using byte 64
Then I was able to produce an RSAPrivateKey.
see this: Final solution in kotlin
Read public key from pem (PK or Cert). Depends on 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;
}
Read private key from 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;
}
If a PEM contains only one RSA private key without encryption, it must be an ASN.1 sequence structure including 9 numbers to present a Chinese Remainder Theorem (CRT) key:
version (always 0)
modulus (n)
public exponent (e, always 65537)
private exponent (d)
prime p
prime q
d mod (p - 1) (dp)
d mod (q - 1) (dq)
q^-1 mod p (qinv)
We can implement an 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();
}
}
Then read the private key from a PEM file:
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;
}
}
To get the public key you can simply do:
public static PublicKey getPublicKeyFromCertFile(final String certfile){
return new X509CertImpl(new FileInputStream(new File(certfile))).getPublicKey();
To get the private key is trickier, you can:
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;
}

Generate Private RSA Key out of Hex-String Key-Format

I have key.json data in which the following information is stored:
{
"privateExponent":"0x74326408a9392dc21ab4297391a8c2152c165ca71a3f2282ded681f2cbf8c999eb27bb17524edf431004fd5c5c4eae82ee9138d5bebd61b80f497f762a8bace0baaee6a374f94b27ee4824ebacbfed15d568f9cc17b369af3f0ad879c1d442a2401c01687d7ea51e64f5e8ca67437d4c591a699604af0adca695761561844527002ae51dd0c5a93217a1c6022c97091761837fb8341a6f85254a29bc2d3e48791a6347701a7743760546530fbe236c9bf90f9994ea428777b065c92dfd1bfa86796f43e1e2a1b47299e52c61620ad4ebe26b9bacec78ca73e66efa9404628a550c29ea59eb9826de5342da7b84bba6bcd50aa0fe267eaa8d113fab76262d4fe9",
"publicExponent": "0x100010",
"modulus": "0x00e1de9b838b4b2026b29f03d8fecb916622b25dd89d317d5e79ba2a3e148b2d73278cb1944ba4be4bf87f9ab03f612cb28944bc45086a00a9f87ea489ff0ea866e5d6cf62654065d12967d05836b286d9d55d0fe67faa7b77d8c66346b76b0716946e5a96c64f180e1bc71881534d79eba75582bba448ad648cf93d59c8eeb738ea6bb9a94ffada4ecee846b3aa666ba0fb68c10b39ed65aa067046e970cf19d2a92b787643e54ce09e1c7459475aa6d4b89eb5032dcf7b8b80833f12a5c86cce46f3d2583feccc6243653f75c0412499a2edafddad31f70811bcf81343a49933c992d25efc1e522220ea9da6c5cf80d9ed63ff5c21a1cc7fb537b414e6708957"
}
The Information for privateExponent, public Exponent and modulus was retrievied from the pem file, where the keys were created by openssl:
openssl genrsa 2048 > key.pem
openssl rsa -text < key.pem
So, first I remove the starting "0x", since Hex Strings are stored without 0x in Java. Then, I want to convert them into a byte-array and generate a Key out of it. This is my code for doing so:
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;
import javax.xml.bind.DatatypeConverter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
/* Json Fomrats:
key.json:
privateExponent: hex-String,
public Exponent: hex-String,
modulus: hex-String
payload.json:
message: String,
sig {
modulus: hex String,
publicExponent: hex String,
signature: String
}
*/
public class Main {
public static String toHexString(byte[] array) {
return DatatypeConverter.printHexBinary(array);
}
public static byte[] toByteArray(String s) {
return DatatypeConverter.parseHexBinary(s);
}
public static void main(String[] args) {
JSONParser parser = new JSONParser();
try {
Object obj = parser.parse(new FileReader(new File((System.getProperty("user.dir") + "\\src\\com\\company\\key.json"))));
JSONObject jObj = (JSONObject) obj;
System.out.println(jObj.get("privateExponent").toString());
String privKeyS = jObj.get("privateExponent").toString().replace("0x", "");
System.out.println(privKeyS);
byte[] privKeyBytes = toByteArray(privKeyS);
System.out.println(privKeyBytes);
PKCS8EncodedKeySpec privKeySpec = new PKCS8EncodedKeySpec(privKeyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PrivateKey key = keyFactory.generatePrivate(privKeySpec);
} catch (FileNotFoundException e1) {
e1.printStackTrace();
} catch (ParseException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (InvalidKeySpecException e) {
e.printStackTrace();
}
}
}
When I run the problem, I get the following error message:
java.security.spec.InvalidKeySpecException: java.security.InvalidKeyException: invalid key format
at sun.security.rsa.RSAKeyFactory.engineGeneratePrivate(RSAKeyFactory.java:217)
at java.security.KeyFactory.generatePrivate(KeyFactory.java:372)
at com.company.Main.main(Main.java:70)
Caused by: java.security.InvalidKeyException: invalid key format
Why, though? I do not see the why this format is not working.
You are trying to load a PKCS#8 encoded key, but you need to create the key from privateExponent and modulus
//Convert hex strings to BigInteger
BigInteger privateExponent = new BigInteger(privateExponentHex, 16);
BigInteger modulus = new BigInteger(modulusHex, 16);
//Build the private key
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
RSAPrivateKeySpec privateSpec = new RSAPrivateKeySpec(modulus, privateExponent);
PrivateKey privateKey = keyFactory.generatePrivate(privateSpec);

Java 7 : output from encryption with public RSA key using cipher "RSA/ECB/OAEPWithSHA1AndMGF1Padding" does not match with openssl command

We have to encrypt our data with HMAC-SHA256 that needs randomly generated salt. We are generating the salt this way:
public String generateSalt() throws Exception
{
KeyGenerator keyGen;
String salt = null;
try
{
keyGen = KeyGenerator.getInstance( "HmacSHA256" );
keyGen.init( 128 );
SecretKey key = keyGen.generateKey();
byte[] encodedKey = key.getEncoded();
salt = Base64.encodeBase64String( key.getEncoded() );
LOG.info( "Salt : " + salt );
}
catch ( NoSuchAlgorithmException )
{
e.printStackTrace();
throw e;
}
return salt;
}
According to our test this salt generation part is right. I have issue with the next part:
Now I have to write this salt in binary format in a file ( say named as pie_raw) and that's been done as:
private void writeToFile( byte[] saltBytes, String fileName ) throws FileNotFoundException, IOException
{
DataOutputStream out = new DataOutputStream( new FileOutputStream( enviro.getOutputFilePath()
+ fileName ) );
out.write( saltBytes );
out.close();
LOG.info( " Raw file created : " + enviro.getOutputFilePath() + fileName );
}
And then, I have to encrypt this salt with a supplied public RSA key in ".pem" and for Java implementation, the cipher will be "RSA/ECB/OAEPWithSHA1AndMGF1Padding". And finally the binary ciphertext should be written to a file named "pie_key". This part has been implemented this way:
private byte[] encryptSalt( String salt ) throws Exception
{
byte[] cipheredKey = null;
try
{
String keyString= readKeyFile( enviro.getPublicKeyFile() );
byte[] pem = pemToDer(keyString);
PublicKey publicKey = derToPublicKey(pem);
//PublicKey publicKey = getPublicKey( enviro.getPublicKeyFile() );
// Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
Cipher rsaCipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA1AndMGF1Padding");
rsaCipher.init( Cipher.ENCRYPT_MODE, publicKey );
cipheredKey = rsaCipher.doFinal( salt.getBytes( "UTF-8" ) );//"UTF-8"
LOG.info( "Cyphered key : " + cipheredKey.toString() );
}
catch ( IOException | GeneralSecurityException e )
{
e.printStackTrace();
throw e;
}
return cipheredKey;
}
static String readKeyFile( String path )
throws IOException
{
String line = null;
try (BufferedReader br =
new BufferedReader( new FileReader( path ) ))
{
StringBuilder sb = new StringBuilder();
line = br.readLine();
while ( line != null )
{
sb.append( line );
sb.append( "\n" );
line = br.readLine();
}
return sb.toString();
}
}
public static byte[] pemToDer( String pemKey ) throws GeneralSecurityException
{
String[] parts = pemKey.split( "-----" );
return DatatypeConverter.parseBase64Binary( parts[ parts.length / 2 ] );
}
public static PublicKey derToPublicKey( byte[] asn1key ) throws GeneralSecurityException
{
X509EncodedKeySpec spec = new X509EncodedKeySpec( asn1key );
KeyFactory keyFactory = KeyFactory.getInstance( "RSA" );
return keyFactory.generatePublic( spec );
}
Writing this encrypted salt to a file named as "pie_key" in binary format by calling the "writeToFile" method above.
Now the content of the file "pie_key" should match the out put of the cmd :
openssl rsautl -encrypt -pubin -inkey wrap_pie_key_rsa.pem -oaep -in pie_key.raw -out pie_key
But whatever I tried ( you may find some signs of the ways, I tried ) did not work means that the final binary-encrypted-salt did not match with the output of openssl cmd.
Any idea what I am doing wrong?
I am using Java 7. And the .pem (partial) looks like
-----BEGIN PUBLIC KEY-----
MIIBIjANBgk345iG9w0BAQEFAA54328AMIIBCgKCAQEAt4GLJGPmvYdxwwAe59n3
.
.
.
.
7QIDNQAB
-----END PUBLIC KEY-----
Thanks in advance.
First of all, as Artjom already mentioned, the padding for OAEP or PKCS#1 v1.5 compatible padding is randomized. So even if you encrypt the same salt multiple times you would not get the same value. You can only decrypt the result to see if encryption succeeded.
Furthermore, you say you need a binary salt, but you first encode the salt to base64. It's unlikely that your encryption should contain an encoded salt. Maybe you need to encode the output of the encryption, not the salt.
The spurious encoding happens in the following line:
salt = Base64.encodeBase64String( key.getEncoded() );
Finally, although a new HMAC key generally consists of fully random bytes, I would say that it is not the right way to generate a salt. Instead just use:
SecureRandom rngForSalt = new SecureRandom();
byte[] salt = new byte[SALT_SIZE];
rngForSalt.nextBytes(salt);
Note too that the Bouncy Castle lightweight API (i.e. calling org.bouncycastle functionality directly) contains a PEM codec. No need to program or hack that yourself.
Try this Java 8 code. Bouncy castle provider classes required (no need to register the provider, this is just for the PEM handling).
package nl.maartenbodewes.stackoverflow;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.file.Files;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import org.bouncycastle.util.io.pem.PemObject;
import org.bouncycastle.util.io.pem.PemReader;
import org.bouncycastle.util.io.pem.PemWriter;
public class GenerateAndWrapHMACKey {
public static SecretKey generateHMACKey() throws Exception {
final KeyGenerator keyGen;
try {
keyGen = KeyGenerator.getInstance("HmacSHA256");
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("HMAC KeyGeneration should be available");
}
keyGen.init(128);
SecretKey key = keyGen.generateKey();
return key;
}
public static void writeToFile(SecretKey key, String filename)
throws IOException {
// file handling probably should be in a separate class
Files.write((new File(filename)).toPath(), key.getEncoded());
}
public static RSAPublicKey readRSAPublicKey(String filename) throws IOException, InvalidKeySpecException {
try (PemReader reader = new PemReader(new FileReader(filename))) {
PemObject pemObject = reader.readPemObject();
KeyFactory kf;
try {
kf = KeyFactory.getInstance("RSA");
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("RSA key factory not available", e);
}
KeySpec keySpec = new X509EncodedKeySpec(pemObject.getContent());
try {
return (RSAPublicKey) kf.generatePublic(keySpec);
} catch (ClassCastException e) {
throw new InvalidKeySpecException("That's no RSA key", e);
}
}
}
public static byte[] wrapKey(Key key, RSAPublicKey wrappingKey) throws InvalidKeyException, IllegalBlockSizeException {
Cipher rsaWrapper;
try {
rsaWrapper = Cipher.getInstance("RSA/ECB/OAEPWithSHA1AndMGF1Padding");
rsaWrapper.init(Cipher.WRAP_MODE, wrappingKey);
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException e) {
throw new RuntimeException("RSA OAEP should be available for RSA public key", e);
}
return rsaWrapper.wrap(key);
}
public static void main(String[] args) throws Exception {
// we need an RSA PEM key first I guess :)
KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
kpg.initialize(1024, new SecureRandom());
KeyPair kp = kpg.generateKeyPair();
String publicKeyFilename = "rsa_pub.pem";
try (PemWriter pemWriter = new PemWriter(new FileWriter(publicKeyFilename))) {
pemWriter.writeObject(new PemObject("PUBLIC KEY", kp.getPublic().getEncoded()));
}
RSAPublicKey wrappingRSAPublicKey = readRSAPublicKey(publicKeyFilename);
SecretKey hmacKey = generateHMACKey();
byte[] wrappedKey = wrapKey(hmacKey, wrappingRSAPublicKey);
System.out.println(Base64.getEncoder().encodeToString(wrappedKey));
}
}

Categories