We can use some algorithms for padding block when the plaintext is not enough a block, like PKCS5 or PKCS7, it also can defend against PA attack, if we use ECB or CBC mode. Or we can use the mode of AES which support a stream of plaintext, like CFB, OFB, CTR mode.
CBC (short for cipher-block chaining) is a AES block cipher mode that trumps the ECB mode in hiding away patterns in the plaintext. CBC mode achieves this by XOR-ing the first plaintext block (B1) with an initialization vector before encrypting it.
Both the AES-CBC and AES-GCM are able to secure your valuable data with a good implementation. but to prevent complex CBC attacks such as Chosen Plaintext Attack(CPA) and Chosen Ciphertext Attack(CCA) it is necessary to use Authenticated Encryption. So the best option is for that is GCM.
Lot of people including myself face lot of issues in making this work due to missing some information like, forgetting to convert to Base64, initialization vectors, character set, etc. So I thought of making a fully functional code.
Hope this will be useful to you all: To compile you need additional Apache Commons Codec jar, which is available here: http://commons.apache.org/proper/commons-codec/download_codec.cgi
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Base64;
public class Encryptor {
public static String encrypt(String key, String initVector, String value) {
try {
IvParameterSpec iv = new IvParameterSpec(initVector.getBytes("UTF-8"));
SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);
byte[] encrypted = cipher.doFinal(value.getBytes());
System.out.println("encrypted string: "
+ Base64.encodeBase64String(encrypted));
return Base64.encodeBase64String(encrypted);
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
public static String decrypt(String key, String initVector, String encrypted) {
try {
IvParameterSpec iv = new IvParameterSpec(initVector.getBytes("UTF-8"));
SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);
byte[] original = cipher.doFinal(Base64.decodeBase64(encrypted));
return new String(original);
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
public static void main(String[] args) {
String key = "Bar12345Bar12345"; // 128 bit key
String initVector = "RandomInitVector"; // 16 bytes IV
System.out.println(decrypt(key, initVector,
encrypt(key, initVector, "Hello World")));
}
}
In this answer I choose to approach the "Simple Java AES encrypt/decrypt example" main theme and not the specific debugging question because I think this will profit most readers.
This is a simple summary of my blog post about AES encryption in Java so I recommend reading through it before implementing anything. I will however still provide a simple example to use and give some pointers what to watch out for.
In this example I will choose to use authenticated encryption with Galois/Counter Mode or GCM mode. The reason is that in most case you want integrity and authenticity in combination with confidentiality (read more in the blog).
Here are the steps required to encrypt/decrypt with AES-GCM with the Java Cryptography Architecture (JCA). Do not mix with other examples, as subtle differences may make your code utterly insecure.
As it depends on your use-case, I will assume the simplest case: a random secret key.
SecureRandom secureRandom = new SecureRandom();
byte[] key = new byte[16];
secureRandom.nextBytes(key);
SecretKey secretKey = SecretKeySpec(key, "AES");
Important:
SecureRandom
An initialization vector (IV) is used so that the same secret key will create different cipher texts.
byte[] iv = new byte[12]; //NEVER REUSE THIS IV WITH SAME KEY
secureRandom.nextBytes(iv);
Important:
SecureRandom
final Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
GCMParameterSpec parameterSpec = new GCMParameterSpec(128, iv); //128 bit auth tag length
cipher.init(Cipher.ENCRYPT_MODE, secretKey, parameterSpec);
byte[] cipherText = cipher.doFinal(plainText);
Important:
CipherInputStream
when encrypting large chunks of datacipher.updateAAD(associatedData);
More here.
Just append IV and ciphertext. As stated above, the IV doesn't need to be secret.
ByteBuffer byteBuffer = ByteBuffer.allocate(iv.length + cipherText.length);
byteBuffer.put(iv);
byteBuffer.put(cipherText);
byte[] cipherMessage = byteBuffer.array();
Optionally encode with Base64 if you need a string representation. Either use Android's or Java 8's built-in implementation (do not use Apache Commons Codec - it's an awful implementation). Encoding is used to "convert" byte arrays to string representation to make it ASCII safe e.g.:
String base64CipherMessage = Base64.getEncoder().encodeToString(cipherMessage);
If you have encoded the message, first decode it to byte array:
byte[] cipherMessage = Base64.getDecoder().decode(base64CipherMessage)
Important:
Initialize the cipher and set the same parameters as with the encryption:
final Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
//use first 12 bytes for iv
AlgorithmParameterSpec gcmIv = new GCMParameterSpec(128, cipherMessage, 0, 12);
cipher.init(Cipher.DECRYPT_MODE, secretKey, gcmIv);
//use everything from 12 bytes on as ciphertext
byte[] plainText = cipher.doFinal(cipherMessage, 12, cipherMessage.length - 12);
Important:
cipher.updateAAD(associatedData);
if you added it during encryption.A working code snippet can be found in this gist.
Note that most recent Android (SDK 21+) and Java (7+) implementations should have AES-GCM. Older versions may lack it. I still choose this mode, since it is easier to implement in addition to being more efficient compared to similar mode of Encrypt-then-Mac (with e.g. AES-CBC + HMAC). See this article on how to implement AES-CBC with HMAC.
Here a solution without Apache Commons Codec
's Base64
:
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
public class AdvancedEncryptionStandard
{
private byte[] key;
private static final String ALGORITHM = "AES";
public AdvancedEncryptionStandard(byte[] key)
{
this.key = key;
}
/**
* Encrypts the given plain text
*
* @param plainText The plain text to encrypt
*/
public byte[] encrypt(byte[] plainText) throws Exception
{
SecretKeySpec secretKey = new SecretKeySpec(key, ALGORITHM);
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
return cipher.doFinal(plainText);
}
/**
* Decrypts the given byte array
*
* @param cipherText The data to decrypt
*/
public byte[] decrypt(byte[] cipherText) throws Exception
{
SecretKeySpec secretKey = new SecretKeySpec(key, ALGORITHM);
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, secretKey);
return cipher.doFinal(cipherText);
}
}
Usage example:
byte[] encryptionKey = "MZygpewJsCpRrfOr".getBytes(StandardCharsets.UTF_8);
byte[] plainText = "Hello world!".getBytes(StandardCharsets.UTF_8);
AdvancedEncryptionStandard advancedEncryptionStandard = new AdvancedEncryptionStandard(
encryptionKey);
byte[] cipherText = advancedEncryptionStandard.encrypt(plainText);
byte[] decryptedCipherText = advancedEncryptionStandard.decrypt(cipherText);
System.out.println(new String(plainText));
System.out.println(new String(cipherText));
System.out.println(new String(decryptedCipherText));
Prints:
Hello world!
դ;��LA+�ߙb*
Hello world!
Looks to me like you are not dealing properly with your Initialization Vector (IV). It's been a long time since I last read about AES, IVs and block chaining, but your line
IvParameterSpec ivParameterSpec = new IvParameterSpec(aesKey.getEncoded());
does not seem to be OK. In the case of AES, you can think of the initialization vector as the "initial state" of a cipher instance, and this state is a bit of information that you can not get from your key but from the actual computation of the encrypting cipher. (One could argue that if the IV could be extracted from the key, then it would be of no use, as the key is already given to the cipher instance during its init phase).
Therefore, you should get the IV as a byte[] from the cipher instance at the end of your encryption
cipherOutputStream.close();
byte[] iv = encryptCipher.getIV();
and you should initialize your Cipher
in DECRYPT_MODE
with this byte[] :
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
Then, your decryption should be OK. Hope this helps.
The IV that your using for decryption is incorrect. Replace this code
//Decrypt cipher
Cipher decryptCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec ivParameterSpec = new IvParameterSpec(aesKey.getEncoded());
decryptCipher.init(Cipher.DECRYPT_MODE, aesKey, ivParameterSpec);
With this code
//Decrypt cipher
Cipher decryptCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec ivParameterSpec = new IvParameterSpec(encryptCipher.getIV());
decryptCipher.init(Cipher.DECRYPT_MODE, aesKey, ivParameterSpec);
And that should solve your problem.
Below includes an example of a simple AES class in Java. I do not recommend using this class in production environments, as it may not account for all of the specific needs of your application.
import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
public class AES
{
public static byte[] encrypt(final byte[] keyBytes, final byte[] ivBytes, final byte[] messageBytes) throws InvalidKeyException, InvalidAlgorithmParameterException
{
return AES.transform(Cipher.ENCRYPT_MODE, keyBytes, ivBytes, messageBytes);
}
public static byte[] decrypt(final byte[] keyBytes, final byte[] ivBytes, final byte[] messageBytes) throws InvalidKeyException, InvalidAlgorithmParameterException
{
return AES.transform(Cipher.DECRYPT_MODE, keyBytes, ivBytes, messageBytes);
}
private static byte[] transform(final int mode, final byte[] keyBytes, final byte[] ivBytes, final byte[] messageBytes) throws InvalidKeyException, InvalidAlgorithmParameterException
{
final SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
final IvParameterSpec ivSpec = new IvParameterSpec(ivBytes);
byte[] transformedBytes = null;
try
{
final Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");
cipher.init(mode, keySpec, ivSpec);
transformedBytes = cipher.doFinal(messageBytes);
}
catch (NoSuchAlgorithmException | NoSuchPaddingException | IllegalBlockSizeException | BadPaddingException e)
{
e.printStackTrace();
}
return transformedBytes;
}
public static void main(final String[] args) throws InvalidKeyException, InvalidAlgorithmParameterException
{
//Retrieved from a protected local file.
//Do not hard-code and do not version control.
final String base64Key = "ABEiM0RVZneImaq7zN3u/w==";
//Retrieved from a protected database.
//Do not hard-code and do not version control.
final String shadowEntry = "AAECAwQFBgcICQoLDA0ODw==:ZtrkahwcMzTu7e/WuJ3AZmF09DE=";
//Extract the iv and the ciphertext from the shadow entry.
final String[] shadowData = shadowEntry.split(":");
final String base64Iv = shadowData[0];
final String base64Ciphertext = shadowData[1];
//Convert to raw bytes.
final byte[] keyBytes = Base64.getDecoder().decode(base64Key);
final byte[] ivBytes = Base64.getDecoder().decode(base64Iv);
final byte[] encryptedBytes = Base64.getDecoder().decode(base64Ciphertext);
//Decrypt data and do something with it.
final byte[] decryptedBytes = AES.decrypt(keyBytes, ivBytes, encryptedBytes);
//Use non-blocking SecureRandom implementation for the new IV.
final SecureRandom secureRandom = new SecureRandom();
//Generate a new IV.
secureRandom.nextBytes(ivBytes);
//At this point instead of printing to the screen,
//one should replace the old shadow entry with the new one.
System.out.println("Old Shadow Entry = " + shadowEntry);
System.out.println("Decrytped Shadow Data = " + new String(decryptedBytes, StandardCharsets.UTF_8));
System.out.println("New Shadow Entry = " + Base64.getEncoder().encodeToString(ivBytes) + ":" + Base64.getEncoder().encodeToString(AES.encrypt(keyBytes, ivBytes, decryptedBytes)));
}
}
Note that AES has nothing to do with encoding, which is why I chose to handle it separately and without the need of any third party libraries.
Online Editor Runnable version:-
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
//import org.apache.commons.codec.binary.Base64;
import java.util.Base64;
public class Encryptor {
public static String encrypt(String key, String initVector, String value) {
try {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
IvParameterSpec iv = new IvParameterSpec(initVector.getBytes("UTF-8"));
SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);
byte[] encrypted = cipher.doFinal(value.getBytes());
//System.out.println("encrypted string: "
// + Base64.encodeBase64String(encrypted));
//return Base64.encodeBase64String(encrypted);
String s = new String(Base64.getEncoder().encode(encrypted));
return s;
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
public static String decrypt(String key, String initVector, String encrypted) {
try {
IvParameterSpec iv = new IvParameterSpec(initVector.getBytes("UTF-8"));
SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);
byte[] original = cipher.doFinal(Base64.getDecoder().decode(encrypted));
return new String(original);
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
public static void main(String[] args) {
String key = "Bar12345Bar12345"; // 128 bit key
String initVector = "RandomInitVector"; // 16 bytes IV
System.out.println(encrypt(key, initVector, "Hello World"));
System.out.println(decrypt(key, initVector, encrypt(key, initVector, "Hello World")));
}
}
This is an improvement over the accepted answer.
Changes:
(1) Using random IV and prepend it to the encrypted text
(2) Using SHA-256 to generate a key from a passphrase
(3) No dependency on Apache Commons
public static void main(String[] args) throws GeneralSecurityException {
String plaintext = "Hello world";
String passphrase = "My passphrase";
String encrypted = encrypt(passphrase, plaintext);
String decrypted = decrypt(passphrase, encrypted);
System.out.println(encrypted);
System.out.println(decrypted);
}
private static SecretKeySpec getKeySpec(String passphrase) throws NoSuchAlgorithmException {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
return new SecretKeySpec(digest.digest(passphrase.getBytes(UTF_8)), "AES");
}
private static Cipher getCipher() throws NoSuchPaddingException, NoSuchAlgorithmException {
return Cipher.getInstance("AES/CBC/PKCS5PADDING");
}
public static String encrypt(String passphrase, String value) throws GeneralSecurityException {
byte[] initVector = new byte[16];
SecureRandom.getInstanceStrong().nextBytes(initVector);
Cipher cipher = getCipher();
cipher.init(Cipher.ENCRYPT_MODE, getKeySpec(passphrase), new IvParameterSpec(initVector));
byte[] encrypted = cipher.doFinal(value.getBytes());
return DatatypeConverter.printBase64Binary(initVector) +
DatatypeConverter.printBase64Binary(encrypted);
}
public static String decrypt(String passphrase, String encrypted) throws GeneralSecurityException {
byte[] initVector = DatatypeConverter.parseBase64Binary(encrypted.substring(0, 24));
Cipher cipher = getCipher();
cipher.init(Cipher.DECRYPT_MODE, getKeySpec(passphrase), new IvParameterSpec(initVector));
byte[] original = cipher.doFinal(DatatypeConverter.parseBase64Binary(encrypted.substring(24)));
return new String(original);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With