I am first going to describe the problem which I have, and then give some background to what I am trying to do. Finally I shall paste some relevant code snippets.
I am trying to implement secret key encryption/decryption using the method specified in https://stackoverflow.com/a/992413/171993. If I use that example as-is, it works (although I did notice that I need to re-instantiate the Cipher
class, otherwise the decryption produces garbage). However, in my implementation I get the following exception:
java.security.InvalidKeyException: Wrong algorithm: AES or Rijndael required
at com.sun.crypto.provider.AESCrypt.init(AESCrypt.java:77)
at com.sun.crypto.provider.CipherBlockChaining.init(CipherBlockChaining.java:91)
at com.sun.crypto.provider.CipherCore.init(CipherCore.java:469)
at com.sun.crypto.provider.AESCipher.engineInit(AESCipher.java:217)
at javax.crypto.Cipher.implInit(Cipher.java:790)
at javax.crypto.Cipher.chooseProvider(Cipher.java:848)
at javax.crypto.Cipher.init(Cipher.java:1347)
at javax.crypto.Cipher.init(Cipher.java:1281)
at securitytest.SecurityManager.getCipher(SecurityManager.java:175)
at securitytest.SecurityManager.decryptSecretKey(SecurityManager.java:379)
at securitytest.SecurityManager.<init>(SecurityManager.java:82)
at securitytest.Test.main(Test.java:44)
To beat off the obvious question, yes, I do use the same algorithm: in fact, I assigned AES/CBC/PKCS5Padding
to a constant and use that for instantiating both the Cipher
class for encryption and decryption. I have also tried using only AES
instantiate Cipher
for the decryption, but that did not work either.
What I am trying to do is to password-protect a secret key by using AES/CBC/PKCS5Padding. I generate a random salt and initialisation vector. After encrypting the secret key, I append the initialisation vector (an array of bytes) to the encrypted value (also an array of bytes, creating a new array). I then encode this value in Base64 and store it in a Sqlite database, along with the salt (which, for the sake of simplicity, I store as a comma-separated string of values). However when I try to decrypt, I get the above exception. I can verify that directly after my call to the encryption method and directly before the decryption method, the following values are exactly the same (when converted to Base64 so that I can print it out):
- The salt
- The initialisation vector
- The encrypted secret key (i.e. the cipher text)
I have tried both Java 6 and 7: both give the same results. I have also ruled out the unlimited strength jurisdiction policy files as an issue. In fact, I get a similar error if I substitute "AES" with another algorithm and adjust the length of the salt accordingly (for example "Blowfish" with IV length 8, which produces java.security.InvalidKeyException: Wrong algorithm: Blowfish required
).
Google has not been able to help me with this problem. If anyone can shed some light on this, I would be very appreciative.
Here are some code snippets (my apologies, it is a little rough):
private static final int INIT_VECTOR_LENGTH = 16;
private static final int PRIVATE_KEY_LENGTH = 128;
private static final int SALT_LENGTH = 16;
private static final int PBE_KEYSPEC_ITERATIONS = 65536;
private static final String CIPHER_ALGORITHM = "AES";
private static final String CIPHER_ALGORITHM_MODE = "CBC";
private static final String CIPHER_ALGORITHM_PADDING = "PKCS5Padding";
private static final String DIGEST = "SHA1";
private static final String PLAINTEXT_ENCODING = "UTF8";
private static final String PRNG = DIGEST + "PRNG";
private static final String SECRET_KEY_FACTORY = "PBKDF2WithHmac" + DIGEST;
private static final String CIPHER = CIPHER_ALGORITHM + "/" + CIPHER_ALGORITHM_MODE + "/" + CIPHER_ALGORITHM_PADDING;
private IvParameterSpec ivSpec;
private final BASE64Encoder encoder = new BASE64Encoder();
private final BASE64Decoder decoder = new BASE64Decoder();
private Cipher getCipher(SecretKey key, int mode) {
Cipher cipher = null;
try {
cipher = Cipher.getInstance(CIPHER);
}
catch (NoSuchAlgorithmException e) {System.err.println(System.err.println(e.getMessage());}
catch (NoSuchPaddingException e) {System.err.println(e.getMessage());}
try {
if (mode == Cipher.ENCRYPT_MODE) {
cipher.init(mode, key);
AlgorithmParameters params = cipher.getParameters();
ivSpec = params.getParameterSpec(IvParameterSpec.class);
}
else {
/* This is my point-of-failure. */
cipher.init(mode, key, ivSpec);
}
}
catch (InvalidKeyException e) {System.err.println(e.getMessage());}
catch (InvalidAlgorithmParameterException e) {System.err.println(e.getMessage());}
catch (InvalidParameterSpecException e) {System.err.println(e.getMessage());}
return cipher;
}
private SecurityData.Secrets generateSecrets(SecretKey decryptedKey, byte[] salt, String passphrase) {
/* Generate a new key for encrypting the secret key. */
byte[] raw = null;
PBEKey key = null;
PBEKeySpec password = new PBEKeySpec(passphrase.toCharArray(), salt, PBE_KEYSPEC_ITERATIONS, PRIVATE_KEY_LENGTH);
SecretKeyFactory factory = null;
byte[] initVector = null;
byte[] secretKeyBytes = decryptedKey.getEncoded();
try {
factory = SecretKeyFactory.getInstance(SECRET_KEY_FACTORY);
key = (PBEKey) factory.generateSecret(password);
}
catch (NoSuchAlgorithmException e) {System.err.println(e.getMessage());}
catch (InvalidKeySpecException e) {System.err.println(e.getMessage());}
SecretKeySpec newKey = new SecretKeySpec(key.getEncoded(), CIPHER_ALGORITHM);
/* Encrypt the secret key. */
IvParameterSpec ivSpec = new IvParameterSpec(initVector);
Cipher cipher = getCipher(newKey, ivSpec, Cipher.ENCRYPT_MODE);
try {
raw = cipher.doFinal(secretKeyBytes);
}
catch (IllegalBlockSizeException e) {System.err.println(e.getMessage());}
catch (BadPaddingException e) {System.err.println(e.getMessage());}
return new SecurityData.Secrets(encoder.encode(concatByteArrays(initVector, raw)), joinByteArray(salt));
}
private SecretKey decryptSecretKey(String encryptedKey, String salt, String passphrase) {
/* Get initialisation vector. */
byte[] raw = null, decoded = null, initVector = new byte[INIT_VECTOR_LENGTH];
try {
decoded = decoder.decodeBuffer(encryptedKey);
} catch (IOException e) {System.err.println(e.getMessage());}
System.arraycopy(decoded, 0, initVector, 0, INIT_VECTOR_LENGTH);
raw = new byte[decoded.length-INIT_VECTOR_LENGTH];
System.arraycopy(decoded, INIT_VECTOR_LENGTH, raw, 0, decoded.length-INIT_VECTOR_LENGTH);
IvParameterSpec ivSpec = new IvParameterSpec(initVector);
/* Generate the key. */
byte[] rawSalt = splitByteArrayString(salt);
PBEKeySpec password = new PBEKeySpec(passphrase.toCharArray(), rawSalt, PBE_KEYSPEC_ITERATIONS, PRIVATE_KEY_LENGTH);
SecretKeyFactory factory = null;
PBEKey key = null;
try {
factory = SecretKeyFactory.getInstance(SECRET_KEY_FACTORY);
key = (PBEKey) factory.generateSecret(password);
}
catch (NoSuchAlgorithmException e) {System.err.println(e.getMessage());}
catch (InvalidKeySpecException e) {System.err.println(e.getMessage());}
Cipher cipher = getCipher(key, Cipher.DECRYPT_MODE);
/* Decrypt the message. */
byte[] stringBytes = null;
try {
stringBytes = cipher.doFinal(raw);
}
catch (IllegalBlockSizeException e) {System.err.println(e.getMessage());}
catch (BadPaddingException e) {System.err.println(e.getMessage());}
/* Converts the decoded message to a String. */
String clear = null;
try {
clear = new String(stringBytes, PLAINTEXT_ENCODING);
}
catch (UnsupportedEncodingException e) {System.err.println(e.getMessage());}
return new SecretKeySpec(clear.getBytes(), CIPHER_ALGORITHM);
}