Karan Mer Karan Mer - 6 months ago 49
Java Question

how to implement AES 128bit with CBC (with 32bit IV and key)in java?

I am trying to implement AES 256 bit CBC algorithm in java. I want to make something like this. Click here

Below is the picture for sample run.
Sample Run

I am using below program from multiple SO threads , following is the code I am using to encrypt/decrypt. Updated the code as per @dave_thompson suggestion but still same error for length of IV.

import java.security.AlgorithmParameters;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Scanner;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;

public class EncryptionDecryption {

private static String salt;
private static int iterations = 65536 ;
private static int keySize = 256;
private static byte[] ivBytes = {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,};
private static SecretKey secretKey;

public static void main(String []args) throws Exception {
Scanner in = new Scanner(System.in);
salt = getSalt();
String s = in.nextLine();
char[] message = s.toCharArray();

System.out.println("Message: " + String.valueOf(message));
System.out.println("Encrypted: " + encrypt(message));
System.out.println("Decrypted: " + decrypt(encrypt(message).toCharArray()));
}

public static String encrypt(char[] plaintext) throws Exception {
byte[] saltBytes = salt.getBytes();

SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
PBEKeySpec spec = new PBEKeySpec(plaintext, saltBytes, iterations, keySize);
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[] encryptedTextBytes = cipher.doFinal(String.valueOf(plaintext).getBytes("UTF-8"));

return DatatypeConverter.printBase64Binary(encryptedTextBytes);
}

public static String decrypt(char[] encryptedText) throws Exception {

System.out.println(encryptedText);

byte[] encryptedTextBytes = DatatypeConverter.parseBase64Binary(new String(encryptedText));
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);
}
}


Problem with current code shows me the following error, but if I change IV back to 16bit it works.

Following are the SO threads I am referring to.


Answer

Okay, now we have a specific target: AES-CBC (although your example data is only one block so CBC doesn't really matter) with 128-bit key expressed in hex, (128-bit) IV all zero, and zero padding (omitted if exact block).

static void SO37248569() throws Exception {
    // fixed key; in real use key should be securely provided or generated
    String keyhex = "6c616d70736865657031323334353637";
    // crude way of converting hex to bytes, better ways are possible
    byte[] key  = new BigInteger (keyhex,16).toByteArray();
    if( key.length > 16 ) key = Arrays.copyOfRange (key, 1, key.length); // maybe signed
    if( key.length != 16 ) throw new Exception ("key length wrong!");
    // all-zero IV, only secure if key is unique every time
    byte[] IV = new byte[16];
    //
    // fixed plaintext for example, in real use obtain as needed
    byte[] plainbytes = "Hello world".getBytes();
    // note: for ASCII-only data the Java default encoding is okay;
    // if real data can or could contain other chars, specify a 
    // suitable encoding; "UTF-8" is good for most text-y data 
    //
    // ENCRYPT: we need to add zero padding ourself since JCE doesn't do that
    // Java makes this easy because arrays are initialized to all-zeros
    if( plainbytes.length %16 !=0 ) 
        plainbytes = Arrays.copyOf (plainbytes, (plainbytes.length /16 +1)*16);
    //
    Cipher aes = Cipher.getInstance ("AES/CBC/NoPadding");
    aes.init (Cipher.ENCRYPT_MODE, new SecretKeySpec (key, "AES"), new IvParameterSpec (IV));
    byte[] cipherbytes = aes.doFinal (plainbytes);
    // crude way of converting bytes to hex, again better possible
    System.out.println ("encrypt hex->" + new BigInteger (1,cipherbytes).toString(16));
    // alternatively just write to a file and let other tools handle
    //
    // DECRYPT: assuming bytes read from file, or already converted from hex
    //same as above: Cipher aes = Cipher.getInstance ("AES/CBC/NoPadding");
    aes.init (Cipher.DECRYPT_MODE, new SecretKeySpec (key, "AES"), new IvParameterSpec (IV));
    byte[] resultbytes = aes.doFinal (cipherbytes);
    //
    // now we need to remove the zero padding, which is ambiguous 
    // this will damage data that actually has trailing zero bytes
    int i; for( i = resultbytes.length; --i>=0 && resultbytes[i]==0; ){}
    resultbytes = Arrays.copyOf (resultbytes, i+1);
    //
    // for example just display, for real use adapt as desired
    System.out.println ("decrypt chars->" + new String (resultbytes));
    // see above about encoding
}

which produces the result

encrypt hex->e6b426aca323815fd6583cbcc4293c8d
decrypt chars->Hello world