How to decrypt an AES/CBC encrypted string in Kotlin?

Viewed 1924

I have this Python method on the server to encrypt a string into bytes (AES/CBC).

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

    def encrypt(self, raw):
        raw = self._pad(raw)
        iv = Random.new().read(AES.block_size)
        cipher = AES.new(self.key, AES.MODE_CBC, iv)
        return base64.b64encode(iv + cipher.encrypt(raw.encode()))

    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):
        return s + (self.bs - len(s) % self.bs) * chr(self.bs - len(s) % self.bs)

The output of encrypt() is in bytes like this: b'PMgMOkBkciIKfWy/DfntVMyAcKtVsM8LwEwnTYE5IXY='

I would like to store this into database, and send it as string via API to Kotlin. And in there I would like to decrypt it via the same shared secret key.

  1. In what format do I save the bytes above into database?
  2. Once arrived in Kotlin client, how do I convert that string into ByteArray?

My theory is that I have to store the bytes as base64 string in the database. And on the other side I have to decode the string as base64 into bytes. Is this approach correct? Will the encryption/decryption work like this end-to-end with the code below?

    fun decrypt(context:Context, dataToDecrypt: ByteArray): ByteArray {
            val cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING")
            val ivSpec = IvParameterSpec(getSavedInitializationVector(context))
            cipher.init(Cipher.DECRYPT_MODE, getSavedSecretKey(context), ivSpec)
            val cipherText = cipher.doFinal(dataToDecrypt)
            val sb = StringBuilder()
            for (b in cipherText) {
                sb.append(b.toChar())
            }   
            return cipherText
    }
    
    fun getSavedSecretKey(context: Context): SecretKey {
            val sharedPref = PreferenceManager.getDefaultSharedPreferences(context)
            val strSecretKey = sharedPref.getString("secret_key", "")
            val bytes = android.util.Base64.decode(strSecretKey, android.util.Base64.DEFAULT)
            val ois = ObjectInputStream(ByteArrayInputStream(bytes))
            val secretKey = ois.readObject() as SecretKey
            return secretKey
    }
    
    fun getSavedInitializationVector(context: Context) : ByteArray {
            val sharedPref = PreferenceManager.getDefaultSharedPreferences(context)
            val strInitializationVector = sharedPref.getString("initialization_vector", "")
            val bytes = android.util.Base64.decode(strInitializationVector, android.util.Base64.DEFAULT)
            val ois = ObjectInputStream(ByteArrayInputStream(bytes))
            val initializationVector = ois.readObject() as ByteArray
            return initializationVector
        }

UPDATE

I have tried to remove the Base64 to remove the memory overhead as suggested.

Python:

    def encrypt(self, raw):
        raw = self._pad(raw)
        iv = Random.new().read(AES.block_size)
        cipher = AES.new(self.key, AES.MODE_CBC, iv)
        return iv + cipher.encrypt(raw.encode())

So this is no longer possible.

enc = AESCipher('abc').encrypt("myLife")
value_to_save_in_db = enc.decode("utf8")

So I need to find a way to store the byte array directly in the database. I think I should be able to do this as blob. But some challenges remain as how to send the bytearray as part of JSON over the API to the android device. I think I have to convert it to Base64 string again. Not sure if I have gained anything in that case...

1 Answers

The following Kotlin code:

val decrypted = decrypt("blEOKMQtUbNOzJbvEkL2gNhjF+qQ/ZK84f2ADu8xyUFme6uBhNYqvEherF/RRO9YRImz5Y04/ll+T07kqv+ExQ==");
println(decrypted);

decrypts a ciphertext of the Python code. Here decrypt() is:

fun decrypt(dataToDecryptB64 : String) : String {

    // Base64 decode Python data
    val dataToDecrypt = Base64.getDecoder().decode(dataToDecryptB64)

    // Separate IV and Ciphertext
    val ivBytes = ByteArray(16)
    val cipherBytes = ByteArray(dataToDecrypt.size - ivBytes.size)
    System.arraycopy(dataToDecrypt, 0, ivBytes, 0, ivBytes.size)
    System.arraycopy(dataToDecrypt, ivBytes.size, cipherBytes, 0, cipherBytes.size)

    // Derive key
    val keyBytes = MessageDigest.getInstance("SHA256").digest("abc".toByteArray(Charsets.UTF_8))

    // Decrypt
    val cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING")
    cipher.init(Cipher.DECRYPT_MODE, SecretKeySpec(keyBytes, "AES"), IvParameterSpec(ivBytes))
    val cipherText = cipher.doFinal(cipherBytes)

    return String(cipherText, Charsets.ISO_8859_1)
}

For this, the ciphertext was generated using the posted Python class AESCipher as follows:

plaintext = 'The quick brown fox jumps over the lazy dog'
cipher = AESCipher('abc')
ciphertext = cipher.encrypt(plaintext)
print(ciphertext.decode('utf8')) # Base64 string, which can be stored e.g. in a DB

I applied the originally posted Python implementation that derives the key using SHA256. However, if the key is derived from a password, for security reasons not SHA256 but a reliable key derivation function, e.g. Argon2 or PBKDF2, should be used.

The Kotlin code first Base64 decodes the Python data and then separates IV and the actual ciphertext. Then, the key is derived by generating the SHA256 hash of the password. Finally the data is decrypted.


The current Python code Base64 encodes the data so that it can be stored as a string in the DB. Alternatively, the Python code could be modified so that no Base64 encoding is performed, and the raw data can be stored (which requires less memory, Base64 overhead: 33%).
Depending on the solution chosen, the Kotlin code may or may not need to Base64 decode the data.

Related