AES Encryption
I am trying to encrypt and decrypt some text using AES technique. Sometimes the code below decrypts fine with an exception of padding at the end of the string. In some cases it doesn抰 decrypt the text (message) properly.
If there is a problem in the code below, please identify it or otherwise please provide some alternative.
Thanking you in advance.
========================= CODE ====================
/*
public class FinalAES {
public FinalAES() {
}
public static void main(String arg[]){
try{
String toEncrypt = "Iamverymuchhappy";//"?;//"ONEtwoTHREE";
String[] returnedData = encryptData(toEncrypt);
System.out.println("**********************************************");
System.out.println("RAW: " + returnedData[0]);
System.out.println("ENCRYPTED: " + returnedData[1]);
System.out.println("**********************************************DE");
String deencrypted = decryptData(returnedData[1],returnedData[0]);
System.out.println("RETURN_DECRYPTED: " + deencrypted);
System.out.println("**********************************************");
}
catch(Exception e){
e.printStackTrace();
}
}
private static String[] encryptData(String toEncrypt){
try{
String message = toEncrypt;
System.out.println("message: " + message);
KeyGenerator kgen = KeyGenerator.getInstance("AES");
kgen.init(128);
SecretKey skey = kgen.generateKey();
byte[] raw = skey.getEncoded();
SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
byte[] encrypted =
cipher.doFinal(message.getBytes());
String[] returnValues = new String[2];
returnValues[0] = new String(raw);
returnValues[1] = new String(encrypted);
return returnValues;
}
catch(Exception ene){
ene.printStackTrace();
}
return null;
}
private static String decryptData(String toDeEncrypt, String rawStr){
try{
String message = toDeEncrypt;
KeyGenerator kgen = KeyGenerator.getInstance("AES");
kgen.init(128);
SecretKey skey = kgen.generateKey();
byte[] raw = (rawStr).getBytes();
SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, skeySpec);
byte[] deencrypted =
cipher.doFinal(message.getBytes());
return new String(deencrypted);
}
catch(Exception tde){
tde.printStackTrace();
}
return null;
}
}
===================== RESULTS =======================
RESULT-1
``````````````
message: Iamverymuchhappy
**********************************************
RAW: ?W~薊衑k%?M?br>ENCRYPTED: 汾€?ㄦ)寜K8'y蔋)u首偭?&9
**********************************************DE
RETURN_DECRYPTED: Iamverymuchhappy
**********************************************
RESULT-2
``````````````
message: Iamverymuchhappy
**********************************************
RAW: 煪d?Q"遢~
ENCRYPTED: 啬c?(荒_jdL/侠l@=b顽p:罅?
**********************************************DE
RETURN_DECRYPTED: 1鼹/?豊?!?
**********************************************
# 1
You have 2 major problems and one minor problem.
1) You have an example of the most common problem seen in this forum You convert the encrypted bytes to a String using returnValues[1] = new String(encrypted);
which may not be invertible. If you must have a String representation of the encrypted data then you should Hex or Base64 encode the encrypted data. Google for Jakarta Commons Codec for classes to perform this.
2) You use a different random key for encryption and decryption so I am surprised that you ever get a successful decryption.
3) The secondary problem with your code which may never result in an error is your use of the default encoding to convert the string to be encrypted to bytes. Since the default encoding may be platform, operating system and locale dependent you may get a different set of bytes for different operating systems and/or locale. You should force the encoding - the safest being UTF-8 which is defined for ALL Java characters. For examplebyte[] encrypted =
cipher.doFinal(message.getBytes("utf-8"));
andreturn new String(deencrypted,"utf-8");
P.S. I don't like the concept of returning an array containing the plain text and the encrypted text. If you must return the plain text
then why not create an object with two fields and return that.
Edit : Why do you specify 'NoPadding'?
Message was edited by:
sabre150
# 2
I have converted the encrypted data to save it into database as for String we have Varchar (variable length). If you feel it is not right, then advice me in which data type of the database should I place the key and the encrypted data?
In contrast if I don抰 save the key in database then where should I place my key to avoid generating different key for encryption and decryption? And my program will give me the same key after restarting the system. Additionally how do I generate the key for the very first time?
No-padding was applied to achieve result?
# 3
> I have converted the encrypted data to save it into
> database as for String we have Varchar (variable
> length). If you feel it is not right, then advice me
> in which data type of the database should I place the
> key and the encrypted data?
I have advised you! Use Base64 or Hex encoding.
>
> In contrast if I don抰 save the key in database then
> where should I place my key to avoid generating
> different key for encryption and decryption? And my
> program will give me the same key after restarting
> the system.
As a byte array on disk or in a Java keystore or something more sophisticated like 'strongkey'.
> Additionally how do I generate the key
> for the very first time?
Many ways but the most simple is probably to use an AES key generator initialized with the desired key length and an instance of SecureRandom.
>
> No-padding was applied to achieve result?
I don't understand!
# 4
Now I have altered the code please check why do not it results the correct string.
I am also send the result along with the code.
public class FinalAES {
public static void main(String arg[]){
try{
String toEncrypt = "Ammar 錩 Ammar";//"Iamverymuchhappy";//"?;//"ONEtwoTHREE";
String returnedData = encryptData(toEncrypt);
System.out.println("**********************************************");
//System.out.println("RAW: " + returnedData[0]);
System.out.println("ENCRYPTED: " + returnedData);
System.out.println("**********************************************DE");
String deencrypted = decryptData(returnedData);
System.out.println("RETURN_DECRYPTED: " + deencrypted);
System.out.println("**********************************************");
} catch(Exception e){
e.printStackTrace();
}
}
private static String encryptData(String toEncrypt){
try{
System.out.println("toEncrypt: " + toEncrypt);
KeyGenerator kgen = KeyGenerator.getInstance("AES");
kgen.init(128);
SecretKey skey = new SecretKeySpec("1234567890123456".getBytes(),"AES");//kgen.generateKey();
byte[] raw = skey.getEncoded();
SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
byte[] encrypted =
cipher.doFinal(toEncrypt.getBytes());
return new String(encrypted);
} catch(Exception ene){
ene.printStackTrace();
}
return null;
}
private static String decryptData(String toDeEncrypt){
try{
KeyGenerator kgen = KeyGenerator.getInstance("AES");
kgen.init(128);
SecretKey skey = kgen.generateKey();
SecretKeySpec skeySpec = new SecretKeySpec("1234567890123456".getBytes(), "AES");
Cipher cipher = Cipher.getInstance("AES");//AES/ECB/NoPadding
cipher.init(Cipher.DECRYPT_MODE, skeySpec);
byte[] deencrypted =
cipher.doFinal(toDeEncrypt.getBytes());
return new String(deencrypted);
}
catch(Exception tde){
tde.printStackTrace();
}
return null;
}
}
============= RESULT =================
toEncrypt: Ammar 錩 Ammar
**********************************************
ENCRYPTED: 睦?<T4o堞>聓,??湾﹪,喊懌s錝
**********************************************DE
RETURN_DECRYPTED: ?-T?4]|?
**********************************************
# 5
Did you read anything I wrote in reply #1 or reply #3?
# 6
Yes, I did. I think other then using cipher.doFinal(toEncrypt.getBytes("utf-8"))I did what you said. If I am missing something please let me know. In other words I don't understand what you were saying.
# 7
Moreover if my String toEncrypt
do not use any space it does work fine. however if I use a char for morethan one time in a string at different positions "Ammar 錩 Ammar"
then it will not decrypt properly.
Additional if I use morethan one space in a String "A m m a r錩Am m ar"
it will give the following exception:
javax.crypto.BadPaddingException: Given final block not properly padded
at com.sun.crypto.provider.SunJCE_h.b(DashoA12275)
at com.sun.crypto.provider.SunJCE_h.b(DashoA12275)
at com.sun.crypto.provider.AESCipher.engineDoFinal(DashoA12275)
at javax.crypto.Cipher.doFinal(DashoA12275)
# 8
> Yes, I did.
> I think other then using
> [code]cipher.doFinal(toEncrypt.getBytes("utf-8"))[/cod
> e]
> I did what you said. If I am missing something please
> let me know. In other words I don't understand what
> you were saying.
You have done none of what I said! You have not Base64 or Hex encoded the encrypted result. You still have a random key for encryption and a different random key for decryption. You still use the default character encoding.
# 9
Then correct meI think by doing this I am generation same key everytime SecretKeySpec skeySpec = new SecretKeySpec("1234567890123456".getBytes(), "AES");
# 10
> Then correct me
>
> I think by doing this I am generation same key
> everytime
> SecretKeySpec skeySpec = new
> SecretKeySpec("1234567890123456".getBytes(),
> "AES");
Yes, you are right. You have fixed one of the problems but why have you left inKeyGenerator kgen = KeyGenerator.getInstance("AES");
kgen.init(128);
Fix the others now.
Message was edited by:
sabre150
# 11
What should I do about this:
KeyGenerator kgen = KeyGenerator.getInstance("AES");
kgen.init(128);
Additionally I am sending you my new code, tell me as there is an exception
public class FinalAES {
public static void main(String arg[]){
try{
String toEncrypt = "A m m a r錩Am m ar";//"!屈镼AS W SE#$?T^Y&";//Ammar 錩 Ammar";//"Iamverymuchhappy";//"?;//"ONEtwoTHREE";
String returnedData = encryptData(toEncrypt);
System.out.println("**********************************************");
//System.out.println("RAW: " + returnedData[0]);
System.out.println("ENCRYPTED: " + returnedData);
System.out.println("**********************************************DE");
String deencrypted = decryptData(returnedData);
System.out.println("RETURN_DECRYPTED: " + new String(deencrypted));
System.out.println("**********************************************");
//SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
//byte[] btyerand = random.generateSeed(toEncrypt.getBytes().length);
//System.out.println("btyerand: " + new String(btyerand));
} catch(Exception e){
e.printStackTrace();
}
}
private static String encryptData(String toEncrypt){
try{
System.out.println("toEncrypt: " + toEncrypt);
KeyGenerator kgen = KeyGenerator.getInstance("AES");
kgen.init(128);
SecretKey skey = new SecretKeySpec("1234567890123456".getBytes(),"AES");//kgen.generateKey();
byte[] raw = skey.getEncoded();
SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
byte[] encrypted =
cipher.doFinal(toEncrypt.getBytes("utf-8"));
return asHex(encrypted);
} catch(Exception ene){
ene.printStackTrace();
}
return null;
}
private static String decryptData(String toDecrypt){
try{
KeyGenerator kgen = KeyGenerator.getInstance("AES");
kgen.init(128);
SecretKey skey = kgen.generateKey();
SecretKeySpec skeySpec = new SecretKeySpec("1234567890123456".getBytes(), "AES");
Cipher cipher = Cipher.getInstance("AES");//AES/ECB/NoPadding
cipher.init(Cipher.DECRYPT_MODE, skeySpec);
byte[] bts = new BigInteger(toDecrypt, 16).toByteArray();
String strBytes = new String(bts);
byte[] decrypted =
cipher.doFinal(strBytes.getBytes("utf-8"));
return asHex(decrypted);
}
catch(Exception tde){
tde.printStackTrace();
}
return null;
}
public static String asHex (byte buf[]) {
StringBuffer strbuf = new StringBuffer(buf.length * 2);
int i;
for (i = 0; i < buf.length; i++) {
if (((int) buf[i] & 0xff) < 0x10)
strbuf.append("0");
strbuf.append(Long.toString((int) buf[i] & 0xff, 16));
}
return strbuf.toString();
}
}
The exception is:
javax.crypto.IllegalBlockSizeException: Input length must be multiple of 16 when decrypting with padded cipher
at com.sun.crypto.provider.SunJCE_h.b(DashoA12275)
at com.sun.crypto.provider.SunJCE_h.b(DashoA12275)
at com.sun.crypto.provider.AESCipher.engineDoFinal(DashoA12275)
at javax.crypto.Cipher.doFinal(DashoA12275)
at encryption.FinalAES.decryptData(FinalAES.java:91)
at encryption.FinalAES.main(FinalAES.java:34)
# 12
I can't keep spoon feeding you. You have to do some reading and you have to learn to test code. I suspect that you hex encoding/decoding is failing. I suggested you look at Jakarta Commons Codec but for some reason you decided to ignore this advice and try to write your own. What is worse, you don't even seem to have tested your own code for this transformation.
Message was edited by:
sabre150
# 13
Ok, I am now using Jakarta Commons Codec. So there is no reason that Encoding/Decoding can go wrong. Right!
In addition you also said to use "utf-8"
cipher.doFinal(toEncrypt.getBytes("utf-8"))
Either I use it or don't use it the code did not decrypt properly. However one thing in my view can be incorrect or missing is in decrypt where I can not make the hex byte to utf-8
I am also attaching the code:
public class FinalAES {
static SecretKey skey = new SecretKeySpec("1234567890123456".getBytes(),"AES");
public static void main(String arg[]){
try{
String toEncrypt = "ABCDEF";//"A m m a r錩Am m ar";//"!屈镼AS W SE#$?T^Y&";//Ammar 錩 Ammar";//"Iamverymuchhappy";//"?;//"ONEtwoTHREE";
byte[] returnedData = encryptData(toEncrypt);
System.out.println("**********************************************");
String decrypted = decryptData(returnedData);
System.out.println("RETURN_DECRYPTED: " + decrypted);
System.out.println("**********************************************");
} catch(Exception e){
e.printStackTrace();
}
}
private static byte[] encryptData(String toEncrypt){
try{
System.out.println("toEncrypt: " + toEncrypt);
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, skey);
byte[] encrypted =
cipher.doFinal(toEncrypt.getBytes("utf-8"));
return encrypted;
} catch(Exception ene){
ene.printStackTrace();
}
return null;
}
private static String decryptData(byte[] toDecrypt){
try{
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, skey);
Hex hexObj = new Hex();
byte[] bts = hexObj.encode(toDecrypt);
byte[] decrypted =
cipher.doFinal(bts);
return new String(decrypted);
}
catch(Exception tde){
tde.printStackTrace();
}
return null;
}
}
# 14
The whole point of the Hex encoding is that the resulting bytes can be converted into ASCII characters and viewed or stored in a data database in a reversible form. For some reason you only use the Hex class in your decrypt method.
You either need
1) to hex encode in your encrypt() method the result of the doFinal() before returning the result and then hex decode inside your decrypt() before the doFinal(). i.e. in encrypt()return new String(hexEncoder.encode(encrypted), "ASCII");
and inside decrypt you needbyte[] bytesToDecrypt = hexEncoder.decode(hexEncodedEncryptedData.getBytes("ASCII"));
OR
2) to just remove the Hex code stuff in the decrypt() method and work only with the bytes[]. If you do this you might have trouble storing the result in a database unless the column is abinary type such a BLOB.
# 15
I have tried both the techniques but no luck :(
private static byte[] encryptData(String toEncrypt){
try{
System.out.println("toEncrypt: " + toEncrypt);
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, skey);
byte[] encrypted =
cipher.doFinal(toEncrypt.getBytes());
//Hex hexObj = new Hex();
//return new String(hexObj.encode(encrypted), "ASCII");////encrypted;
return encrypted;
} catch(Exception ene){
ene.printStackTrace();
}
return null;
}
private static String decryptData(byte[] toDecrypt){
try{
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, skey);
//Hex hexObj = new Hex();
//byte[] bts = hexObj.encode(hexObj.decode(toDecrypt.getBytes("ASCII")));
//byte[] decrypted = cipher.doFinal(bts);
byte[] decrypted = cipher.doFinal(toDecrypt);
//return new String(decrypted);
Hex hexEncoder = new Hex();
return new String(hexEncoder.encode(decrypted), "ASCII");
}
catch(Exception tde){
tde.printStackTrace();
}
return null;
}
# 16
I suspect you should think a bout a change of career. Surely the decrypt cipher should be initialized with Cipher.DECRYPT .
# 17
Please, try methods from the code below, I haven't experienced the problem you describe, with them. Furthermore, using CBC padded encryption is known as being more secure
import java.security.SecureRandom;
import javax.crypto.*;
import javax.crypto.spec.*;
public class JCE {
// Create the initialization vector required for CBC mode
private static IvParameterSpec ivParametersDESede = new IvParameterSpec(
new byte[] { 12, 34, 56, 78, 90, 87, 65, 43 });
private static IvParameterSpec ivParametersAES = new IvParameterSpec(
new byte[] { 12, 34, 56, 78, 90, 87, 65, 43,
12, 34, 56, 78, 90, 87, 65, 43});
public static void main(String[] args) throws Exception {
testCryptDESede_CBC();
testCryptAES_CBC();
}
static void testCryptDESede_CBC() throws Exception {
System.out.println(" start testing DESede with CBC padding --");
String text = "some text input .. used here for testing DESede";
String pass = "some password .........."; // must be >= 24 bytes
System.out.println(text);
byte[] plaintext = text.getBytes();
byte[] key = pass.getBytes();
byte[] ciphertext = cryptDESedeCBC(plaintext, key, Cipher.ENCRYPT_MODE);
System.out.println(new String(ciphertext));
byte[] decrypted = cryptDESedeCBC(ciphertext, key, Cipher.DECRYPT_MODE);
System.out.println(new String(decrypted));
try { //try decrypting with wrong password
byte[] decryptedWrongPass = cryptDESedeCBC(ciphertext, "wrong pass".getBytes(), Cipher.DECRYPT_MODE);
throw new RuntimeException("It should crash here ! password is wrong!!!");
} catch(Exception ex) {
//it's ok, it must crash if the password is wrong
}
}
static void testCryptAES_CBC() throws Exception {
System.out.println(" start testing AES with CBC padding --");
String text = "some text input .. used here for testing AES";
String pass = "some password here"; // can be of any length
System.out.println(text);
byte[] plaintext = text.getBytes();
byte[] key = pass.getBytes();
byte[] ciphertext = cryptAES_CBC(plaintext, key, Cipher.ENCRYPT_MODE);
System.out.println(new String(ciphertext));
byte[] decrypted = cryptAES_CBC(ciphertext, key, Cipher.DECRYPT_MODE);
System.out.println(new String(decrypted));
try { //try decrypting with wrong password
byte[] decryptedWrongPass = cryptAES_CBC(ciphertext, "wrong pass".getBytes(), Cipher.DECRYPT_MODE);
throw new RuntimeException("It should crash here ! password is wrong!!!");
} catch(javax.crypto.BadPaddingException ex) {
//it's ok, it must crash if the password is wrong
}
}
public static byte[] cryptDESedeCBC(byte[] ciphertext, byte[] encryptKey, int mode)
throws Exception {
// Create a DESede key spec from the key
DESedeKeySpec spec = new DESedeKeySpec(encryptKey);
// Get the secret key factor for generating DESede keys
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DESede");
// Generate a DESede SecretKey object
SecretKey theKey = keyFactory.generateSecret(spec);
// Create a DESede Cipher
Cipher cipher = Cipher.getInstance("DESede/CBC/PKCS5Padding");
// Initialize the cipher and put it in decrypt mode
cipher.init(mode, theKey, ivParametersDESede);
// Decrypt the data
byte[] plaintext = cipher.doFinal(ciphertext);
// return the result
return plaintext;
}
/**
*
* @param input --> the input to be encrypted or decrypted
* @param keySeed --> a byte array used as the seed for generating the encryption key
* @param mode --> Possible values are Cipher.ENCRYPT_MODE and Cipher.DECRYPT_MODE
* @return the result of encrypting or decrypting the input (the type of operation
* depending on mode parameter)
* @throws Exception
*/
public static byte[] cryptAES_CBC(byte[] input, byte[] keySeed, int mode)
throws Exception {
KeyGenerator keygen = KeyGenerator.getInstance("AES");
keygen.init(new SecureRandom(keySeed));
SecretKey theKey = keygen.generateKey();
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(mode, theKey, ivParametersAES);
return cipher.doFinal(input);
}
}
PS: to turn byte arrays into Strings and vice-versa use
new sun.misc.BASE64Encoder().encode(byte[] b)
respectively
new sun.misc.BASE64Decoder().decode(String s)
Best Regards.
