Java AES encryption and decryption
I want to encrypt and decrypt a password using 128 bit AES encryption and a 16 byte key. There was an error javax.crypto.BadPaddingException
decrypting the value . Do I lose anything when decrypting?
public static void main(String args[]) {
Test t = new Test();
String encrypt = new String(t.encrypt("mypassword"));
System.out.println("decrypted value:" + t.decrypt("ThisIsASecretKey", encrypt));
}
public String encrypt(String value) {
try {
byte[] raw = new byte[]{'T', 'h', 'i', 's', 'I', 's', 'A', 'S', 'e', 'c', 'r', 'e', 't', 'K', 'e', 'y'};
SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
byte[] encrypted = cipher.doFinal(value.getBytes());
System.out.println("encrypted string:" + (new String(encrypted)));
return new String(skeySpec.getEncoded());
} catch (NoSuchAlgorithmException ex) {
Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex);
} catch (IllegalBlockSizeException ex) {
Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex);
} catch (BadPaddingException ex) {
Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex);
} catch (InvalidKeyException ex) {
Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex);
} catch (NoSuchPaddingException ex) {
Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex);
}
return null;
}
public String decrypt(String key, String encrypted) {
try {
SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes(), "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(skeySpec.getEncoded(), "AES"));
//getting error here
byte[] original = cipher.doFinal(encrypted.getBytes());
return new String(original);
} catch (IllegalBlockSizeException ex) {
Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex);
} catch (BadPaddingException ex) {
Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex);
} catch (InvalidKeyException ex) {
Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex);
} catch (NoSuchAlgorithmException ex) {
Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex);
} catch (NoSuchPaddingException ex) {
Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex);
}
return null;
}
error message
encrypted string:�Bj�.�Ntk�F�`�
encrypted key:ThisIsASecretKey
decrypted value:null
May 25, 2012 12:54:02 PM bean.Test decrypt
SEVERE: null
javax.crypto.BadPaddingException: Given final block not properly padded
at com.sun.crypto.provider.SunJCE_f.b(DashoA13*..)
at com.sun.crypto.provider.SunJCE_f.b(DashoA13*..)
at com.sun.crypto.provider.AESCipher.engineDoFinal(DashoA13*..)
at javax.crypto.Cipher.doFinal(DashoA13*..)
at bean.Test.decrypt(Test.java:55)
at bean.Test.main(Test.java:24)
In the end I used the following solution based on @QuantumMechanic answer
public class Test {
public String encryptionKey;
public static void main(String args[]) {
Test t = new Test();
String encrypt = t.encrypt("mypassword");
System.out.println("decrypted value:" + t.decrypt(t.encryptionKey, encrypt));
}
public String encrypt(String value) {
try {
// Get the KeyGenerator
KeyGenerator kgen = KeyGenerator.getInstance("AES");
kgen.init(256);
// Generate the secret key specs.
SecretKey skey = kgen.generateKey();
byte[] raw = skey.getEncoded();
String key = new Base64().encodeAsString(raw);
this.encryptionKey = key;
System.out.println("------------------Key------------------");
System.out.println(key);
System.out.println("--------------End of Key---------------");
SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
String encrypt = (new Base64()).encodeAsString(cipher.doFinal(value.getBytes()));
System.out.println("encrypted string:" + encrypt);
return encrypt;
} catch (NoSuchAlgorithmException ex) {
Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex);
} catch (IllegalBlockSizeException ex) {
Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex);
} catch (BadPaddingException ex) {
Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex);
} catch (InvalidKeyException ex) {
Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex);
} catch (NoSuchPaddingException ex) {
Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex);
}
return null;
}
public String decrypt(String key, String encrypted) {
try {
Key k = new SecretKeySpec(Base64.getDecoder().decode(key), "AES");
Cipher c = Cipher.getInstance("AES");
c.init(Cipher.DECRYPT_MODE, k);
byte[] decodedValue = Base64.getDecoder().decode(encrypted);
byte[] decValue = c.doFinal(decodedValue);
String decryptedValue = new String(decValue);
return decryptedValue;
} catch (IllegalBlockSizeException ex) {
Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex);
} catch (BadPaddingException ex) {
Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex);
} catch (InvalidKeyException ex) {
Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex);
} catch (NoSuchAlgorithmException ex) {
Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex);
} catch (NoSuchPaddingException ex) {
Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex);
}
return null;
}
}
If for a block cipher you won't be using a transformation that Cipher
includes a padding scheme , you need to make the number of bytes in the plaintext an integer multiple of the cipher's block size.
So either pad the plain text to a multiple of 16 bytes (i.e. the AES block size) or specify a padding scheme when creating the Cipher
object . For example, you can use:
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
Unless you have a good reason not to, use the padding scheme already included in the JCE implementation. They take into account many subtle and extreme situations that you would otherwise have to be aware of and deal with yourself.
Ok, your second question is what you want String
to use to save the ciphertext.
Generally speaking,
String s = new String(someBytes);
byte[] retrievedBytes = s.getBytes();
Will there be someBytes
and retrievedBytes
is the same.
If you want/have to save the ciphertext in String
, base64 encode the ciphertext bytes first, then construct String
from the base64 encoded bytes . Then when you decrypt, getBytes()
you get the base64 encoded bytes from String
it, then base64 decode them to get the real ciphertext, then decrypt it.
The problem arises because most (all?) character encodings cannot map arbitrary bytes to valid characters. So when creating the code String
from the ciphertext , the String
constructor (which converts bytes to characters using the character encoding) essentially has to throw away some bytes because it means nothing to them. So when you get the bytes from the string, they are not the same as the bytes you put into the string.
In Java (and in modern programming in general) you can't assume that one character = one byte unless you absolutely know you're dealing with ASCII. That's why you need to use base64 (or something like that) when you want to build a string from arbitrary bytes.