-1

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.

Community
  • 1
  • 1
karan
  • 8,637
  • 3
  • 41
  • 78
  • Please don't confuse bits and bytes. A 16 bit IV would be basically useless if there were such a thing for CBC-mode. – Artjom B. May 16 '16 at 07:51
  • OMG that's 16 bytes set to 0, or sixteen *pairs* of hexadecimal characters. Sheesh. – Maarten Bodewes May 17 '16 at 17:58
  • (tweaked) That website is entering the key and IV in hex, so your example has 128 bits key not 256, and 128 bits (of zero) as IV not 256. And it's using the key directly, not doing PBKDF2 or any other key derivation, and it's doing zero padding not PKCS#5 padding. In short, if you want to do what that website is doing almost everything in your code is wrong, and if you want to do something close to what's in your code that website is completely wrong. **PICK ONE.** But note zero padding is uncommon and usually a bad idea. – dave_thompson_085 May 18 '16 at 01:41
  • @dave_thompson_085 thank you so much for making me aware. I need to make it work like that website does. so how can I do that? any push to a particular idea will be helpful. – karan May 18 '16 at 05:31

3 Answers3

2

The IV size for CBC mode encryption is the same as the block size. AES is a subset of Rijndael with certain restrictions. One of these restrictions is a block size that's always 128 bits.

Rijndael is not standardized when used outside AES parameters. This means it is often not implemented. Oracle's Java does not implement Rijndael outside AES boundaries. Bouncy Castle does, but it doesn't expose higher block sizes to the outside.

So the only thing you can do is to use Rijndael from the - so called - lightweight API of the Bouncy Castle provider. Basically you'd be calling the underlying implementation of the Bouncy Castle provider directly through Bouncy's proprietary interface.

Warning: below code uses a static (zeroed) key and IV. It's just used to demonstrate the Rijndael cipher with a block size of 256 bits. It does not follow (cryptographic) best practice.

import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.Security;

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 org.bouncycastle.crypto.BufferedBlockCipher;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.crypto.engines.RijndaelEngine;
import org.bouncycastle.crypto.modes.CBCBlockCipher;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.crypto.params.ParametersWithIV;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.util.encoders.Hex;

public class RijndaelTestJava {

    private static final boolean FOR_ENCRYPTION = true;

    public static void main(String[] args) throws Exception {
        rijndael256BouncyLW();
        rijndael256BouncyProvider();
    }

    private static void rijndael256BouncyLW() throws InvalidCipherTextException {
        {
            RijndaelEngine rijndael256 = new RijndaelEngine(256);
            BufferedBlockCipher rijndael256CBC =
                    new BufferedBlockCipher(
                            new CBCBlockCipher(rijndael256));
            KeyParameter key = new KeyParameter(new byte[256 / Byte.SIZE]);
            rijndael256CBC.init(FOR_ENCRYPTION, new ParametersWithIV(key,
                    new byte[256 / Byte.SIZE]));
            byte[] in = new byte[64]; // two blocks
            byte[] out = new byte[64]; // two blocks
            int off = rijndael256CBC.processBytes(in, 0, in.length, out, 0);
            off += rijndael256CBC.doFinal(out, off);
            System.out.println(Hex.toHexString(out));
        }
    }

    private static void rijndael256BouncyProvider() throws NoSuchAlgorithmException,
            NoSuchPaddingException, InvalidKeyException,
            InvalidAlgorithmParameterException, IllegalBlockSizeException,
            BadPaddingException {
        {
            Security.addProvider(new BouncyCastleProvider());
            Cipher cipher = Cipher.getInstance("Rijndael/CBC/PKCS7Padding");
            SecretKeySpec key = new SecretKeySpec(new byte[256 / Byte.SIZE],
                    "Rijndael");
            IvParameterSpec iv = new IvParameterSpec(new byte[256 / Byte.SIZE]);
            // throws an InvalidAlgorithmParameterException: IV must be 16 bytes long.
            cipher.init(Cipher.ENCRYPT_MODE, key, iv);
            byte[] out = cipher.doFinal("owlsteead"
                    .getBytes(StandardCharsets.US_ASCII));
            System.out.println(Hex.toHexString(out));
        }
    }
}
Maarten Bodewes
  • 90,524
  • 13
  • 150
  • 263
  • Sorry, too lazy for exception handling. Make sure you distinguish between programmatic exceptions and input related exceptions. – Maarten Bodewes May 16 '16 at 18:13
  • (fixed) This is a great answer for Rijndael, except that you left the key and IV at constant-all-zero which is not very secure, but i doubt that's the question here; anyone who knows non-AES-profiled Rijndael is even possible would know to specify 'Rijndael' and not 'AES'. – dave_thompson_085 May 16 '16 at 20:40
  • @dave_thompson_085 There are a few API's that use Rijndael classes for AES functionality. It's an easy mistake to use a greater block size together with greater key size for those classes (e.g. `RijndaelManaged` in .NET and of course mcrypt in PHP). mcrypt even had a 256 bit block size in their original API example code (but then again, there are few areas where mcrypt isn't severely lacking). – Maarten Bodewes May 17 '16 at 06:54
1

The IV should be the same size as the block size of AES which is 128 bits (16 bytes).

The IV you define in your encrypt you actually don't pass to the cipher .. and thus the cipher generates one for you (that is 16 bytes).

Ebbe M. Pedersen
  • 7,250
  • 3
  • 27
  • 47
  • Thanks for reply @Ebbe , how can I make my algorithm to work with 32byte IV. how to make it work with 256 byte. any ideas? – karan May 16 '16 at 07:32
  • AES always use IV of 16 bytes .. this is independent of the key size – Ebbe M. Pedersen May 16 '16 at 07:36
  • as you can see in my question client is asking to use 32 bit iv. how can i change according to this behavior can you suggest some alterations. it would be good if you can provide some guidance over this. – karan May 16 '16 at 07:42
  • some links will be useful. – karan May 16 '16 at 07:43
  • @KaranMer 32 **bits** IV for AES is impossible and so is 32 **bytes**. Plus, using bytes with value 48, as you are doing, would be weird though legal. It is common in crypto (and other areas of computing also) to write binary values in hexadecimal, and **32 hex digits of zero** (sometimes called nybbles) is 16 bytes or 128 bits which is exactly right for AES; I would bet that's what your client wants and you just misunderstood. Since IV is an array of `byte` and Java defaults primitive arrays to zero, you could just use `new byte[16]` for this. – dave_thompson_085 May 16 '16 at 20:41
  • @dave_thompson_085 can you suggest me how to do this. – karan May 17 '16 at 06:12
  • @dave_thompson_085 can you check out the updated code – karan May 17 '16 at 06:23
1

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
dave_thompson_085
  • 34,712
  • 6
  • 50
  • 70