I have the following code for encrypt
public static String encrypt(String value, char[] secret) {
try {
final byte[] bytes = value != null ? value.getBytes(StandardCharsets.UTF_8) : new byte[0];
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBEWithMD5AndDES");
SecretKey key = keyFactory.generateSecret(new PBEKeySpec(secret));
Cipher pbeCipher = Cipher.getInstance("PBEWithMD5AndDES");
pbeCipher.init(Cipher.ENCRYPT_MODE, key, new PBEParameterSpec(IsoGame.$().crossPlatformManager.getCrossPlatformUtilsInstance().getDeviceUniqueIdentifier().getBytes(StandardCharsets.UTF_8), 20));
return new String(Base64.encodeBase64(pbeCipher.doFinal(bytes)), StandardCharsets.UTF_8);
} catch (Exception e) {
e.printStackTrace();
}
return value;
}
and the following code for decrypt.
public static String decrypt(String value, char[] secret) {
try {
final byte[] bytes = value != null ? Base64.decodeBase64(value.getBytes(StandardCharsets.UTF_8)) : new byte[0];
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBEWithMD5AndDES");
SecretKey key = keyFactory.generateSecret(new PBEKeySpec(secret));
Cipher pbeCipher = Cipher.getInstance("PBEWithMD5AndDES");
pbeCipher.init(Cipher.DECRYPT_MODE, key, new PBEParameterSpec(IsoGame.$().crossPlatformManager.getCrossPlatformUtilsInstance().getDeviceUniqueIdentifier().getBytes(StandardCharsets.UTF_8), 20));
return new String(pbeCipher.doFinal(bytes), StandardCharsets.UTF_8);
} catch (Exception e) {
e.printStackTrace();
}
return value;
}
However, sometimes the exception is being thrown at
pbeCipher.doFinal(bytes)
in decrypt method.
The exception is javax.crypto.BadPaddingException: pad block corrupted
It's strange, as I'm getting this exception sometimes with the same values .
Any ideas?
Thanks.
The most likely reason would simply be the wrong password to be supplied. If the wrong password is supplied then the wrong key is derived. Then the ciphertext will be decrypted to garbage plaintext. This will only be noticed if the padding exception gets thrown: unpadding random bytes is likely to fail.
You could e.g. first validate that the derived key is correct by performing a HMAC over known data using the derived key. In addition, it would be a good idea to use some kind of authenticated encryption, so that if the key or data is wrong or corrupted that decryption does indeed fail. If you're unlucky then - at this time - the data will decrypt, unpadding will succeed and you end up with garbage plaintext.
Of course, you'd better upgrade to PBKDF2 for key derivation, and upgrade AES to e.g. AES-GCM instead of DES. Currently your encryption is entirely insecure, even if you use a strong password.
Your problem is
IsoGame.$().crossPlatformManager.getCrossPlatformUtilsInstance().getDeviceUniqueIdentifier().getBytes(StandardCharsets.UTF_8)
I have ran the following code multiple times and no exception occurred and the decrypted data was equal to "Hello there!":
public static void main(String[] args)
{
new CryptographyError();
}
private CryptographyError()
{
char[] secret = "MySecret".toCharArray();
String mesasge = "Hello there!";
EncryptedData encryptedData = encrypt(mesasge, secret);
System.out.println("ENCRYPTED " + encryptedData.encryptedString);
String decrypted = decrypt(encryptedData, secret);
System.out.println("DECRYPTED " + decrypted);
}
private static final SecureRandom RANDOM = new SecureRandom();
public static EncryptedData encrypt(String value, char[] secret) {
try {
final byte[] bytes = value != null ? value.getBytes(StandardCharsets.UTF_8) : new byte[0];
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBEWithMD5AndDES");
SecretKey key = keyFactory.generateSecret(new PBEKeySpec(secret));
Cipher pbeCipher = Cipher.getInstance("PBEWithMD5AndDES");
byte[] salt = new byte[8];
RANDOM.nextBytes(salt);
pbeCipher.init(Cipher.ENCRYPT_MODE, key, new PBEParameterSpec(salt, 20));
return new EncryptedData(salt, new String(Base64.getEncoder().encode(pbeCipher.doFinal(bytes)), StandardCharsets.UTF_8));
} catch (Exception e) {
e.printStackTrace();
System.out.println(value);
}
return null;
}
public static String decrypt(EncryptedData encryptedData, char[] secret) {
try {
String value = encryptedData.encryptedString;
byte[] salt = encryptedData.salt;
final byte[] bytes = value != null ? Base64.getDecoder().decode(value.getBytes(StandardCharsets.UTF_8)) : new byte[0];
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBEWithMD5AndDES");
SecretKey key = keyFactory.generateSecret(new PBEKeySpec(secret));
Cipher pbeCipher = Cipher.getInstance("PBEWithMD5AndDES");
pbeCipher.init(Cipher.DECRYPT_MODE, key, new PBEParameterSpec(salt, 20));
return new String(pbeCipher.doFinal(bytes), StandardCharsets.UTF_8);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
private static class EncryptedData
{
private final byte[] salt;
private final String encryptedString;
private EncryptedData(byte[] salt, String encryptedString)
{
this.salt = salt;
this.encryptedString = encryptedString;
}
}
The only main difference between my code and your code is
IsoGame.$().crossPlatformManager.getCrossPlatformUtilsInstance().getDeviceUniqueIdentifier().getBytes(StandardCharsets.UTF_8)
which means that must not return the same value on encryption and decryption.
Also if you want to test this you can just change the salt between them and notice the exception is thrown again.
Also Maarten Bodewes gave you some good notes about how to improve your code.
Related
Closed. This question is not reproducible or was caused by typos. It is not currently accepting answers.
This question was caused by a typo or a problem that can no longer be reproduced. While similar questions may be on-topic here, this one was resolved in a way less likely to help future readers.
Closed 1 year ago.
Improve this question
I am trying to create a simple AES encryption/decryption module for use in a larger project, but I'm having trouble getting the AES methods to work. I've done a good amount of research, but I can't figure out what is going wrong within my code (I'm suspecting something simple that I'm missing).
Main:
public static byte[] genKey() {
// Create key generator
KeyGenerator keyGen;
try {
keyGen = KeyGenerator.getInstance("AES");
}
catch(GeneralSecurityException e) {
e.printStackTrace();
return null;
}
// Create random byte generator
SecureRandom r = new SecureRandom();
// Initialize key generator
keyGen.init(256, r);
SecretKey key = keyGen.generateKey();
return key.getEncoded();
}
public static void main(String[] args) throws GeneralSecurityException {
// write your code here
// Create AES handler
AES aes = new AES();
// Generate key
byte[] key = genKey();
// Set key for AES
aes.setKey(key);
Scanner in = new Scanner(System.in);
System.out.print("Please enter a phrase to encrypt: ");
String input = in.nextLine();
// Encrypt phrase
byte[][] encrypted = aes.encrypt(input);
// Decrypt phrase
String plaintext = aes.decrypt(encrypted[0], encrypted[1]);
// Print results
System.out.println("Ciphertext: " + encrypted[1]);
System.out.println("Plaintext: " + plaintext);
}
AES:
private Cipher cipher;
private SecretKey key;
public AES() {
// Create Cipher
try {
cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
} catch (GeneralSecurityException e) {
e.printStackTrace();
}
}
public void setKey(byte[] key) {
this.key = new SecretKeySpec(key, "AES");
}
public byte[][] encrypt(String plaintext) throws GeneralSecurityException {
System.out.println("Using key : " + key.getEncoded() + " to encrypt");
byte[][] values = new byte[2][];
// Decode plaintext into bytes
byte[] decodedPlaintext = new byte[0];
try {
decodedPlaintext = plaintext.getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
// Generate an IV and set up the Cipher to encrypt
byte[] ivBytes = new byte[16];
SecureRandom rand = new SecureRandom();
rand.nextBytes(ivBytes);
IvParameterSpec iv = new IvParameterSpec(ivBytes);
cipher.init(Cipher.ENCRYPT_MODE, key, iv);
// Encrypt decoded plaintext
byte[] ciphertext = cipher.doFinal(decodedPlaintext);
values[0] = ivBytes;
values[1] = ciphertext;
return values;
}
public String decrypt(byte[] ivBytes, byte[] ciphertext) throws GeneralSecurityException {
System.out.println("Using key " + key.getEncoded() + " to decrypt");
// Set up cipher to decrypt
IvParameterSpec iv = new IvParameterSpec(ivBytes);
cipher.init(Cipher.DECRYPT_MODE, key, iv);
byte[] decodedPlaintext = cipher.doFinal(ciphertext);
// Encode plaintext
String plaintext = Base64.getEncoder().encodeToString(decodedPlaintext);
return plaintext;
}
Results:
Please enter a phrase to encrypt: test
Using key : [B#442d9b6e to encrypt
Using key [B#3d012ddd to decrypt
Ciphertext: [B#515f550a
Plaintext: dGVzdA==
I don't understand why my encryption/decryption seem to be using different keys when I only set the key once in the beginning. Am I creating a key wrong?
I've also tried:
byte[] key = new byte[32];
SecureRandom r = new SecureRandom();
r.nextBytes(key);
// Set key for AES
aes.setKey(key);
And run into the same issue.
If you trace this.key in AES class all the way through encode and decode using Arrays.toString(this.key.getEncoded()) it does look like it's using a persistent key.
this.key.getEncoded().toString()) returns a different representation each time but it seems like the actual byte array is the same.
I replaced
String plaintext = Base64.getEncoder().encodeToString(decodedPlaintext);
with
String plaintext = new String(decodedPlaintext, StandardCharsets.UTF_8);
and it seemed to work.
I need some help validating the below code snippet for Java AES encryption with CBC, PKCS5Padding and IV.
I tested the code and was able to encrypt and decrypt. I have a few queries as described below.
Where should the password be stored as a good convention?
Is the way of appending/retrieving salt and IV bytes to the ciphetext fine?
Any other comments highly appreciated, thanks!
public class Encryption {
private static int iterations = 65536;
private static int keySize = 128;
private static char[] password = "password".toCharArray();
private static String algorithm= "PBKDF2WithHmacSHA1";
private static final String SEPARATOR = "~";
public static void main(String []args) throws Exception {
String filePath = "test.xml";
String fileContent = new String(Files.readAllBytes(Paths.get(filePath)));
String encrMesg = encrypt(fileContent);
System.out.println("Encrypted: " + encrypt(encrMesg));
System.out.println("Decrypted: " + decrypt(encrMesg));
}
public static String encrypt(String plaintext) throws Exception {
byte[] saltBytes = getSalt().getBytes();
SecretKeyFactory skf = SecretKeyFactory.getInstance(algorithm);
PBEKeySpec spec = new PBEKeySpec(password, saltBytes, iterations, keySize);
SecretKey secretKey = skf.generateSecret(spec);
SecretKeySpec secretSpec = new SecretKeySpec(secretKey.getEncoded(), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secretSpec);
AlgorithmParameters params = cipher.getParameters();
byte[] ivBytes = params.getParameterSpec(IvParameterSpec.class).getIV();
byte[] cipherText = cipher.doFinal(String.valueOf(plaintext).getBytes("UTF-8"));
return DatatypeConverter.printBase64Binary(ivBytes)+SEPARATOR+DatatypeConverter.printBase64Binary(saltBytes)
+SEPARATOR+DatatypeConverter.printBase64Binary(cipherText);
}
public static String decrypt(String encryptedText) throws Exception {
System.out.println(encryptedText);
String[] encryptedArr = encryptedText.split(SEPARATOR);
byte[] ivBytes = DatatypeConverter.parseBase64Binary(new String(encryptedArr[0]));
byte[] salt = DatatypeConverter.parseBase64Binary(new String(encryptedArr[1]));
byte[] encryptedTextBytes = DatatypeConverter.parseBase64Binary(new String(encryptedArr[2]));
SecretKeyFactory skf = SecretKeyFactory.getInstance(algorithm);
PBEKeySpec spec = new PBEKeySpec(password, salt, iterations, keySize);
SecretKey secretKey = skf.generateSecret(spec);
SecretKeySpec secretSpec = new SecretKeySpec(secretKey.getEncoded(), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secretSpec, new IvParameterSpec(ivBytes));
byte[] decryptedTextBytes = null;
try {
decryptedTextBytes = cipher.doFinal(encryptedTextBytes);
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
} catch (BadPaddingException e) {
e.printStackTrace();
}
return new String(decryptedTextBytes);
}
public static String getSalt() throws Exception {
SecureRandom sr = SecureRandom.getInstance("SHA1PRNG");
byte[] salt = new byte[20];
sr.nextBytes(salt);
return new String(salt);
}
}
Queries
Where should the password be stored as a good convention?
Symmetric keys should go preferably to a vault. Otherwise they should go on a keystore, but then you have the issue of securing the keystore password.
Is the way of appending/retrieving Salt and IV bytes to the Cipher
text is fine?
Salt should be generated with:
SecureRandom random = SecureRandom.getInstanceStrong();
Otherwise you are using weaker entropy pools (i.e. /dev/urandom in linux) to generate your secure numbers, and that leads to weak keys that can be more easily broken.
Any other comments highly appreciated, thanks!
You should consistently use the same encoding when dealing with String conversion, i.e., .getBytes("UTF-8") to avoid issues. You don't use it when converting the salt for example.
I have the following code,
public static String encrypt(String plainText, String key){
try{
PublicKey publicKey = KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(Base64.decode(key, Base64.DEFAULT)));
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
return Base64.encodeToString(cipher.doFinal(plainText.getBytes("UTF-8")),Base64.DEFAULT);
}catch (Exception e) {
e.printStackTrace();
}
return null;
}
I want to convert this to C#. I have tried CryptUtils but it doesn't work https://github.com/ServiceStack/ServiceStack/blob/master/src/ServiceStack.Common/CryptUtils.cs
Sample key,
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ9AMIIBCgKCAQEAoqB1N9kugk4UKYnbh0fcg3qYyYKP0m4B
MjWd05ReeAdj+7JRYDEKO6xthDdVSdRO1/2V+YtY8DnXWnxRaICwu8235s3imZCyqgYnomPWdY+F
K540oTz/zug+9wbrlzt/WQFUU4lPlQbzm/Gjw8XfaCozT0e3bnWQcD7rORCOyuJgwSGgREjTv1ss
pgEaKTMknii9vpGZLeAXwoeIYROhuT4IoIkPDhtY0/UZiCi6v7Ja2dmy53VlWIkcm3rcnSJdvpXr
OgiHvaNABHmeymNycNqd6WUaysBRheluQ86nq/2nZPW0gcvmYt5zbMMYX3yY/n2WtAKeNQBAEW1q
b0s6MwIDAQAB
Possible encryped value,
Y3VTjghDnTrCeG8C/RklKsJ3Y0Mt89sSGGin28E4iQPQvKqeZBws7rBQEZaRamDWftxCkEYZs4Qh
V2l4IVlrawdtRmQlcQh8McrpqP/97Gz8pEDEYnqA7kqBTqZw0Z5o0WsshGSwiAQ9wNSym4xHejkq
zrKxWP8XCMkcT0NlKlRMoqKKICFKZbqWeSQkQM5y9OEcmB6inNNkJCoM1Ip48+cK3cOE6dqXNVrl
sSTZ8WQKwoB3dJmcYqexR3kAvBYdX6ZxEF+2+6b9h8+tc5G7Y5R2eqycyUossdkCcI3fNVhyc72P
axCjZFWZUgfDGCxg1WNhStrH9L8c59P35JKKug==
Since i don't have the private key, i can't decrypt, but at least this produces the right lengthed values.
So try this (you need bouncycastle for reading pem):
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Security;
var keyBytes =
Convert.FromBase64String(
"MIIBI...."); // your key here
AsymmetricKeyParameter asymmetricKeyParameter = PublicKeyFactory.CreateKey(keyBytes);
RsaKeyParameters rsaKeyParameters = (RsaKeyParameters)asymmetricKeyParameter;
RSAParameters rsaParameters = new RSAParameters();
rsaParameters.Modulus = rsaKeyParameters.Modulus.ToByteArrayUnsigned();
rsaParameters.Exponent = rsaKeyParameters.Exponent.ToByteArrayUnsigned();
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
rsa.ImportParameters(rsaParameters);
byte[] plaintext = Encoding.UTF8.GetBytes("amount=1¤cy=AED");
byte[] ciphertext = rsa.Encrypt(plaintext, false);
string cipherresult = Convert.ToBase64String(ciphertext);
I have the following class I use to store encrypted preferences to use with my application (using interface with 3rd part site which does not support OAuth)...
public class CryptoTranslator {
private static SecretKey SEC_KEY;
/**
* #return the sEC_KEY
*/
public static SecretKey getSEC_KEY() {
return SEC_KEY;
}
public static String getSEC_KEY_String(){
return Base64.encodeToString(SEC_KEY.getEncoded(), Base64.DEFAULT);
}
/**
* #param sEC_KEY the sEC_KEY to set
*/
public static void setSEC_KEY(SecretKey sEC_KEY) {
SEC_KEY = sEC_KEY;
}
public static void setSEC_KEY_STRING(String sEC_KEY){
byte[] key = Base64.decode(sEC_KEY, Base64.DEFAULT);
SEC_KEY = new SecretKeySpec(key, 0, key.length, "AES");
}
public static void generateKey() throws NoSuchAlgorithmException {
// Generate a 256-bit key
final int outputKeyLength = 256;
SecureRandom secureRandom = new SecureRandom();
// Do *not* seed secureRandom! Automatically seeded from system entropy.
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
keyGenerator.init(outputKeyLength, secureRandom);
SecretKey key = keyGenerator.generateKey();
SEC_KEY = key;
}
private static byte[] getRawKey() throws Exception {
if (SEC_KEY == null){
generateKey();
}
byte[] raw = SEC_KEY.getEncoded();
return raw;
}
/**
*
*
* #param clear clear text string
* #param mode this should either be Cipher.ENCRYPT_MODE or Cipher.DECRYPT_MODE
* #return
* #throws Exception
*/
private static String translate(String clear, int mode) throws Exception {
if(mode != Cipher.ENCRYPT_MODE && mode != Cipher.DECRYPT_MODE)
throw new IllegalArgumentException("Encryption invalid. Mode should be either Cipher.ENCRYPT_MODE or Cipher.DECRYPT_MODE");
SecretKeySpec skeySpec = new SecretKeySpec(getRawKey(), "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(mode, skeySpec);
byte[] encrypted = cipher.doFinal(clear.getBytes());
return new String(encrypted);
}
public static String encrypt(String clear) throws Exception {
return translate(clear,Cipher.ENCRYPT_MODE);
}
public static String decrypt(String encrypted) throws Exception {
return translate(encrypted,Cipher.DECRYPT_MODE);
}
}
So now I have encrypted and stored the data. Now I want to pull it out...
String secString = settings.getString(SEC_KEY, null);
if (secString == null) {
try {
CryptoTranslator.generateKey();
settings.edit()
.putString(SEC_KEY,
CryptoTranslator.getSEC_KEY_String()).commit();
} catch (NoSuchAlgorithmException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} else {
CryptoTranslator.setSEC_KEY_STRING(secString);
}
try {
getUserNamePassword();
} catch (Exception ex) {
Log.i("Preferences",
"There was an issue getting username and password");
isStored = CRED_STATUS_DEF;
}
...
private static void getUserNamePassword() throws Exception {
isStored = settings.getBoolean(CRED_STATUS, CRED_STATUS_DEF);
if (isStored) {
if (settings.contains(USERNAME_KEY))
username = settings.getString(USERNAME_KEY, "");
if (settings.contains(PASSWORD_KEY))
password = settings.getString(PASSWORD_KEY, "");
}
isUsernamePasswordValid();
if (isStored) {
String username2 = CryptoTranslator.decrypt(username);
Log.d("Security", "Username encrypted");
String password2 = CryptoTranslator.decrypt(password);
username = username2;
password = password2;
Log.d("Security", "Password encrypted");
}
}
But this gives me the following error....
javax.crypto.IllegalBlockSizeException: last block incomplete in decryption
Can someone see what I am doing wrong?
Update
Ok per the response I went ahead and changed my code to the following...
public static final int IV_LENGTH = 16;
private static final String RANDOM_ALGORITHM = "SHA1PRNG";
...
private static String translate(String clear, int mode) throws Exception {
if (mode != Cipher.ENCRYPT_MODE && mode != Cipher.DECRYPT_MODE)
throw new IllegalArgumentException(
"Encryption invalid. Mode should be either Cipher.ENCRYPT_MODE or Cipher.DECRYPT_MODE");
SecretKeySpec skeySpec = new SecretKeySpec(getRawKey(), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec ivSpec = new IvParameterSpec(generateIv());
cipher.init(mode, skeySpec, ivSpec);
byte[] encrypted = cipher.doFinal(clear.getBytes());
return new String(encrypted);
}
...
private static byte[] generateIv() throws NoSuchAlgorithmException,
NoSuchProviderException {
SecureRandom random = SecureRandom.getInstance(RANDOM_ALGORITHM);
byte[] iv = new byte[IV_LENGTH];
random.nextBytes(iv);
return iv;
}
Now I get...
javax.crypto.BadPaddingException: pad block corrupted
To try and use hex changed to...
private static byte[] translate(byte[] val, int mode) throws Exception {
if (mode != Cipher.ENCRYPT_MODE && mode != Cipher.DECRYPT_MODE)
throw new IllegalArgumentException(
"Encryption invalid. Mode should be either Cipher.ENCRYPT_MODE or Cipher.DECRYPT_MODE");
SecretKeySpec skeySpec = new SecretKeySpec(getRawKey(), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec ivSpec = new IvParameterSpec(generateIv());
cipher.init(mode, skeySpec, ivSpec);
byte[] encrypted = cipher.doFinal(val);
return encrypted;
}
This seems to almost work (I am getting the .com back) but the chars are still pretty jumbled.
public static String encrypt(String clear) throws Exception {
byte[] test = translate(clear.getBytes(), Cipher.ENCRYPT_MODE);
return new String(Hex.encodeHex(test));
}
public static String decrypt(String encrypted) throws Exception {
return new String(translate(Hex.decodeHex(encrypted.toCharArray()), Cipher.DECRYPT_MODE));
}
*The converting to Hex and back is screwed up here.
So there are a couple of issues with your code.
First is the output of an AES cipher is not character data, you are mangling your ciphertext by trying to put it in a String. When you try to decrypt your mangled ciphertext it is now the wrong length. You need to Base64 or Hex encode the ciphertext if you want to store it in a String and then decode it back in to a byte[] before decrypting it.
Second, when you specify just AES for your cipher spec Java expands that to AES/ECB/PKCS5Padding. ECB is an insecure cipher mode if you intend to encrypt more than 1 block of data (16 bytes for AES). I recommend you switch to a different spec AES/CBC/PKCS5Padding should be acceptable. Using a mode other than ECB will require an Initialization Vector (IV). The IV should be randomly generated but does not need to be secret, so you can store as plaintext with your ciphertext as you'll need it to decrypt as well. The initialization vector needs to be one block in length (16 bytes for AES). Do not reuse the same IV with the same AES key ever, generate a new IV for each encryption being done.
Finally, if your going to store IV + ciphertext in a third party service I recommend you add a MAC (such as HMACSHA1). A MAC will ensure the integrity of your IV + ciphertext before you attempt to decrypt it. A MAC will require a secret key as well, and you should not use the same key you generated for the cipher itself. You can prepend the generated MAC to your IV + ciphertext, so now you are storing MAC + IV + ciphertext.
Android AES client side + PHP AES server side it will throw this error :)
The solution is:
cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
Please search over the internet for the full source code. I am under NDA and to lazzy to make anonymous my whole code regarding this part, but I am sure you will find it.
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);
}
The SecretKey object needs to return "AES" from its getAlgorithm() method. That's why the example has these steps:
SecretKey tmp = factory.generateSecret(spec);
SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "AES");