Using java keytool to encrypt imported paswords with PBEWithHmacSHA256AndAES_128 - java

I am looking to store sensitive passwords in a java keystore using java keytool's importpass. I am using Oracle java version 1.8.0_212, and cannot upgrade from java 8 at the moment.
I have created a PKCS12 keystore with the following command:
keytool -keystore test-keystore.p12 -genkey -storetype PKCS12 -alias test
I then imported a password into the keystore using:
keytool -importpass -storetype pkcs12 -alias protectedPass -keystore test-keystore.p12
This worked, but this uses the encryption algorithm "PBEWithMD5AndDES" by default, which isn't particularly secure. I am now trying to use "PBEWithHmacSHA256AndAES_128" from the Java Security Standard Algorithm Names doc but having issues getting this to work.
I've tried specifying the keyalg like this:
keytool -importpass -keyalg PBEWithHmacSHA256AndAES_128 -storetype pkcs12 -alias protectedPass -keystore test-keystore.p12 -v
and while this doesn't cause an error, it doesn't seem to actually affect the output. The secret is key still generated with PBEWithMD5AndDES:
D:\temp>keytool -importpass -keyalg PBEWithHmacSHA256AndAES_128 -storetype pkcs12 -alias protectedPass -keystore test-keystore.p12 -v
Enter keystore password:
Enter the password to be stored:
Re-enter password:
Generated PBEWithMD5AndDES secret key
[Storing test-keystore.p12]
I can see examples, such as in Java Keystores the Gory Details, of people using KeyStore.PasswordProtection to use algorithms like this, but I wanted to use the keytool if possible.
Am I missing something key here or trying to do something silly?
EDIT:
Tried to do something similar programmatically to figure out what I'm doing wrong and had no luck using PKCS12 keystores with this algorithm. However, jceks seems to work. I think this is to do with it using the SunJCE provider instead. Is there some parameter I am missing to get this algorithm working with pcks12? Or is there some other approach I could take?
I have provided some simple demo code below. If you swap "JCEKS" for "PKCS12" it will throw java.security.NoSuchAlgorithmException: unrecognized algorithm name: PBEWithHmacSHA256AndAES_128.
Demo Code:
static void encrypt() throws Exception {
KeyStore keyStore = KeyStore.getInstance("JCEKS");
keyStore.load(null, "changeit".toCharArray());
KeyStore.PasswordProtection keyStorePP = new KeyStore.PasswordProtection("changeit".toCharArray());
SecretKeyFactory pbeKeyFactory = SecretKeyFactory.getInstance("PBEWithHmacSHA256AndAES_128");
SecretKey pbeKey = pbeKeyFactory.generateSecret(new PBEKeySpec("testpassword".toCharArray()));
keyStore.setEntry(SECRET_KEY_ALIAS, new KeyStore.SecretKeyEntry(
pbeKey), keyStorePP);
FileOutputStream outputStream = new FileOutputStream(FILE_PATH);
keyStore.store(outputStream, "changeit".toCharArray());
}
static void decrypt() throws Exception {
KeyStore keyStore = KeyStore.getInstance("JCEKS");
FileInputStream fileInputStream = new FileInputStream(FILE_PATH);
keyStore.load(fileInputStream, "changeit".toCharArray());
KeyStore.PasswordProtection keyStorePP = new KeyStore.PasswordProtection("changeit".toCharArray());
KeyStore.SecretKeyEntry ske =
(KeyStore.SecretKeyEntry)keyStore.getEntry(SECRET_KEY_ALIAS, keyStorePP);
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBEWithHmacSHA256AndAES_128");
PBEKeySpec keySpec = (PBEKeySpec)factory.getKeySpec(
ske.getSecretKey(),
PBEKeySpec.class);
System.out.println(new String(keySpec.getPassword()));
}

You need to keep separate the algorithm a key is for and the way it is protected.
A Java keystore (depending on type, including JCEKS and PKCS12 but not JKS) can contain three kinds of entries. A SecretKeyEntry in general contains a secret key which is typically valid for a particular cipher algorithm, identified in the SecretKey object within the entry. But for password-based algorithms, the 'key' object is actually a generic 'PBEKey' which contains a password and AFAICT can be used for any PBE algorithm. It appears the 'algorithm' for such keys is actually stored as PBEwithMD5andDES, I suspect because that's the only scheme from original PKCS5 (i.e. PBES1) Java implements. However, my Oracle 8u212 keytool says more generically Generated PBE secret key in this case, not PBEwithMD5andDES as you show.
The stored secret key (and stored private key also) can be and normally is 'protected' in the keystore by being encrypted with a password-based encryption algorithm; this 'protection' algorithm is different from and in general not related to the algorithm(s) that the key or password will be used with after retrieval.
keytool -importpass -keyalg PBEWithHmacSHA256AndAES_128 attempts to specify the algorithm the key-really-password is nominally to be used with when retrieved, but not how it is protected, which for PKCS12 in most older versions including yours is actually PBE-SHA1-TripleDES (the same as for privatekeys) -- you can see this, with a little work, using OpenSSL. And in my 8u212 keytool specifying -keyalg fails for PKCS12 with an exception claiming it can't protect because there are no parameters for that algorithm (although I think the problem is serializing, not actually encrypting) much as you say your code does for PKCS12, but Java does have these parameters -- although in a different provider (SunJCE vs SunJSSE) which might be messing it up somehow; as you say JCEKS is in SunJCE.
Similarly your code generates a key to be used with that algorithm, but protected the default way. To change the protection you need to specify it in PasswordProtection, as stated in the page you link; see https://stackoverflow.com/a/47389384/ (by the same author) for an example.
Recent versions of Java -- 8u301 and 11.0.12 up -- have a new feature to configure the protection algorithms for PKCS12 with security properties in the java.security file which is in JRE/lib/security for 8 down or JRE/conf/security for 9 up. This applies to both code you write if not overridden as above, and to keytool (which can't override). However, those versions (except for 12 through 16, which are now EOL) also now default to PBEwithHmacSHA256andAES_256 which is already better than you asked for, so you probably don't need to change anything -- just use 8u301 up.
Note JCEKS uses a Sun-defined PBE-MD5-TripleDES similar to, but not, PBES1, while JKS doesn't support secretkey entries but for privatekey entries uses a deliberately weak (ITAR-friendly back in the 1990s) Sun-custom algorithm; these cannot be changed.

Related

How to convert Curl SSL requests into Java code

Curl allows making 2-way SSL requests by specifying the certificate and private key files like the two curl requests below
First get an access token:
$ curl https://connect2.server.com/auth/token
-H "Authorization: Basic $BASIC_AUTH"
--cert ~/certs/certpath/fullchain.pem
--key ~/certs/certpath/privkey.pem
Then use the token to access the API:
$ curl https://connect2.server.com/api/public/preview1/orgs/$ORGUUID/API
-H "Authorization: Bearer $ACCESS_TOKEN"
--cert ~/certs/certpath/fullchain.pem
--key ~/certs/certpath/privkey.pem
Question:
How to implement the above requests in Java? What libraries are required? Java seems to use p12 file, however, we have .pem files.
‌1. You can convert PEM privatekey plus chain to a PKCS12 file using openssl pkcs12 -export. That is not programming or development and no longer ontopic here, but there are dozens of Qs about this here going back many years when topicality was broader, as well as in other Stacks (security.SX, serverfault, superuser, maybe more).
‌2. If you don't have or dislike OpenSSL, you can read those files (among others) into any kind of Java keystore (JCEKS, JKS, PKCS12, and several BouncyCastle variants you probably don't want) using the software from https://www.keystore-explorer.org . That's also offtopic, and I've seen some existing Qs mention it but not many.
‌3. If you want to do this with your own code, which is ontopic, and assuming your curl uses OpenSSL or at least those files are OpenSSL formats:
3.0 Java can read PEM cert sequence with CertificateFactory.getInstance("X.509") then generateCertificates(InputStream) (note s) -- the doc is a bit sketchy but this method actually can handle separate certs as DER or PEM (which you apparently have), or PKCS7 containing certs as a single blob (commonly called p7b or p7c) ditto.
3.1 if that privkey file is PKCS8-unencrypted, i.e. if the PEM labels are BEGIN/END PRIVATE KEY with no other word between, that case can be handled by standard Java, assuming you know what algorithm it is for (which if necessary you can determine from the first=leaf certificate). Delete the BEGIN/END lines and decode the rest from base64 to binary either ignoring linebreaks (with Base64.getMimeDecoder()) or with .getDecoder() after deleting the linebreaks. Put the result in PKCS8EncodedKeySpec and feed it to generatePrivate in a KeyFactory instance for the correct algorithm.
3.2 BouncyCastle (bcpkix+bcprov) can read all the PEM formats for privatekey used by OpenSSL with PEMParser and JcaPEMKeyConverter and if applicable a DecryptorBuilder. There are many existing Qs on this that you can find with that fairly-unique classname. This does mean a dependency on Bouncy.
3.3 if you don't have or don't want Bouncy and have a format other than PKCS8-unencrypted, life gets harder. You could avoid this by using OpenSSL to convert the privkey file to PKCS8-unencrypted putting you back in #3.1, but if you do that you might as well go way back to #1 and use OpenSSL to convert the lot to PKCS12 in one foop.
if you have an OpenSSL 'traditional' algorithm-specific format like BEGIN/END RSA PRIVATE KEY or BEGIN/END EC PRIVATE KEY, and the first two lines after BEGIN are NOT Proc-type: 4 and DEK-info, you can base64-decode the body and convert it to PKCS8 by adding a (DER) prefix in front that specifies the algorithm and 'wraps' the algorithm-specific part. I think there are dupes for this but I can't presently find any; if this case applies and you identify the algorithm I'll add it.
if you have a 'traditional' format that does have Proc-type: 4 and DEK-info, or you have BEGIN/END ENCRYPTED PRIVATE KEY, those are encrypted. Making sense of them with only standard Java is a fair bit of work which I'll do only if you can't use the other options and specify exactly what case you have.
Following are the steps & code to add SSL certificates into HTTP Post request.
STEP 1. CONVERT PEM CERTIFICATE TO P12 FORMAT
openssl pkcs12 -export -out cacert.p12 -inkey /etc/letsencrypt/archive/server/privkey21.pem -in /etc/letsencrypt/archive/server/cert21.pem -certfile /etc/letsencrypt/archive/server/chain21.pem -passin pass:PWD -passout pass:PWD
STEP 2. (OPTIONAL NOT REQUIRED) CONVERT CERTIFICATE P12 TO JKS FORMAT
keytool -importkeystore -srckeystore cacert.p12 -srcstoretype pkcs12 -destkeystore cacert.jks
STEP 3. ADD CERTIFICATE TO HTTP POST REQUEST THROUGH SSLSocketFactory
/**
* This function is responsible to create createSSLSocketFactory with SSL certificate
* #return
*/
public static SSLSocketFactory createSSLSocketFactory(){
try
{
FileInputStream f5 = new FileInputStream(new File("/etc/letsencrypt/archive/server/cacert21.p12"));
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
KeyStore ks1 = KeyStore.getInstance(KeyStore.getDefaultType());
ks1.load(f5, "PWD".toCharArray());
kmf.init(ks1, "PWD".toCharArray());
SSLContext sslContext = SSLContext.getInstance("SSL");
sslContext.init(kmf.getKeyManagers(), null, null);
f5.close();
return sslContext.getSocketFactory();
}
catch (Exception e) {
e.printStackTrace();
return null;
}
}

Self -Signed certificate vs CA

I am making a solution for encryption of files via combination of RSA and AES. RSA is basically used for handshaking here to encrypt the symmetric key and decrypt at the receiver end via key pairs.
I have used Java Keystores for Private keys and Self-Signed certificates for Public Keys. Both are created as below:
KEY STORE
keytool -genkey -keyalg RSA -alias BANK -keystore receiverKeystore.jks -validity 360 -keysize 2048
CERTIFICATE EXPORT
keytool -export -alias BANK -keystore receiverKeystore.jks -rfc -file bankCert.cer
Similarly both are used in Java as below:
Private Key
private void setKeys() {
KeyStore senderKeyStore = null;
String keyStorePassword = "fms123";
try {
senderKeyStore = KeyStore.getInstance( "JKS" );
FileInputStream is = new FileInputStream( keystorePath );
senderKeyStore.load( is, keyStorePassword.toCharArray() );
senderPrivateKey = ( PrivateKey ) senderKeyStore.getKey( KEYSTORE_ALIAS, keyStorePassword.toCharArray() );
receiverPublicKey = loadReceiverPublicKeyFromCertificate();
}
catch ( Exception e ) {
System.out.println( " Exception in setting keys from key store = " + e );
}
}
PUBLIC KEY
private PublicKey loadReceiverPublicKeyFromCertificate () {
PublicKey publicKey = null;
try {
FileInputStream fin = new FileInputStream( certificateFilePath );
CertificateFactory factory = CertificateFactory.getInstance( "X.509" );
X509Certificate certificate = ( X509Certificate ) factory.generateCertificate( fin );
publicKey = certificate.getPublicKey();
}
catch ( Exception e ) {
System.out.println( " Exception in loading receiver certificate = " + e );
}
return publicKey;
}
I have now following Questions:
Is the the correct way do it?
If I use a trusted certificate here, how will the public key will be fetched from it? Will it be fetched in the same way? and for private keys? will I use the same approach or something from the certificate?
How can I distribute my certificate to the receiver? whats the secure communication shall for that?
I am new to encryption, will be very glad to get some advises.
Your handshaking is called key establishment. Handshaking is used for (online) transport modes, and I don't think you're developing that. I'd use a PKCS#12 key store instead, JKS is kinda deprecated. The whole idea of using a char[] for the password is that you can delete its contents directly after using it, so using a String as input is not a good idea. Strings are usually interned and always immutable in Java, so you cannot destroy them without ugly hacks involving native code.
Trusted certificates are no different from untrusted certificates. Yes, you can retrieve the public key from it. The private keys are commonly stored together with the certificate chain leading to the leaf user certificate that the private key belongs to. However, the private key is not part of the certificate, and therefore you cannot retrieve a private key from it. That's OK though, private keys should be established on the location where they are needed; there is no need to transport them other than for backup purposes.
How you can send the certificate to the receiver is up to you. The tricky part is to have the receiver trust the certificate. It's valid to use a pre-established key such as a trusted PGP key . If you haven't got such a key then you are in trouble though. What you can do is to use other methods to establish trust. Let the other party call you and then verify that the certificate fingerprint (basically just a hash over the certificate) is correct is probably one of the easiest ways. Bringing it over in person is of course also an option, if your users are not too far away.
You cannot create a secure channel out of thin air though. If you cannot trust who is on the other side, then establishing full trust is obviously impossible.

BadPaddingException loading p12 keystore

When executing the following code:
KeyStore ks = KeyStore.getInstance(storeType);
ks.load(new FileInputStream(keyStore), storePassword.toCharArray());
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(ks, keyPassword.toCharArray());
I get an exception:
java.security.UnrecoverableKeyException: Get Key failed: Given final block not properly padded. Such issues can arise if a bad key is used during decryption.
Caused by: javax.crypto.BadPaddingException: Given final block not properly padded. Such issues can arise if a bad key is used during decryption.
This was originally from a JKS keystore converted to a PKCS12 keystore using keytool. I tried creating a new fresh PKCS12 keystore but no luck.
JKS supports using two passwords, one for the store and one for the key inside. PKCS12 uses the same password for both. When using the keytool, you'll get a warning about this.
When migrating to the new keystore, the code will likely continue using one password for the keystore, and another (different) password for the key, though now that won't work.
Java 9 gives a much better exception message around this indicating it might arise from a bad key during decryption.
In this case, make sure to pass in a key password that matches the store password.

keytool - see the public and private keys

I created Java keystore programmatically of type jks (i.e. default type).
It is initially empty so I created a DSA certificate.
keytool -genkey -alias myCert -v -keystore trivial.keystore
How can I see the public and private keys?
I.e. is there a command that prints the private key of my certificate?
I could only find keytool -certreq which in my understanding prints the certificate as a whole:
-----BEGIN NEW CERTIFICATE REQUEST-----
MIICaTCCAicCAQAwZTELMAkGA1UEBhMCR1IxDzANBgNVBAgTBkdyZWVjZTEPMA0GA1UEBxMGQXRo
BQADLwAwLAIUQZbY/3Qq0G26fsBbWiHMbuVd3VICFE+gwtUauYiRbHh0caAtRj3qRTwl
-----END NEW CERTIFICATE REQUEST-----
I assume this is the whole certificate. How can I see private (or public key) via keytool?
You created a private (and associated public) key in your keystore. For it to be really usable, you can get it signed by a certification agency (CA) - for this is the -certreq command (you send the output to this certification agency, along with some other information and a bit of money, and they send back a certificate, which you can then import in your keystore.)
Viewing the private key is not intended ... you usually don't need this, since you use the keystore in your Java program, and this knows how to use it.
Edit: Since you want to look at your keystore, here a quick Java program which does this:
import java.io.*;
import java.security.*;
import java.security.cert.Certificate;
public class KeyPrinter {
/**
* to be invoked with these parameters:
*
* [0]: keystore-password
* [1]: filename
* [2]: alias
* [3]: entry-Password (if necessary)
*/
public static void main(String[] params)
throws IOException, GeneralSecurityException
{
char[] storePass = params[0].toCharArray();
String fileName = params[1];
String alias = params[2];
KeyStore.ProtectionParameter entryPass;
if(params.length > 3) {
entryPass=new KeyStore.PasswordProtection(params[3].toCharArray());
} else {
entryPass = null;
}
KeyStore store = KeyStore.getInstance("JKS");
InputStream input = new FileInputStream(fileName);
store.load(input, storePass);
KeyStore.Entry entry = store.getEntry(alias, entryPass);
System.out.println(entry);
}
}
First call keytool -list -keystore myStore to know which alias to look for, then call this program with the passwords and parameters. In case of a private key entry, it shows the key itself and additionally a self-signed certificate which contains the public key, in a readable form. In case of a "trusted certificate", it shows only the public key.
No, you cannot.
You can access the private key from code, but you cannot export it using the keytool.
Use OpenSSL if you need to export private key.
Another option: you can generate keystore in PKCS12 format. Then you can import it to a browser and then to export the private key.
(Portecle) is a very convenient GUI tool for managing keystores. And among other things it have an option to export private key and its associated certificate.
The common way to share your public key is to share a certificate for your keypair (it contains your public key inside)
keytool -list -v -alias myCert -storepass 123456 -keystore file.jks
or
keytool -list -rfc -alias myCert -storepass 123456 -keystore file.jks
as noted in
keytool -help
You can use keystore-explorer.

Can't sign a dig sig utilizing java / keytool

I have created a certificate basically straight from the keytool example page:
keytool -genkey -dname "cn=Anything, ou=Anything, o=Anything, c=US" -alias business -keypass kpi135 -keystore C:\mykeystore -storepass ab987c -validity 1095
I am trying to access this certificate and use the private key portion to digitally sign a portion of text to authenticate with a third party. Below is the code I'm attempting:
//Add bouncyCastle as a provider
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
KeyStore keystore = KeyStore.getInstance("JKS");
keystore.load(new FileInputStream("C:\\mykeystore"), "ab987c".toCharArray());
Signature sig = Signature.getInstance("MD5withRSA", "BC");
PrivateKey privateKey = (PrivateKey)keystore.getKey("business", "kpi135".toCharArray()); //Exception here
sig.initSign(privateKey);
sig.update("myUID__myNonce".getBytes());
byte[] digitalSignature = sig.sign();
System.out.println("Signature Formulated: " + digitalSignature);
I get the following exception:
java.security.InvalidKeyException: Supplied key (sun.security.provider.DSAPrivat
eKey) is not a RSAPrivateKey instance
at org.bouncycastle.jce.provider.JDKDigestSignature.engineInitSign(Unkno
wn Source)
at java.security.Signature$Delegate.engineInitSign(Signature.java:1095)
at java.security.Signature.initSign(Signature.java:480)
at MainClass.<init>(MainClass.java:15)
at MainClass.main(MainClass.java:28)
I assume it's because I've somehow created the certificate with the wrong type of key, but I'm not sure I'm finding what I need by digging through the keytool page. It does mention that you can apparently generate a key using -keysig RSA and -sigalg RSA however when I try those flags when creating a certificate I get:
keytool error: java.security.NoSuchAlgorithmException: RSA Signature not availab
le
enter code here
Actually it seems you can add "-keyalg RSA" to the keygen command which alleviates the mismatch issue. I was incorrectly trying both -keyalg and -sigalg in the same keygen command. The code above now executes without exceptions.

Categories