Marcus
Marcus

Reputation: 818

AES Java Multithreading exceptions

I noticed that an application I was running was throwing exceptions upon simultaneous decryption. I wrote the following to test this:

public void run() {
    for(int i=0; i<100000; i++){
        String encrypted = Crypt.encrypt(
                "Lorem ipsum dolor sit amet.",
                "password"
        );

        String decrypted = Crypt.decrypt(encrypted, "password")[0];
        System.out.println(decrypted);
    }
}


public static void main(String[] args) {

    Thread t1 = new Thread(new Main());
    Thread t2 = new Thread(new Main());

    t1.start();
    t2.start();

}

The Crypt methods are as follows:

public static String encrypt(String input, String key){

    try {

        byte[] ivBytes = new byte[16];
        SecureRandom.getInstance("SHA1PRNG").nextBytes(ivBytes);

        IvParameterSpec ips = new IvParameterSpec(ivBytes);
        byte[] keybytes = md5(key);//This isn't final. Don't worry ;)
        byte[] crypted = null;
        SecretKeySpec skey = new SecretKeySpec(keybytes, "AES");
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(Cipher.ENCRYPT_MODE, skey, ips);
        byte[] ptext = input.getBytes("UTF-8");
        crypted = cipher.doFinal(ptext);

        return Base64.encodeBase64String(ivBytes)+Base64.encodeBase64String(crypted);
    }catch(Exception e){
        e.printStackTrace();
    }
    return null;
}

public static String[] decrypt(String input, String key){

    String iv = input.substring(0, 24);
    String encrypted = input.substring(24);
    try {
        IvParameterSpec ips = new IvParameterSpec(Base64.decodeBase64(iv));
        byte[] keybytes = md5(key);//This isn't final. Don't worry ;)
        byte[] output = null;
        SecretKeySpec skey = new SecretKeySpec(keybytes, "AES");
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(Cipher.DECRYPT_MODE, skey, ips);
        output = cipher.doFinal(Base64.decodeBase64(encrypted));
        if(output==null){
            throw new Exception();
        }

        return new String[]{new String(output),iv};
    }catch(Exception e){
        e.printStackTrace();
    }
}

Sure enough the first attempt on both threads fail:

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 common.Crypt.decrypt(Crypt.java:122)
at bootstrap.Main.run(Main.java:427)
at java.lang.Thread.run(Thread.java:680)

There are successes for approximately 20 attempts on each thread (presumably where the unsafe calls don't intersect) and then exceptions are thrown. This pattern seems to continue.

I'm running this on OS X 10.7.2 if that helps.

If I understand correctly this could simply be a vendor issue as it's using the Sun JDK which I could easily swap out, but I thought it would be best to get some more experienced opinions on this and put it up in case someone having the same issue stumbles across it.

If someone could point me in the direction of a thread safe encryption+decryption scheme which achieves the same results I'd be really grateful.

Thanks, Marcus

Upvotes: 1

Views: 2377

Answers (2)

Marcus
Marcus

Reputation: 818

The issue was caused by this line which was being used to generate a key from the password.

byte[] keybytes = md5(key);

The md5 function which used the MessageDigest class was prone to returning garbage which was then being fed into the decryption which proceeded to fail.

Upvotes: 3

SparX
SparX

Reputation: 369

public static String[] decrypt(String input, String key){

    String iv = input.substring(0, 24);
    String encrypted = input.substring(24);
    try {
        IvParameterSpec ips = new IvParameterSpec(Base64.decodeBase64(iv));
        byte[] keybytes = md5(key);//This isn't final. Don't worry ;)
        byte[] output = null;
        SecretKeySpec skey = new SecretKeySpec(keybytes, "AES");
        Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding"); //Change here
        cipher.init(Cipher.DECRYPT_MODE, skey, ips);
        output = cipher.doFinal(Base64.decodeBase64(encrypted));
        if(output==null){
            throw new Exception();
        }

        return new String[]{new String(output),iv};
    }catch(Exception e){
        e.printStackTrace();
    }
}

Upvotes: 1

Related Questions