How to Load RSA Private Key From File - java

I am working on a test harness for a SAML 1.1 Assertion Consumer Service. The test must generate a signed SAMLResponse and submit it to the ACS encoded in Base64. The ACS must be able to verify the signed message using the X509 public cert.
I am able to build the SAMLResponse, adding the necessary assertions, etc. But when I try to sign the object I am running into problems. Here is a snippet of my current code:
String certPath = "mycert.pem";
File pubCertFile = new File(certPath);
BufferedInputStream bis = null;
try {
bis = new BufferedInputStream(new FileInputStream(pubCertFile));
} catch(FileNotFoundException e) {
throw new Exception("Could not locate certfile at '" + certPath + "'", e);
}
CertificateFactory certFact = null;
Certificate cert = null;
try {
certFact = CertificateFactory.getInstance("X.509");
cert = certFact.generateCertificate(bis);
} catch(CertificateException e) {
throw new Exception("Could not instantiate cert", e);
}
bis.close();
ArrayList<Certificate> certs = new ArrayList<Certificate>();
certs.add(cert);
String keyPath = "mykey.pem";
File privKeyFile = new File(keyPath);
try {
bis = new BufferedInputStream(new FileInputStream(privKeyFile));
} catch(FileNotFoundException e) {
throw new Exception("Could not locate keyfile at '" + keyPath + "'", e);
}
byte[] privKeyBytes = new byte[(int)privKeyFile.length()];
bis.read(privKeyBytes);
bis.close();
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
KeySpec ks = new PKCS8EncodedKeySpec(privKeyBytes);
RSAPrivateKey privKey = (RSAPrivateKey) keyFactory.generatePrivate(ks);
samlResponse.sign(Signature.getInstance("SHA1withRSA").toString(), privKey, certs);
The error occurs on the second-to-last line. I see the following in the console:
java.security.spec.InvalidKeySpecException: java.security.InvalidKeyException: invalid key format
Though not customary or secure, but for sake of this thread, I am providing the public cert and private key that I am using. I of course will re-create new ones once the problem is solved. :)
aj#mmdev0:~/$ cat mykey.pem
-----BEGIN RSA PRIVATE KEY-----
MIICXgIBAAKBgQDnbcLSlDFaDMhalcmQgclTFobpkHQHJtxMVGRlbv7zknttAVbY
1jzGjJ6HVupndzDxA9tbiMjQujmGlS/8g5IEbVsR9o6dmcmbvujtEZ2rHZ82tMYP
VAt2IoS/W/q2Rr1cAZ/zTKEmh0ZZjzCZFueLfrYPm3am5JLcXgVtbKwybQIDAQAB
AoGBAJ441oettYgBUUFNQv8/HGtn7Vjl38277cVptTH8DuZr8WJ3Fe8tmWONZBzX
eW6/eIBuyJvuCo1ZpFa0zJfxQ/Ph6QlQwdN50GNfh9RzSS6lDdfy8BRhc27sypXS
L6c5ljB6ql+pp3DdxFhJMOs3ZmBJdeyWe7uFrkngtnM1nxZBAkEA+1hbV1Q305wa
u8YMF1SlNIAfgLJ7buD43SEXle0egz405PFG8f8yDmvROwDiRceILGVrRbInd7Cb
dvJKr34WOQJBAOu2+reG44rNuiXeGX1MYg6TlWYyABm7PrTrhPZkedodOQB8p7zD
AqtDSK7RnDCoThndPW6kdNAeB+kG4ug5XdUCQHRDU8UajNRSkj8nhjJIkj6twWS7
qsMIR7Wp+An+7C1TWg5I2UNZg2MOVnNPnlseyAuZQjy0AvOnetJTk16IGWkCQQCL
FUbOr8rnhgiGe4yywDVDwJVw3aPtiuyvOCEWeabkqkWOIf+fg7m5cFQcwxXUKBsd
a8vp0yQSAQZN24Bb4i2ZAkEA8xGJFlFDY9HREWZnDey5STgbUeT1wYkyKcDsUrp1
kR/3BliGqSIfje+mSKDIZqaP+gai/8bIABYAsDP/t6+cuA==
-----END RSA PRIVATE KEY-----
aj#mmdev0:~/$ cat mycert.pem
-----BEGIN CERTIFICATE-----
MIID7zCCA1igAwIBAgIJAKrURaAaD6ulMA0GCSqGSIb3DQEBBQUAMIGsMQswCQYD
VQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xHDAa
BgNVBAoTE0hvc3R3YXkgQ29ycG9yYXRpb24xITAfBgNVBAsTGFJlc2VhcmNoIGFu
ZCBEZXZlbG9wbWVudDEYMBYGA1UEAxMPd3d3Lmhvc3R3YXkuY29tMR0wGwYJKoZI
hvcNAQkBFg5hakBob3N0d2F5LmNvbTAeFw0xMDA3MTQwMjMyMDhaFw0xMTA3MTQw
MjMyMDhaMIGsMQswCQYDVQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNV
BAcTB0NoaWNhZ28xHDAaBgNVBAoTE0hvc3R3YXkgQ29ycG9yYXRpb24xITAfBgNV
BAsTGFJlc2VhcmNoIGFuZCBEZXZlbG9wbWVudDEYMBYGA1UEAxMPd3d3Lmhvc3R3
YXkuY29tMR0wGwYJKoZIhvcNAQkBFg5hakBob3N0d2F5LmNvbTCBnzANBgkqhkiG
9w0BAQEFAAOBjQAwgYkCgYEA523C0pQxWgzIWpXJkIHJUxaG6ZB0BybcTFRkZW7+
85J7bQFW2NY8xoyeh1bqZ3cw8QPbW4jI0Lo5hpUv/IOSBG1bEfaOnZnJm77o7RGd
qx2fNrTGD1QLdiKEv1v6tka9XAGf80yhJodGWY8wmRbni362D5t2puSS3F4FbWys
Mm0CAwEAAaOCARUwggERMB0GA1UdDgQWBBQI/4Inzs6OH5IquItuKhIrhPb24zCB
4QYDVR0jBIHZMIHWgBQI/4Inzs6OH5IquItuKhIrhPb246GBsqSBrzCBrDELMAkG
A1UEBhMCVVMxETAPBgNVBAgTCElsbGlub2lzMRAwDgYDVQQHEwdDaGljYWdvMRww
GgYDVQQKExNIb3N0d2F5IENvcnBvcmF0aW9uMSEwHwYDVQQLExhSZXNlYXJjaCBh
bmQgRGV2ZWxvcG1lbnQxGDAWBgNVBAMTD3d3dy5ob3N0d2F5LmNvbTEdMBsGCSqG
SIb3DQEJARYOYWpAaG9zdHdheS5jb22CCQCq1EWgGg+rpTAMBgNVHRMEBTADAQH/
MA0GCSqGSIb3DQEBBQUAA4GBAA388zZp6UNryC/6o44hj7wTBQdzFFM5cs3B668A
ylAnnal+J8RMIeCHoMF4S7yFQtYdOiWeScgw3c7KXrhJK1X7fU3I+eb1t3Yp1cTI
htyzw14AoiICFalmlVgTCsn3+uh6AXP02PTkR8osdEpUOlWap4uzSKYNKc7tLOFd
4CkM
-----END CERTIFICATE-----
Thanks!

You need to convert your private key to PKCS8 format using following command:
openssl pkcs8 -topk8 -inform PEM -outform DER -in private_key_file -nocrypt > pkcs8_key
After this your java program can read it.

Two things. First, you must base64 decode the mykey.pem file yourself. Second, the openssl private key format is specified in PKCS#1 as the RSAPrivateKey ASN.1 structure. It is not compatible with java's PKCS8EncodedKeySpec, which is based on the SubjectPublicKeyInfo ASN.1 structure. If you are willing to use the bouncycastle library you can use a few classes in the bouncycastle provider and bouncycastle PKIX libraries to make quick work of this.
import java.io.BufferedReader;
import java.io.FileReader;
import java.security.KeyPair;
import java.security.Security;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openssl.PEMKeyPair;
import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
// ...
String keyPath = "mykey.pem";
BufferedReader br = new BufferedReader(new FileReader(keyPath));
Security.addProvider(new BouncyCastleProvider());
PEMParser pp = new PEMParser(br);
PEMKeyPair pemKeyPair = (PEMKeyPair) pp.readObject();
KeyPair kp = new JcaPEMKeyConverter().getKeyPair(pemKeyPair);
pp.close();
samlResponse.sign(Signature.getInstance("SHA1withRSA").toString(), kp.getPrivate(), certs);

Related

Convert RSA Private Key to DER Format in Java

I have a .pem file that contains the private key in this format:
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA3wVu5KhHVJjc9ri5mWKNDW5xXe08smNeu2GSAdBwEaGBHaWj
...
xqDDtaoYKUvwhuKHboTJMs9CtQyrVNk+TDSdfaEdTEWTNeu2UwaP4QBhA==
-----END RSA PRIVATE KEY-----
If I want to convert it manually using OpenSSL I would use this command:
openssl pkcs8 -topk8 -inform PEM -outform DER -in secret.pem -nocrypt secret.key
However, I want to do that programmatically using java but I couldn't figure out how.
Any help is much appreciated
The OpenSSL statement converts the PEM encoded private key in PKCS#1 format into a DER encoded key in PKCS#8 format.
In Java, importing the PEM encoded PKCS#1 private key can be done with e.g. BouncyCastle's PEMParser and JcaPEMKeyConverter (using the bcprov und bcpkix jars). The export can be accomplished with PrivateKey#getEncoded() which returns the DER encoded PKCS#8 private key:
import java.io.FileOutputStream;
import java.io.FileReader;
import java.security.KeyPair;
import org.bouncycastle.openssl.PEMKeyPair;
import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
...
String inputFile = "<path to PKCS#1 PEM key>";
String outputFile = "<path to PKCS#8 DER key>";
try (FileReader fileReader = new FileReader(inputFile);
PEMParser pemParser = new PEMParser(fileReader);
FileOutputStream outputStream = new FileOutputStream(outputFile)) {
// Import PEM encoded PKCS#1 private key
JcaPEMKeyConverter converter = new JcaPEMKeyConverter();
KeyPair keyPair = converter.getKeyPair((PEMKeyPair)pemParser.readObject());
// Export DER encoded PKCS#8 private key
byte[] privateKey = keyPair.getPrivate().getEncoded();
outputStream.write(privateKey, 0, privateKey.length);
}
You can run the task in a console in the background. Therefor you can use a ProcessBuilder.
//split the command every space and create a process builder
String[] commands = "your command written above...".split(" ");
ProcessBuilder pb = new ProcessBuilder(commands);
// starting the process
Process process = pb.start();
If you need to read the console output (instead of an output file) you can use an input stream:
// for reading the output from stream
BufferedReader stdInput = new BufferedReader(new InputStreamReader(
process.getInputStream()));
String s = null;
while ((s = stdInput.readLine()) != null) {
System.out.println(s);
}
I haven't tested this code in particular. If you need more info feel free to ask or visit this website: Process Builder

How to read PKCS8 file with empty password using BouncyCastle

I can generate a PKCS#8 encrypted file with empty password using OpenSSL:
~ $ openssl pkcs8 -topk8 -in ca_private.pem
Enter pass phrase for ca_private.pem:
Enter Encryption Password: <ENTER>
Verifying - Enter Encryption Password: <ENTER>
-----BEGIN ENCRYPTED PRIVATE KEY-----
MIIBvTBXBgkqhkiG9w0BBQ0wSjApBgkqhkiG9w0BBQwwHAQIsWq90VBNFMwCAggA
MAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUDBAEqBBCFtKOCdDUeRohccAqQZaDIBIIB
YG+ohLBKQ766BTCXXZ7wyAP1l0grcQPgnzI2XmEj33rBIMogS6l3oAN3Faos2I6n
PcUY+aNLQtDSbvPzF4ozd0oWYBTa60iYGboQQ2RolhRRTzNW6K2tWBWUB35v2rLV
VYu7xJMX+dr/PxzhEgaQ4Nerb7v7/EAn4fLv3zcW9f/tPbljKUAiKc/YYP+GjRjA
GyJThdVpyeK6Jflobc3V8gqL8Gk0MgeHmXuUR1+SthA6ia5havH7D/FMLvXxZtRK
CpWOQ8mJp7g7dbUf+qWTLX+dMPQFPZDEofdkCY2/J4dSkgNnPgp+1oxSVpEAAR9v
gWsRezU2KfFUEMIljYOT+s4ZhkeAGtA8qa8qnr0yv9uz1OkzFtrleNf0WV8wRqI7
azo/7ff9TbecseYlTRgR40nd2l3Z9RLMVhsS09vPffYDw3jt+Zqf3m7iEri6eSug
5bMcZTszaQsVT0HOfCcpQ1Q=
-----END ENCRYPTED PRIVATE KEY-----
When reading this with a PEMParser, it returns an instance of PKCS8EncryptedPrivateKeyInfo (as expected).
Trying to decrypt that with JceOpenSSLPKCS8DecryptorProviderBuilder, however, fails with
org.bouncycastle.pkcs.PKCSException: unable to read encrypted data: password empty
There is a hard coded check for empty password when creating the decryption key. Is there a way around this? OpenSSL surely deals just fine reading the PKCS#8 back...
Sample code:
import com.excelfore.api.lib.util.ApiCrypto;
import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.openssl.jcajce.JceOpenSSLPKCS8DecryptorProviderBuilder;
import org.bouncycastle.operator.InputDecryptorProvider;
import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo;
import java.io.FileReader;
class Scratch {
public static void main(String[] args) throws Exception {
try (FileReader fr = new FileReader(args[0])) {
Object whatIsIt = new PEMParser(fr).readObject();
if (whatIsIt instanceof PKCS8EncryptedPrivateKeyInfo) {
PKCS8EncryptedPrivateKeyInfo pInfo = (PKCS8EncryptedPrivateKeyInfo) whatIsIt;
InputDecryptorProvider provider =
new JceOpenSSLPKCS8DecryptorProviderBuilder().setProvider(ApiCrypto.bouncyCastleProvider).build(args[1].toCharArray());
pInfo.decryptPrivateKeyInfo(provider);
System.out.println("ok");
} else {
System.out.println("I don't want "+whatIsIt.getClass().getName());
}
}
}
}
Not sure if there is what you expect or not (do not test yet with empty passphrase)
public static KeyPair parseKeyPair(Path pemFile, String passPhrase) {
try(PEMParser pemParser = new PEMParser(Files.newBufferedReader(pemFile))) {
Object object = pemParser.readObject();
JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider(BouncyCastleProviderHolder.BC_PROVIDER);
if(object instanceof PEMEncryptedKeyPair) {
if(passPhrase == null)
throw new IllegalArgumentException("Pass phrase required for parsing RSA private key");
PEMDecryptorProvider decProv = new JcePEMDecryptorProviderBuilder().build(passPhrase.toCharArray());
return converter.getKeyPair(((PEMEncryptedKeyPair) object).decryptKeyPair(decProv));
}
if(object instanceof PEMKeyPair) {
return converter.getKeyPair((PEMKeyPair) object);
}
} catch(Exception e) {
throw new PicApplicationException("Couldn't parse pem to keypair", e);
}
throw new PicApplicationException("Couldn't parse pem to keypair");
}
You may pass empty passphrase to this line:
PEMDecryptorProvider decProv = new JcePEMDecryptorProviderBuilder().build("".toCharArray());
The BouncyCastle provider will not use an empty passphrase for PKCS8 decryption (actually any PBE). The SunJCE provider will, BUT you cannot specify it explicitly as the provider to use, because it doesn't handle AlgorithmParameters for the cipher by OID for PBES2; instead you must let the DecryptorProvider use the default JCA search and have Bouncy provider in that search after SunJCE; the simplest way is to put it at the end with Security.addProvider:
Security.addProvider(new BouncyCastleProvider());
try( Reader r = new FileReader(args[0]); PEMParser p = new PEMParser(r) ) {
JceOpenSSLPKCS8DecryptorProviderBuilder b = new JceOpenSSLPKCS8DecryptorProviderBuilder();
//DON'T:if( args.length>1 ) b.setProvider(args[1]);
InputDecryptorProvider d = b.build(new char[0]);
PrivateKeyInfo k0 = ((PKCS8EncryptedPrivateKeyInfo)p.readObject()).decryptPrivateKeyInfo(d);
PrivateKey k1 = new JcaPEMKeyConverter().getPrivateKey(k0);
System.out.println (k1.getAlgorithm());
}

How to read EC Private key in java which is in .pem file format

How to read EC private key which is in. Pem file using JAVA. While reading I am getting the following exception.
Caused by: java.security.InvalidKeyException: IOException : version mismatch: (supported: 00, parsed: 01
Actually my. Pem file contains private key in the following structure.
----BEGIN EC PRIVATE KEY ------
====+====+===
====+====+===
-----END EC PRIVATE KEY-----
From an EC PRIVATE KEY as requested (ex key.pem), I succeeded to import it in a java.security.KeyStore
transform private key from PEM => PKCS#8 DER
openssl pkcs8 -in key.pem -inform PEM -topk8 -nocrypt -out key-pkcs8.der -outform DER
load it (jvm version java-1.8.0-openjdk-1.8.0.201.b09-2.fc28.x86_64)
void loadPrivateKey(KeyStore ks, X509Certificate cert){
File privKeyFile = new File("key-pkcs8.der");
// read private key DER file
DataInputStream dis = new DataInputStream(new FileInputStream(privKeyFile));
byte[] privKeyBytes = new byte[(int)privKeyFile.length()];
dis.read(privKeyBytes);
dis.close();
KeyFactory kf = KeyFactory.getInstance("EC");
// decode private key
PKCS8EncodedKeySpec privSpec = new PKCS8EncodedKeySpec(privKeyBytes);
PrivateKey privKey = kf.generatePrivate(privSpec);
ks.setKeyEntry("key-alias", privKey, "password".toCharArray(), new Certificate[] {cert});
}

Verify Digital Signature on Android

I am developing an Android application that requires Digitally signing an html document.
The document resides in the DB, in a JSON form.
I'm signing the document locally using a BASH Script I found on some other SO question :
openssl dgst -sha1 someHTMLDoc.html > hash
openssl rsautl -sign -inkey privateKey.pem -keyform PEM -in hash > signature.bin
Private key was generated using :
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -pkeyopt rsa_keygen_pubexp:3 -out privateKey.pem
Public key was generated using :
openssl pkey -in privateKey.pem -out publicKey.pem -pubout
I want to verify the signature created in Signature.bin together with the data in someHTMLDoc.html, back in the application.
I am sending both the html and signature as JSON Object ex:
{ "data" : "<html><body></body></html>", "signature":"6598 13a9 b12b 21a9 ..... " }
The android application holds the PublicKey in shared prefs as follows :
-----BEGIN PUBLIC KEY-----
MIIBIDANBgkqhkiG9w0AAAEFAAOCAQ0AvniCAKCAQEAvni/NSEX3Rhx91HkJl85
\nx1noyYET ......
Notice the "\n" (newline) in there (was automatically added when copying string from publicKey.pem to Android Gradle Config.
Ok, after all preparations, now the question.
I am trying to validate the key with no success.
I am using the following code :
private boolean verifySignature(String data, String signature) {
InputStream is = null;
try {
is = new ByteArrayInputStream(Config.getDogbarPublic().getBytes("UTF-8")); //Read DogBar Public key
BufferedReader br = new BufferedReader(new InputStreamReader(is));
List<String> lines = new ArrayList<String>();
String line;
while ((line = br.readLine()) != null)
lines.add(line);
// removes the first and last lines of the file (comments)
if (lines.size() > 1 && lines.get(0).startsWith("-----") && lines.get(lines.size() - 1).startsWith("-----")) {
lines.remove(0);
lines.remove(lines.size() - 1);
}
// concats the remaining lines to a single String
StringBuilder sb = new StringBuilder();
for (String aLine : lines)
sb.append(aLine);
String key = sb.toString();
byte[] keyBytes = Base64.decode(key.getBytes("utf-8"), Base64.DEFAULT);
X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey publicKey = keyFactory.generatePublic(spec);
Signature signCheck = Signature.getInstance("SHA1withRSA"); //Instantiate signature checker object.
signCheck.initVerify(publicKey);
signCheck.update(data.getBytes());
return signCheck.verify(signature.getBytes()); //verify signature with public key
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
Can anyone help ? what am i doing wrong ?
Am i missing some byte conversion ? maybe the JSON object is affecting the signature ?
Should a signature contain the \n (linebreak) that the original file contains or should it be without in the JSON file ?
Thanks in advance for all the help, its highly appreciated.
Digital signature is a process of computing digest (function H) of data (C) and encrypting it with asymmetric encryption algorithm (function E) to produce cypher text (S):
S = E(H(C))
Signature verification takes the signature decrypts the given signature (function D) - which results in H(C) only if the public key used in decryption is paired with private key used in encryption, and computes the digest of data to check if the two digests match:
H(C) == D(E(H(C)))
It's clear from this that the bytes given to the hash function (C) must be exactly the same in order for the signature to validate.
In your case they are not, because when you're computing the digest using openssl dgst the output (H(C) on the right) is literally something like:
SHA1(someHTMLDoc.html)= 22596363b3de40b06f981fb85d82312e8c0ed511
And this is the input to the RSA encryption.
And when you're verifying the signature, the output of the digest (H(C) on the left) are the raw bytes, for instance in hex:
22596363b3de40b06f981fb85d82312e8c0ed511
So you end up encrypting bytes to produce (H(C) on the right):
0000000: 5348 4131 2873 6f6d 6548 746d 6c44 6f63 SHA1(someHtmlDoc
0000010: 2e68 746d 6c29 3d20 3232 3539 3633 3633 .html)= 22596363
0000020: 6233 6465 3430 6230 3666 3938 3166 6238 b3de40b06f981fb8
0000030: 3564 3832 3331 3265 3863 3065 6435 3131 5d82312e8c0ed511
0000040: 0a .
and comparing against bytes (H(C) on the left):
0000000: 2259 6363 b3de 40b0 6f98 1fb8 5d82 312e "Ycc..#.o...].1.
0000010: 8c0e d511 ....
Also you need to use -sign with openssl dgst in order to have proper output format (see Difference between openSSL rsautl and dgst).
So on the OpenSSL side do:
openssl dgst -sha1 -sign privateKey.pem someHTMLDoc.html > signature.bin
On the Java side do:
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.security.KeyFactory;
import java.security.Signature;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.X509EncodedKeySpec;
import org.spongycastle.util.io.pem.PemObject;
import org.spongycastle.util.io.pem.PemReader;
public class VerifySignature {
public static void main(final String[] args) throws Exception {
try (PemReader reader = publicKeyReader(); InputStream data = data(); InputStream signatureData = signature()) {
final PemObject publicKeyPem = reader.readPemObject();
final byte[] publicKeyBytes = publicKeyPem.getContent();
final KeyFactory keyFactory = KeyFactory.getInstance("RSA");
final X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(publicKeyBytes);
final RSAPublicKey publicKey = (RSAPublicKey) keyFactory.generatePublic(publicKeySpec);
final Signature signature = Signature.getInstance("SHA1withRSA");
signature.initVerify(publicKey);
final byte[] buffy = new byte[16 * 1024];
int read = -1;
while ((read = data.read(buffy)) != -1) {
signature.update(buffy, 0, read);
}
final byte[] signatureBytes = new byte[publicKey.getModulus().bitLength() / 8];
signatureData.read(signatureBytes);
System.out.println(signature.verify(signatureBytes));
}
}
private static InputStream data() throws FileNotFoundException {
return new FileInputStream("someHTMLDoc.html");
}
private static PemReader publicKeyReader() throws FileNotFoundException {
return new PemReader(new InputStreamReader(new FileInputStream("publicKey.pem")));
}
private static InputStream signature() throws FileNotFoundException {
return new FileInputStream("signature.bin");
}
}
I've used Spongy Castle for PEM decoding of the public key to make things a bit more readable and easier to use.
If you have a digitally signed XML file (downloaded from the web) and a certificate (.cer file) and you want to verify the digital signature in an android app then here is the code:
You need two things xmlFilePath and certificateFilePath
boolean verifySignature() {
boolean valid = false;
try {
File file = new File("xmlFilePath");
DocumentBuilderFactory f = DocumentBuilderFactory.newInstance();
f.setNamespaceAware(true);
Document doc = f.newDocumentBuilder().parse(file);
NodeList nodes = doc.getElementsByTagNameNS(Constants.SignatureSpecNS, "Signature");
if (nodes.getLength() == 0) {
throw new Exception("Signature NOT found!");
}
Element sigElement = (Element) nodes.item(0);
XMLSignature signature = new XMLSignature(sigElement, "");
CertificateFactory cf = CertificateFactory.getInstance("X.509");
InputStream ims = new InputStream("certificateFilePath");
X509Certificate cert = (X509Certificate) cf.generateCertificate(ims);
if (cert == null) {
PublicKey pk = signature.getKeyInfo().getPublicKey();
if (pk == null) {
throw new Exception("Did not find Certificate or Public Key");
}
valid = signature.checkSignatureValue(pk);
} else {
valid = signature.checkSignatureValue(cert);
}
} catch (Exception e) {
e.printStackTrace();
Toast.makeText(this, "Failed signature " + e.getMessage(), Toast.LENGTH_SHORT).show();
}
return valid;
}
If you want to do it in java but not in android studio. Here is the code:
public static boolean isXmlDigitalSignatureValid(String signedXmlFilePath,
String pubicKeyFilePath) throws Exception {
boolean validFlag;
File file = new File(signedXmlFilePath);
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.parse(file);
doc.getDocumentElement().normalize();
NodeList nl = doc.getElementsByTagNameNS(XMLSignature.XMLNS, "Signature");
if (nl.getLength() == 0) {
throw new Exception("No XML Digital Signature Found, document is discarded");
}
FileInputStream fileInputStream = new FileInputStream(pubicKeyFilePath);
CertificateFactory cf = CertificateFactory.getInstance("X.509");
X509Certificate cert = (X509Certificate) cf.generateCertificate(fileInputStream);
PublicKey publicKey = cert.getPublicKey();
DOMValidateContext valContext = new DOMValidateContext(publicKey, nl.item(0));
XMLSignatureFactory fac = XMLSignatureFactory.getInstance("DOM");
XMLSignature signature = fac.unmarshalXMLSignature(valContext);
validFlag = signature.validate(valContext);
return validFlag;
}
The reason is that you will need to add dependency if you use the same code in android studio, sometimes confusing also.
If you are interested in reading digital signature documents, you can read www.xml.com/post It is an interesting document for understanding the need for a digital signature.

Import PEM into Java Key Store

I am trying to connect to an SSL server which requires me to authenticate myself. In order to use SSL over Apache MINA I need a suitable JKS file. However, I have only been given a .PEM file.
How would I go about creating a JKS file from a PEM file?
First, convert your certificate in a DER format :
openssl x509 -outform der -in certificate.pem -out certificate.der
And after, import it in the keystore :
keytool -import -alias your-alias -keystore cacerts -file certificate.der
If you only want to import a certificate in PEM format into a keystore, keytool will do the job:
keytool -import -alias *alias* -keystore cacerts -file *cert.pem*
If you need an easy way to load PEM files in Java without having to deal with external tools (opensll, keytool), here is my code I use in production :
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.security.KeyFactory;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.interfaces.RSAPrivateKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.ArrayList;
import java.util.List;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLServerSocketFactory;
import javax.xml.bind.DatatypeConverter;
public class PEMImporter {
public static SSLServerSocketFactory createSSLFactory(File privateKeyPem, File certificatePem, String password) throws Exception {
final SSLContext context = SSLContext.getInstance("TLS");
final KeyStore keystore = createKeyStore(privateKeyPem, certificatePem, password);
final KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
kmf.init(keystore, password.toCharArray());
final KeyManager[] km = kmf.getKeyManagers();
context.init(km, null, null);
return context.getServerSocketFactory();
}
/**
* Create a KeyStore from standard PEM files
*
* #param privateKeyPem the private key PEM file
* #param certificatePem the certificate(s) PEM file
* #param the password to set to protect the private key
*/
public static KeyStore createKeyStore(File privateKeyPem, File certificatePem, final String password)
throws Exception, KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException {
final X509Certificate[] cert = createCertificates(certificatePem);
final KeyStore keystore = KeyStore.getInstance("JKS");
keystore.load(null);
// Import private key
final PrivateKey key = createPrivateKey(privateKeyPem);
keystore.setKeyEntry(privateKeyPem.getName(), key, password.toCharArray(), cert);
return keystore;
}
private static PrivateKey createPrivateKey(File privateKeyPem) throws Exception {
final BufferedReader r = new BufferedReader(new FileReader(privateKeyPem));
String s = r.readLine();
if (s == null || !s.contains("BEGIN PRIVATE KEY")) {
r.close();
throw new IllegalArgumentException("No PRIVATE KEY found");
}
final StringBuilder b = new StringBuilder();
s = "";
while (s != null) {
if (s.contains("END PRIVATE KEY")) {
break;
}
b.append(s);
s = r.readLine();
}
r.close();
final String hexString = b.toString();
final byte[] bytes = DatatypeConverter.parseBase64Binary(hexString);
return generatePrivateKeyFromDER(bytes);
}
private static X509Certificate[] createCertificates(File certificatePem) throws Exception {
final List<X509Certificate> result = new ArrayList<X509Certificate>();
final BufferedReader r = new BufferedReader(new FileReader(certificatePem));
String s = r.readLine();
if (s == null || !s.contains("BEGIN CERTIFICATE")) {
r.close();
throw new IllegalArgumentException("No CERTIFICATE found");
}
StringBuilder b = new StringBuilder();
while (s != null) {
if (s.contains("END CERTIFICATE")) {
String hexString = b.toString();
final byte[] bytes = DatatypeConverter.parseBase64Binary(hexString);
X509Certificate cert = generateCertificateFromDER(bytes);
result.add(cert);
b = new StringBuilder();
} else {
if (!s.startsWith("----")) {
b.append(s);
}
}
s = r.readLine();
}
r.close();
return result.toArray(new X509Certificate[result.size()]);
}
private static RSAPrivateKey generatePrivateKeyFromDER(byte[] keyBytes) throws InvalidKeySpecException, NoSuchAlgorithmException {
final PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes);
final KeyFactory factory = KeyFactory.getInstance("RSA");
return (RSAPrivateKey) factory.generatePrivate(spec);
}
private static X509Certificate generateCertificateFromDER(byte[] certBytes) throws CertificateException {
final CertificateFactory factory = CertificateFactory.getInstance("X.509");
return (X509Certificate) factory.generateCertificate(new ByteArrayInputStream(certBytes));
}
}
Have fun.
I've developed http://code.google.com/p/java-keyutil/ which imports PEM certificates straight into a Java keystore. Its primary purpose is to import a multi-part PEM Operating System certificate bundles such as ca-bundle.crt. These often includes headers which keytool cannot handle
</self promotion>
In my case I had a pem file which contained two certificates and an encrypted private key to be used in mutual SSL authentication.
So my pem file looked like this:
-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: DES-EDE3-CBC,C8BF220FC76AA5F9
...
-----END RSA PRIVATE KEY-----
-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
Here is what I did
Split the file into three separate files, so that each one contains just one entry,
starting with ---BEGIN.. and ending with ---END.. lines. Lets assume we now have three files: cert1.pem, cert2.pem, and pkey.pem.
Convert pkey.pem into DER format using openssl and the following syntax:
openssl pkcs8 -topk8 -nocrypt -in pkey.pem -inform PEM -out pkey.der -outform DER
Note, that if the private key is encrypted you need to supply a password( obtain it from the supplier of the original pem file ) to convert to DER format,
openssl will ask you for the password like this: "enter a passphrase for pkey.pem: ".
If conversion is successful, you will get a new file called pkey.der.
Create a new java keystore and import the private key and the certificates:
String keypass = "password"; // this is a new password, you need to come up with to protect your java key store file
String defaultalias = "importkey";
KeyStore ks = KeyStore.getInstance("JKS", "SUN");
// this section does not make much sense to me,
// but I will leave it intact as this is how it was in the original example I found on internet:
ks.load( null, keypass.toCharArray());
ks.store( new FileOutputStream ( "mykeystore" ), keypass.toCharArray());
ks.load( new FileInputStream ( "mykeystore" ), keypass.toCharArray());
// end of section..
// read the key file from disk and create a PrivateKey
FileInputStream fis = new FileInputStream("pkey.der");
DataInputStream dis = new DataInputStream(fis);
byte[] bytes = new byte[dis.available()];
dis.readFully(bytes);
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
byte[] key = new byte[bais.available()];
KeyFactory kf = KeyFactory.getInstance("RSA");
bais.read(key, 0, bais.available());
bais.close();
PKCS8EncodedKeySpec keysp = new PKCS8EncodedKeySpec ( key );
PrivateKey ff = kf.generatePrivate (keysp);
// read the certificates from the files and load them into the key store:
Collection col_crt1 = CertificateFactory.getInstance("X509").generateCertificates(new FileInputStream("cert1.pem"));
Collection col_crt2 = CertificateFactory.getInstance("X509").generateCertificates(new FileInputStream("cert2.pem"));
Certificate crt1 = (Certificate) col_crt1.iterator().next();
Certificate crt2 = (Certificate) col_crt2.iterator().next();
Certificate[] chain = new Certificate[] { crt1, crt2 };
String alias1 = ((X509Certificate) crt1).getSubjectX500Principal().getName();
String alias2 = ((X509Certificate) crt2).getSubjectX500Principal().getName();
ks.setCertificateEntry(alias1, crt1);
ks.setCertificateEntry(alias2, crt2);
// store the private key
ks.setKeyEntry(defaultalias, ff, keypass.toCharArray(), chain );
// save the key store to a file
ks.store(new FileOutputStream ( "mykeystore" ),keypass.toCharArray());
(optional) Verify the content of your new key store:
$ keytool -list -keystore mykeystore -storepass password
Keystore type: JKS
Keystore provider: SUN
Your keystore contains 3 entries:
cn=...,ou=...,o=.., Sep 2, 2014, trustedCertEntry,
Certificate fingerprint (SHA1): 2C:B8: ...
importkey, Sep 2, 2014, PrivateKeyEntry,
Certificate fingerprint (SHA1): 9C:B0: ...
cn=...,o=...., Sep 2, 2014, trustedCertEntry,
Certificate fingerprint (SHA1): 83:63: ...
(optional) Test your certificates and private key from your new key store against your SSL server:
( You may want to enable debugging as an VM option: -Djavax.net.debug=all )
char[] passw = "password".toCharArray();
KeyStore ks = KeyStore.getInstance("JKS", "SUN");
ks.load(new FileInputStream ( "mykeystore" ), passw );
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
kmf.init(ks, passw);
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(ks);
TrustManager[] tm = tmf.getTrustManagers();
SSLContext sclx = SSLContext.getInstance("TLS");
sclx.init( kmf.getKeyManagers(), tm, null);
SSLSocketFactory factory = sclx.getSocketFactory();
SSLSocket socket = (SSLSocket) factory.createSocket( "192.168.1.111", 443 );
socket.startHandshake();
//if no exceptions are thrown in the startHandshake method, then everything is fine..
Finally register your certificates with HttpsURLConnection if plan to use it:
char[] passw = "password".toCharArray();
KeyStore ks = KeyStore.getInstance("JKS", "SUN");
ks.load(new FileInputStream ( "mykeystore" ), passw );
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
kmf.init(ks, passw);
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(ks);
TrustManager[] tm = tmf.getTrustManagers();
SSLContext sclx = SSLContext.getInstance("TLS");
sclx.init( kmf.getKeyManagers(), tm, null);
HostnameVerifier hv = new HostnameVerifier()
{
public boolean verify(String urlHostName, SSLSession session)
{
if (!urlHostName.equalsIgnoreCase(session.getPeerHost()))
{
System.out.println("Warning: URL host '" + urlHostName + "' is different to SSLSession host '" + session.getPeerHost() + "'.");
}
return true;
}
};
HttpsURLConnection.setDefaultSSLSocketFactory( sclx.getSocketFactory() );
HttpsURLConnection.setDefaultHostnameVerifier(hv);
I used Keystore Explorer
Open JKS with a private key
Examine signed PEM from CA
Import key
Save JKS
I'm always forgetting how to do this because it's something that I just do once in a while, this is one possible solution, and it just works:
Go to your favourite browser and download the main certificate from the secured website.
Execute the two following lines of code:
$ openssl x509 -outform der -in GlobalSignRootCA.crt -out GlobalSignRootCA.der
$ keytool -import -alias GlobalSignRootCA -keystore GlobalSignRootCA.jks -file GlobalSignRootCA.der
If executing in Java SE environment add the following options:
$ java -Djavax.net.ssl.trustStore=GlobalSignRootCA.jks -Djavax.net.ssl.trustStorePassword=trustStorePassword -jar MyJar.jar
Or add the following to the java code:
System.setProperty("javax.net.ssl.trustStore", "GlobalSignRootCA.jks");
System.setProperty("javax.net.ssl.trustStorePassword","trustStorePassword");
The other option for step 2 is to just using the keytool command. Bellow is an example with a chain of certificates:
$ keytool -import -file org.eu.crt -alias orgcrt -keystore globalsignrs.jks
$ keytool -import -file GlobalSignOrganizationValidationCA-SHA256-G2.crt -alias globalsignorgvalca -keystore globalsignrs.jks
$ keytool -import -file GlobalSignRootCA.crt -alias globalsignrootca -keystore globalsignrs.jks
There is also a GUI tool that allows visual JKS creation and certificates importing.
http://portecle.sourceforge.net/
Portecle is a user friendly GUI application for creating, managing and examining keystores, keys, certificates, certificate requests, certificate revocation lists and more.
I got it from internet. It works pretty good for pem files that contains multiple entries.
#!/bin/bash
pemToJks()
{
# number of certs in the PEM file
pemCerts=$1
certPass=$2
newCert=$(basename "$pemCerts")
newCert="${newCert%%.*}"
newCert="${newCert}"".JKS"
##echo $newCert $pemCerts $certPass
CERTS=$(grep 'END CERTIFICATE' $pemCerts| wc -l)
echo $CERTS
# For every cert in the PEM file, extract it and import into the JKS keystore
# awk command: step 1, if line is in the desired cert, print the line
# step 2, increment counter when last line of cert is found
for N in $(seq 0 $(($CERTS - 1))); do
ALIAS="${pemCerts%.*}-$N"
cat $pemCerts |
awk "n==$N { print }; /END CERTIFICATE/ { n++ }" |
$KEYTOOLCMD -noprompt -import -trustcacerts \
-alias $ALIAS -keystore $newCert -storepass $certPass
done
}
pemToJks <pem to import> <pass for new jks>
Although this question is pretty old and it has already a-lot answers, I think it is worth to provide an alternative. Using native java classes makes it very verbose to just use pem files and almost forces you wanting to convert the pem files into p12 or jks files as using p12 or jks files are much easier. I want to give anyone who wants an alternative for the already provided answers.
GitHub - SSLContext Kickstart
var keyManager = PemUtils.loadIdentityMaterial("certificate-chain.pem", "private-key.pem");
var trustManager = PemUtils.loadTrustMaterial("some-trusted-certificate.pem");
var sslFactory = SSLFactory.builder()
.withIdentityMaterial(keyManager)
.withTrustMaterial(trustManager)
.build();
var sslContext = sslFactory.getSslContext();
I need to provide some disclaimer here, I am the library maintainer
OpenJDK keytool handles PEM certificates natively now (and has been for a few releases but I'm not sure since when).
keytool recommends against specifying the cacerts file path like any other keystore but using -cacerts option instead.
So the command line which works with OpenJDK 18 (and probably many earlier versions) is:
keytool -cacerts -import -alias <alias> -file <path_to_cert.pem>
If you want to do this on Android, there are a few minor tweaks that can be added to the PEMImporter class from this great answer. To summarize those:
First I used Android Studio to translate into Kotlin (this is not necessary, I just prefer it). The original class contained all static methods, so this resulted in a named object instead.
javax.xml.bind.DatatypeConverter was removed from the java core in version 11. Although you can still import it (in gradle:
implementation("javax.xml.bind:jaxb-api:2.4.0-b180830.0359"), this does not work on Android and it is simpler to use java.util.Base64 for the tasks it performed (namely, translating base64 to bytes). The output is identical (although you need to trim line endings in the raw PEM data).
Replace SunX509 and JKS with PKIX. It is only necessary in the first case, and in the second case probably inconsequential; I do not think it has any significance if you are populating a KeyStore with already initialized PrivateKey etc. objects as is done here. I in fact used getDefaultAlgorithm() in place of "JKS" in createKeyStore, and although that default is currently "jks", the key store worked fine in a KeyManagerFactory created with PKIX as the algorithm.
I should also note that I am not using the createSSLFactory method and am instead using the output of createKeyStore() to initialize a KeyManagerFactory and extract KeyManagers used to initialize anSSLContext:
val context = SSLContext.getInstance(contextProtocol)
val password = String(...)
val ks : KeyStore = try {
PEMImporter.createKeyStore(
File(keyPath),
File(certPath),
password
)
} catch (ex : Throwable) { ... }
val kmf = KeyManagerFactory.getInstance("PKIX")
try { kmf.init(ks, password.toCharArray()) }
It probably doesn't matter much what the password is here since the PEMImporter works with already unencrypted key data -- unless you want to write a PrivateKey back to a file (I presume getEncoded() is a step in that direction but I've never had need to do this). It just has to match in the two uses above.
I also added a catch for RSA PRIVATE KEYS, which, as it turns out, are
not the same as PEM keys with no "RSA" in the first line; a subtlety I was previously unaware of. The former are PKCS #1, the latter PKCS #8; you should be able to use whatever tool you normally use to deal with these (eg., when creating keys with certtool, use --pkcs8). Note that doesn't mean PKCS #8 keys aren't potentially RSA based, it's just about the protocol used to store and extract the key data.
Here's my Android version of PEMImporter in Kotlin:
import java.io.*
import java.security.*
import java.security.cert.CertificateException
import java.security.cert.CertificateFactory
import java.security.cert.X509Certificate
import java.security.interfaces.RSAPrivateKey
import java.security.spec.InvalidKeySpecException
import java.security.spec.PKCS8EncodedKeySpec
import java.util.*
import javax.net.ssl.KeyManagerFactory
import javax.net.ssl.SSLContext
import javax.net.ssl.SSLServerSocketFactory
object PEMImporter {
#Throws(Exception::class)
fun createSSLFactory(
privateKeyPem: File,
certificatePem: File?,
password: String
): SSLServerSocketFactory {
val context = SSLContext.getInstance("TLS")
val keystore = createKeyStore(privateKeyPem, certificatePem, password)
val kmf = KeyManagerFactory.getInstance("PKIX")
kmf.init(keystore, password.toCharArray())
val km = kmf.keyManagers
context.init(km, null, null)
return context.serverSocketFactory
}
/**
* Create a KeyStore from standard PEM files
*
* #param privateKeyPem the private key PEM file
* #param certificatePem the certificate(s) PEM file
* #param password the password to set to protect the private key
*/
#Throws(
Exception::class,
KeyStoreException::class,
IOException::class,
NoSuchAlgorithmException::class,
CertificateException::class
)
fun createKeyStore(privateKeyPem: File, certificatePem: File?, password: String): KeyStore {
val cert = createCertificates(certificatePem)
val keystore = KeyStore.getInstance(KeyStore.getDefaultType())
keystore.load(null)
// Import private key
val key = createPrivateKey(privateKeyPem)
keystore.setKeyEntry(privateKeyPem.name, key, password.toCharArray(), cert)
return keystore
}
#Throws(Exception::class)
private fun createPrivateKey(privateKeyPem: File): PrivateKey {
val r = BufferedReader(FileReader(privateKeyPem))
var s = r.readLine()
if (s.contains("BEGIN RSA PRIVATE KEY")) {
r.close()
throw IllegalArgumentException(privateKeyPem.name +
" is a PKCS #1 key, not a PKCS #8.")
}
if (s == null || (!s.contains("BEGIN PRIVATE KEY"))) {
r.close()
throw IllegalArgumentException("Bad private key header (${privateKeyPem.name}): $s")
}
val b = StringBuilder()
s = ""
while (s != null) {
if (s.contains("END PRIVATE KEY")) {
break
}
b.append(s.trimEnd())
s = r.readLine()
}
r.close()
val hexString = b.toString()
// Base64 is in java.util.
val bytes = Base64.getDecoder().decode(hexString)
return generatePrivateKeyFromDER(bytes)
}
#Throws(Exception::class)
private fun createCertificates(certificatePem: File?): Array<X509Certificate> {
val result = mutableListOf<X509Certificate>()
val r = BufferedReader(FileReader(certificatePem))
var s = r.readLine()
if (s == null || !s.contains("BEGIN CERTIFICATE")) {
r.close()
throw IllegalArgumentException("No CERTIFICATE found")
}
var b = StringBuilder()
while (s != null) {
if (s.contains("END CERTIFICATE")) {
val hexString = b.toString()
val bytes = Base64.getDecoder().decode(hexString.trimEnd())
val cert = generateCertificateFromDER(bytes)
result.add(cert)
b = StringBuilder()
} else {
if (!s.startsWith("----")) {
b.append(s)
}
}
s = r.readLine()
}
r.close()
return result.toTypedArray()
}
#Throws(InvalidKeySpecException::class, NoSuchAlgorithmException::class)
private fun generatePrivateKeyFromDER(keyBytes: ByteArray): RSAPrivateKey {
val spec = PKCS8EncodedKeySpec(keyBytes)
val factory = KeyFactory.getInstance("RSA")
return factory.generatePrivate(spec) as RSAPrivateKey
}
#Throws(CertificateException::class)
private fun generateCertificateFromDER(certBytes: ByteArray): X509Certificate {
val factory = CertificateFactory.getInstance("X.509")
return factory.generateCertificate(ByteArrayInputStream(certBytes)) as X509Certificate
}
}

Categories