Tuesday, December 9, 2008

Storing a password somewhere safe

If you want to store a password in your deployed application, perhaps for an external service you need to make sure it is saved somewhere safe. Internally at oracle we are never allowed to store a password in plain text, ideally it has to be stored somewhere encoded to prevent a idle HD search from finding it. This made me wonder how I could store a simple character password in a keystore for later use to invoke a remote web service.

Keys are complicated beasties as you can see from the number of sub classes in java. In the most part they are going to be public/private key pair which are the mainstay of the internet. But if you just want to store a simple password you need to create a SecretKey.

Now the complicated this is that you have to create a key spec, which is transparent and create new opaque secret key from it for storage in the keystore. In case you are wonder PBE stands for "Password Based Encryption" which is the simplest type I could find in the security spec that would suit my needs.

SecretKeyFactory factory = SecretKeyFactory.getInstance("PBE");
 SecretKey generatedSecret =
  factory.generateSecret(new PBEKeySpec(    
    password));

It turns out that the default "JKS" KeyStore type will not allow you to store a SecretKey. So instead you need the code to create the right kind. It might look something like this:

KeyStore ks = KeyStore.getInstance("JCEKS");
 ks.load(null, keyStorePassword);
 PasswordProtection keyStorePP = new PasswordProtection(keyStorePassword);

 SecretKeyFactory factory = SecretKeyFactory.getInstance("PBE");
 SecretKey generatedSecret =
    factory.generateSecret(new PBEKeySpec(    
      password));

 ks.setEntry(key, new SecretKeyEntry(
    generatedSecret), keyStorePP);

 ks.save(…, // Output stream for keystore
    keyStorePassword);

Note that you can't just add PBEKeySpec to a keystore because while it is a "Key" it is not of type "SecretKey" so you have to convert. When loading the password out from the keystore you need the SecretKeyFactory again to convert the SecretKey back into the PBEKeySpec that you can extract the password from.

KeyStore ks = KeyStore.getInstance("JCEKS");
 ks.load(…, // InputStrweam to keystore
   keyStorePassword);
 
 SecretKeyFactory factory = SecretKeyFactory.getInstance("PBE");

 SecretKeyEntry ske =
   (SecretKeyEntry)ks.getEntry(key, keyStorePP);
 PBEKeySpec keySpec = (PBEKeySpec)factory.getKeySpec(
     ske.getSecretKey(), 
     PBEKeySpec.class);

 char[] password = keySpec.getPassword();

Now in general use the JCEKS keystore is far more secure than the default JKS format. The reason it is not the default is back in the old days they encryption was treated as a munition. I think this is only a problem for a few black listed countries now - although this restriction seems a bit pointless given the reach of the internet.

7 comments:

Unknown said...

This works but storing secret keys in JCEKS seems to be provider-dependent: what's stored in an IBM JRE can't be read from a Sun/Oracle one and vice-versa. This is not the case for private keys. Any workaround for this?

Unknown said...

The SecretKey storage is provider-dependent in JCEKS: what you write in an IBM JRE can't be read in a Sun/Oracle one and vice-versa. Is there a workaround for that?

Kamal Giri said...

Good article. But I am confused as to how I can secure my keystore password.

dan c said...

ls.save(...)

What is ls, where did this come from?

Gerard Davison said...

Sorry that should have been ks.save, sorry for the typo.

dan c said...

I think you mean ks.store(...) ?
Keystore does not have a save(...) method.

Here's a full working example adapted from your code.

Thanks,
Dan.


import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.*;
import java.security.KeyStore.PasswordProtection;
import java.security.KeyStore.SecretKeyEntry;
import java.security.cert.CertificateException;
import java.security.spec.InvalidKeySpecException;

import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;


public class SecretKeyUtil {


private SecretKeyFactory factory;

private KeyStore ks;

private Path keystoreLocation;

private char[] keystorePassword;

public SecretKeyUtil(Path keystoreLocation, char[] keystorePassword, boolean loadExisting)
throws KeyStoreException, IOException,
NoSuchAlgorithmException, CertificateException {

this.keystoreLocation = keystoreLocation;
this.keystorePassword = keystorePassword;

ks = KeyStore.getInstance("JCEKS");
if (loadExisting) {
ks.load(Files.newInputStream(keystoreLocation), keystorePassword);
} else {
if (Files.exists(keystoreLocation)) {
throw new IOException("Cannot create new keystore, keystore file " + keystoreLocation
+ " already exists");
}
ks.load(null, keystorePassword);
}

factory = SecretKeyFactory.getInstance("PBE");
}

public void createKeyEntry(String alias, char[] password) throws KeyStoreException, NoSuchAlgorithmException,
CertificateException, IOException,
InvalidKeySpecException {

SecretKey generatedSecret = factory.generateSecret(new PBEKeySpec(password));

ks.setEntry(alias, new SecretKeyEntry(generatedSecret), new PasswordProtection(keystorePassword));

ks.store(Files.newOutputStream(keystoreLocation), keystorePassword);

}

public char[] retrieveEntryPassword(String alias) throws NoSuchAlgorithmException, UnrecoverableEntryException,
KeyStoreException, InvalidKeySpecException {

SecretKeyEntry entry = (SecretKeyEntry) ks.getEntry(alias, new PasswordProtection(keystorePassword));
PBEKeySpec keySpec = (PBEKeySpec) factory.getKeySpec(entry.getSecretKey(), PBEKeySpec.class);

return keySpec.getPassword();

}



}

peci1 said...

Would PKCS12 keystore work, too? It seems to me that it could solve the interoperability/compatibility issue edward wrote about.