Reading Oracle documentation, I see that by default JKS files are encrypted using PBEWithMD5AndTripleDES
. While DES alone makes me feel uneasy, MD5 lights a big red
Triple DES is pretty strong, and Oracle likely uses keys with 168 bit of entropy (giving a full 112 bits of security at the time of writing).
Furthermore, although MD5 may not be secure for e.g. signatures, it is certainly valid for use in a key derivation scheme as such as PBE.
Of course it would be a good idea for Oracle to migrate away from these schemes in time, but Triple DES and MD5 should not worry you needlessly. Writing your own is probably a worse idea, there are too many pitfalls.
Choose a good password, it's probably the best thing you can do. Or put your keystore in a correctly configured HSM or smartcard if you want high end security.
In the end I went with PKCS#8 files encrypted using PBEWithSHA256And256BitAES-CBC-BC
Encryption:
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.AlgorithmParameters;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import java.security.Security;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.InvalidParameterSpecException;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.EncryptedPrivateKeyInfo;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.PBEParameterSpec;
import org.bouncycastle.asn1.bc.BCObjectIdentifiers;
public class EncodePKCS8 {
/**
* @param args
* @throws NoSuchAlgorithmException
* @throws InvalidKeySpecException
* @throws NoSuchPaddingException
* @throws InvalidAlgorithmParameterException
* @throws InvalidKeyException
* @throws BadPaddingException
* @throws IllegalBlockSizeException
* @throws InvalidParameterSpecException
* @throws IOException
* @throws NoSuchProviderException
*/
public static void main(String[] args) throws NoSuchAlgorithmException,
InvalidKeySpecException, NoSuchPaddingException, InvalidKeyException,
InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException,
InvalidParameterSpecException, IOException, NoSuchProviderException
{
// before we can do anything with BouncyCastle we have to register its provider
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
String password = "Very long and complex password";
// generate RSA key pair
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(2048);
KeyPair keyPair = keyPairGenerator.genKeyPair();
byte[] encryptedPkcs8 = encryptPrivateKey(password, keyPair);
FileOutputStream fos = new FileOutputStream("privkey.p8");
fos.write(encryptedPkcs8);
fos.close();
return;
}
private static byte[] encryptPrivateKey(String password, KeyPair keyPair)
throws InvalidKeyException, NoSuchAlgorithmException, NoSuchProviderException,
InvalidKeySpecException, NoSuchPaddingException,
InvalidAlgorithmParameterException, IllegalBlockSizeException,
BadPaddingException, InvalidParameterSpecException, IOException
{
int count = 100000; // hash iteration count, best to leave at default or increase
return encryptPrivateKey(password, keyPair, count);
}
/**
*
* @param password
* @param keyPair
* @param count
* @return PKCS#8 encoded, encrypted keyPair
* @throws NoSuchAlgorithmException
* @throws NoSuchProviderException
* @throws InvalidKeySpecException
* @throws NoSuchPaddingException
* @throws InvalidKeyException
* @throws InvalidAlgorithmParameterException
* @throws IllegalBlockSizeException
* @throws BadPaddingException
* @throws InvalidParameterSpecException
* @throws IOException
*/
private static byte[] encryptPrivateKey(String password,
KeyPair keyPair, int count) throws NoSuchAlgorithmException,
NoSuchProviderException, InvalidKeySpecException,
NoSuchPaddingException, InvalidKeyException,
InvalidAlgorithmParameterException, IllegalBlockSizeException,
BadPaddingException, InvalidParameterSpecException, IOException
{
// extract the encoded private key, this is an unencrypted PKCS#8 private key
byte[] encodedprivkey = keyPair.getPrivate().getEncoded();
// Use a PasswordBasedEncryption (PBE) algorithm, OID of this algorithm will be saved
// in the PKCS#8 file, so changing it (when more standard algorithm or safer
// algorithm is available) doesn't break backwards compatibility.
// In other words, decryptor doesn't need to know the algorithm before it will be
// able to decrypt the PKCS#8 object.
String encAlg = BCObjectIdentifiers.bc_pbe_sha256_pkcs12_aes256_cbc.getId();
SecureRandom random = new SecureRandom();
byte[] salt = new byte[16];
random.nextBytes(salt);
// Create PBE parameter set
PBEParameterSpec pbeParamSpec = new PBEParameterSpec(salt, count);
PBEKeySpec pbeKeySpec = new PBEKeySpec(password.toCharArray());
SecretKeyFactory keyFac = SecretKeyFactory.getInstance(encAlg, "BC");
SecretKey pbeKey = keyFac.generateSecret(pbeKeySpec);
Cipher pbeCipher = Cipher.getInstance(encAlg, "BC");
// Initialize PBE Cipher with key and parameters
pbeCipher.init(Cipher.ENCRYPT_MODE, pbeKey, pbeParamSpec);
// Encrypt the encoded Private Key with the PBE key
byte[] ciphertext = pbeCipher.doFinal(encodedprivkey);
// Now construct PKCS #8 EncryptedPrivateKeyInfo object
AlgorithmParameters algparms = AlgorithmParameters.getInstance(encAlg, "BC");
algparms.init(pbeParamSpec);
EncryptedPrivateKeyInfo encinfo = new EncryptedPrivateKeyInfo(algparms, ciphertext);
// DER encoded PKCS#8 encrypted key
byte[] encryptedPkcs8 = encinfo.getEncoded();
return encryptedPkcs8;
}
}
Decryption:
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.security.AlgorithmParameters;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Security;
import java.security.interfaces.RSAKey;
import java.security.interfaces.RSAPrivateCrtKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.security.spec.RSAPublicKeySpec;
import javax.crypto.Cipher;
import javax.crypto.EncryptedPrivateKeyInfo;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
public class DecodePKCS8 {
/**
* @param args
* @throws IOException
* @throws NoSuchPaddingException When file is corrupted
* @throws NoSuchAlgorithmException When no BC provider has been loaded
* @throws InvalidKeySpecException When decryption of file failed
* @throws InvalidAlgorithmParameterException When file is corrupted
* @throws InvalidKeyException When Unlimited cryptography extensions are not installed
*/
public static void main(String[] args) throws
IOException, NoSuchAlgorithmException, NoSuchPaddingException,
InvalidKeySpecException, InvalidKeyException, InvalidAlgorithmParameterException
{
// before we can do anything with BouncyCastle we have to register its provider
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
String password = "Very long and complex password";
// read DER encoded key from files
byte[] encodedprivkey = getFileBytes("privkey.p8");
// this is a encoded PKCS#8 encrypted private key
EncryptedPrivateKeyInfo ePKInfo = new EncryptedPrivateKeyInfo(encodedprivkey);
// first we have to read algorithm name and parameters (salt, iterations) used
// to encrypt the file
Cipher cipher = Cipher.getInstance(ePKInfo.getAlgName());
PBEKeySpec pbeKeySpec = new PBEKeySpec(password.toCharArray());
SecretKeyFactory skFac = SecretKeyFactory.getInstance(ePKInfo
.getAlgName());
Key pbeKey = skFac.generateSecret(pbeKeySpec);
// Extract the iteration count and the salt
AlgorithmParameters algParams = ePKInfo.getAlgParameters();
cipher.init(Cipher.DECRYPT_MODE, pbeKey, algParams);
// Decrypt the encryped private key into a PKCS8EncodedKeySpec
KeySpec pkcs8KeySpec = ePKInfo.getKeySpec(cipher);
// Now retrieve the RSA Public and private keys by using an
// RSA key factory.
KeyFactory rsaKeyFac = KeyFactory.getInstance("RSA");
// First get the private key
PrivateKey rsaPriv = rsaKeyFac.generatePrivate(pkcs8KeySpec);
// Now derive the RSA public key from the private key
RSAPublicKeySpec rsaPubKeySpec = new RSAPublicKeySpec(((RSAKey) rsaPriv).getModulus(),
((RSAPrivateCrtKey) rsaPriv).getPublicExponent());
PublicKey rsaPubKey = (RSAPublicKey) rsaKeyFac.generatePublic(rsaPubKeySpec);
System.out.println("Key extracted, public part: " + rsaPubKey);
}
private static byte[] getFileBytes(String path)
{
File f = new File(path);
int sizecontent = ((int) f.length()); // no key file will ever be bigger than 4GiB...
byte[] data = new byte[sizecontent];
try
{
FileInputStream freader = new FileInputStream(f);
freader.read(data, 0, sizecontent) ;
freader.close();
return data;
}
catch(IOException ioe)
{
System.out.println(ioe.toString());
return null;
}
}
}
Since Java 8, you can create a PKCS#12 keystore and pass an explicit PasswordProtection parameter when storing a key to specify the encryption algorithm to use:
import java.io.FileOutputStream;
import java.security.KeyStore;
import java.security.KeyStore.PasswordProtection;
import java.security.KeyStore.PrivateKeyEntry;
import java.security.PrivateKey;
import java.security.SecureRandom;
import java.security.cert.Certificate;
import javax.crypto.spec.PBEParameterSpec;
public class scratch {
public static void main(String... args) throws Exception {
KeyStore keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(null, null); // Initialize a blank keystore
// Your key to store
PrivateKey key = ...;
Certificate[] chain = new Certificate[] { ... };
char[] password = "changeit".toCharArray();
byte[] salt = new byte[20];
new SecureRandom().nextBytes(salt);
keyStore.setEntry("test", new PrivateKeyEntry(key, chain),
new PasswordProtection(password,
"PBEWithHmacSHA512AndAES_256",
new PBEParameterSpec(salt, 100_000)));
keyStore.store(new FileOutputStream("/tmp/keystore.p12"), password);
}
}
You can read a bit more on the details in this article (dislaimer: I wrote that article).
Main Class
`public class a {
/**
* @param args the command line arguments
*/
public static void main(String[] args) throws Exception {
// TODO code application logic here
String b = "MSISDN=559915129&productID=5859";
AEScryptography enj = new AEScryptography();
String[] argts = {b, "EN"};
System.out.println("ENCY -> "+enj.encryptionDecryption(argts));
System.out.println(checksum.encode(b));
} }`
AEScryptography Class
`public class AEScryptography {
public String encryptionDecryption(String[] args) throws UnsupportedEncodingException, NoSuchProviderException, NoSuchPaddingException, InvalidKeyException, ShortBufferException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException {
String returnVariable = "";
if (args.length == 2) {
try {
String keystorePass = "201774"; // KeyStroe Password
String keyPass = "mc7129"; // KeyPassword
String alias = "raVi"; // Alias
InputStream keystoreStream = new FileInputStream("D:/keyFile.jks");
KeyStore keystore = KeyStore.getInstance("JCEKS");
keystore.load(keystoreStream, keystorePass.toCharArray());
Key key = keystore.getKey(alias, keyPass.toCharArray());
byte[] bt = key.getEncoded();
String s = new String(bt);
String originalString = args[0];
switch (args[1]) {
case "EN":
String encryptedString = AES.encrypt(originalString, s, bt);
returnVariable = encryptedString;
break;
case "DE":
String decryptedString = AES.decrypt(originalString, s, bt);
returnVariable = decryptedString;
break;
default:
System.out.println("java -jar /home/jocg/AES-256Encryption.jar StringToEncrypt/Decrypt EN/DE");
break;
}
} catch (KeyStoreException | IOException | NoSuchAlgorithmException | CertificateException | UnrecoverableKeyException ex) {
System.out.println(ex);
}
} else {
System.out.println("java -jar /home/jocg/AES-256Encryption.jar StringToEncrypt/Decrypt EN/DE");
}
return returnVariable;
} }`
AES Class
public class AES {
private static SecretKeySpec secretKey;
private static byte[] key;
public static void setKey(String myKey, byte[] ActualKey) throws
NoSuchAlgorithmException {
try {
key = myKey.getBytes("UTF-8");
key = ActualKey;
secretKey = new SecretKeySpec(key, "AES");
} catch (UnsupportedEncodingException e) {
}
}
public static String encrypt(String strToEncrypt, String secret, byte[] bt) throws UnsupportedEncodingException, NoSuchProviderException, InvalidAlgorithmParameterException {
try {
setKey(secret, bt);
byte[] iv = new byte[16];
IvParameterSpec ivspec = new IvParameterSpec(iv);
SecretKey secKey = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secKey, ivspec);
byte[] newData = cipher.doFinal(strToEncrypt.getBytes("UTF-8"));
return Base64.getEncoder().encodeToString(newData);
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException e) {
System.out.println("Error while encrypting: " + e.toString());
}
return null;
}
}`