Vineeth Mohan
Vineeth Mohan

Reputation: 2864

Issue with key and iv on AES 256-CBC

I get a encrypted base64 string from Python.

The format is AES 256 CBC, but when I try to decrypt using Android it return decrypted string as nil.

Python

# coding=utf-8
import base64
from random import choice
from string import letters

try:
    from Crypto import Random
    from Crypto.Cipher import AES
except ImportError:
    import crypto
    import sys

    sys.modules['Crypto'] = crypto
    from crypto.Cipher import AES
    from crypto import Random


class AESCipher(object):
    def __init__(self, key):
        self.bs = 32
        self.key = key

    def encrypt(self, raw):
        _raw = raw
        raw = self._pad(raw)

        print raw, ';'
        print _raw, ';'

        iv = "".join([choice(letters[:26]) for i in xrange(16)])
        print " iv :", iv
        cipher = AES.new(self.key, AES.MODE_CBC, iv)
        return base64.b64encode(iv + cipher.encrypt(raw))

    def decrypt(self, enc):
        enc = base64.b64decode(enc)
        iv = enc[:AES.block_size]
        cipher = AES.new(self.key, AES.MODE_CBC, iv)
        return self._unpad(cipher.decrypt(enc[AES.block_size:])).decode('utf-8')

    def _pad(self, s):
        a = (self.bs - len(s) % self.bs)
        b = chr(self.bs - len(s) % self.bs)
        return s + a * b

    @staticmethod
    def _unpad(s):
        return s[:-ord(s[len(s) - 1:])]
def encrypt(k, t):
    o = AESCipher(k)
    return o.encrypt(t)


def decrypt(k, t):
    o = AESCipher(k)
    return o.decrypt(t)


def main():
    k = "qwertyuiopasdfghjklzxcvbnmqwerty"
    s1 = "Hello World!"

    d2 = encrypt(k, s1)

    print " Password :", k
    print "Encrypted :", d2
    print "    Plain :", decrypt(k, d2)

if __name__ == '__main__':
    main()

Java

Here I use https://github.com/fukata/AES-256-CBC-Example

final String aEcodedSting = "aWVnZWphbnBleWJlemdteeAal+cw04QPYRuuIC3J1/zbkZZSCqxGLo/a26ZiieOk";
String decrypted = AESUtil.decrypt(aEcodedSting);

When I try to decrypt I got this

java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.vinu.aessamble/com.example.vinu.aessamble.MainActivity}: 
java.lang.RuntimeException: javax.crypto.BadPaddingException: error:1e06b065:Cipher functions:EVP_DecryptFinal_ex:BAD_DECRYPT

This is the Python encryption output:

Password : qwertyuiopasdfghjklzxcvbnmqwerty
Encrypted : aWVnZWphbnBleWJlemdteeAal+cw04QPYRuuIC3J1/zbkZZSCqxGLo/a26ZiieOk
iv : iegejanpeybezgmy
plainText : ser456&*(

Please notify me when anyone can solve this using another library.

Upvotes: 1

Views: 3764

Answers (1)

Christian Ascone
Christian Ascone

Reputation: 1177

There are 4 problems:

  1. Difference between python output and java input
  2. Different IV and key
  3. Different key creation
  4. Padding

1) Currently your python code output is a base64 encoding of iv + encrypted_data

return base64.b64encode(iv + cipher.encrypt(raw))

But in java you're directly decrypting raw data.

You should fix this way

// Decode base64
byte[] array = Base64.decode(src);
// Get only encrypted data (removing first 16 byte, namely the IV)
byte[] encrypted = Arrays.copyOfRange(array, 16, array.length);
// Decrypt data
decrypted = new String(cipher.doFinal(encrypted));

2) You must use same IV and key for input and output, so you should copy them from python console output:

iv : qbmocwtttkttpqvv
Password : qwertyuiopasdfghjklzxcvbnmqwerty
Encrypted : anZxZHVpaWJpb2FhaWdqaCK0Un7H9J4UlXRizOJ7s8lchAWAPdH4GRf5tLAkCmm6
    Plain : Hello World!

and paste in java code:

private static final String ENCRYPTION_KEY = "qwertyuiopasdfghjklzxcvbnmqwerty";
private static final String ENCRYPTION_IV = "qbmocwtttkttpqvv";

3) In python you're using the key as string, but in java library it is hashed before being used for decrypting, so you should change your makeKey() method:

static Key makeKey() {
    try {
        byte[] key = ENCRYPTION_KEY.getBytes("UTF-8");
        return new SecretKeySpec(key, "AES");
    } catch (UnsupportedEncodingException e) {
        e.printStackTrace();
    }

    return null;
}

4) Finally, you don't need to specify a padding in java with "AES/CBC/PKCS5Padding", because this way you force Cipher to pad automatically.

You can simply use "AES/CBC/NoPadding" in your decrypt() method, so it should look like this:

public static String decrypt(String src) {
    String decrypted = "";
    try {
        Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
        cipher.init(Cipher.DECRYPT_MODE, makeKey(), makeIv());
        byte[] array = Base64.decode(src);
        byte[] encrypted = Arrays.copyOfRange(array, 16, array.length);
        decrypted = new String(cipher.doFinal(encrypted));
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
    return decrypted;
}

Java output with your base64 and IV:

encrypted: aWVnZWphbnBleWJlemdteeAal+cw04QPYRuuIC3J1/zbkZZSCqxGLo/a26ZiieOk
decrypted: ser456&*(

Edit:

As suggested by Artjom B. (thank you), it would be better to read IV directly from ciphertext instead of hardcoding in AESUtil.

Your input consists of the IV in first 16 bytes and encrypted text in last 16 bytes, so you could take advantage of this.

public static String decrypt(String src) {
    String decrypted = "";
    try {
        Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
        // Decode input
        byte[] array = Base64.decode(src);
        // Read first 16 bytes (IV data)
        byte[] ivData = Arrays.copyOfRange(array, 0, 16);
        // Read last 16 bytes (encrypted text)
        byte[] encrypted = Arrays.copyOfRange(array, 16, array.length);
        // Init the cipher with decrypt mode, key, and IV bytes array (no more hardcoded)
        cipher.init(Cipher.DECRYPT_MODE, makeKey(), new IvParameterSpec(ivData));
        // Decrypt same old way
        decrypted = new String(cipher.doFinal(encrypted));
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
    return decrypted;
}

Moreover, as said here

Python code uses a 32 byte block size for padding which means that Java will still not be able to decrypt half of all possible ciphertexts. AES block size is 16 bytes and this should be changed in the Python implementation

You could change your Python class as below (AES.block_size is equal to 16):

class AESCipher(object):
    def __init__(self, key):
        self.bs = AES.block_size
        self.key = key

Upvotes: 4

Related Questions