Python to Kotlin AES communication (javax.crypto.BadPaddingException)

Viewed 275

I am trying to encrypt a UTF-8 string on Python with AES and decrypt it in Kotlin, here is the Python encrypt part:

class aes():

    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 = self.key[:self.bs]  # Random.new().read(self.bs)
        cipher = AES.new(self.key, AES.MODE_CBC, iv)
        return base64.b64encode(iv + cipher.encrypt(raw.encode()))

    def pad(self, s):
        return s + (self.bs - len(s) % self.bs) * chr(self.bs - len(s) % self.bs)

...

client_key = self.rsa.decrypt(int(connection.recv(bufsize).decode()))

enc = aes(client_key)
em = enc.encrypt("AES_OK")

connection.send(em+b'\0')

And the Kotlin part decrypting it:

object AES256 {
    private val decorder = Base64.getDecoder()
    private fun cipher(opmode:Int, secretKey:String):Cipher {

        val c = Cipher.getInstance("AES/CBC/PKCS7Padding")

        val sk = SecretKeySpec(secretKey.toByteArray(Charsets.UTF_8), "AES")
        val iv = IvParameterSpec(secretKey.substring(0, 16).toByteArray(Charsets.UTF_8))
        c.init(opmode, sk, iv)
        return c
    }
    @RequiresApi(Build.VERSION_CODES.O)
    fun decrypt(str:String, secretKey:String):String {
        val byteStr = decorder.decode(str.toByteArray(Charsets.UTF_8))
        // Error here
        return String(cipher(Cipher.DECRYPT_MODE, secretKey).doFinal(byteStr))
    }
}

fun readBytes(input: BufferedReader) : String {
    var byte: Int = input.read()
    var r = ""
    while(byte != 0) {
        r += byte.toChar()
        byte = input.read()
    }
    return r
}

resposta = readBytes(input)
resposta = AES256.decrypt(resposta, atc_aesk.toString(Charsets.UTF_8))

And I get the following exception:

javax.crypto.BadPaddingException: error:1e000065:Cipher functions:OPENSSL_internal:BAD_DECRYPT
  1. All the RSA and connection code works properly, the ciphertext and key are the same on both sides.
  2. I'm using a 32 bytes key, I also tried a 16 bytes one, same error.

I would greatly appreciate any help or input, thanks.

2 Answers

The user President James K. Polk answered this in a comment, the mistake was that I hashed the key only in the Python code and not in Kotlin... self.key = hashlib.sha256(key.encode()).digest()

I tried finding the problem like a whole week, I really feel so dumb, thank you President James K. Polk.

I had exactly the same use case and seemed to have copied the same code from some places online. Still took me a second. Here is a working pair:

class AESCipher(object):

    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())).decode('utf-8')

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

class AESCipher(private val key: String) {

    private fun createCipher(mode: Int, ivBytes: ByteArray): Cipher {
        val c = Cipher.getInstance("AES/CBC/PKCS7Padding")
        val sk = SecretKeySpec(key.getSha256(), "AES")
        val iv = IvParameterSpec(ivBytes)
        c.init(Cipher.DECRYPT_MODE, sk, iv)
        return c
    }

    fun decrypt(data: String): ByteArray {
        val bytes = Base64.decode(data, Base64.DEFAULT)
        val ivBytes = bytes.take(16).toByteArray()
        val rawDataBytes = bytes.drop(16).toByteArray()
        val cipher = createCipher(Cipher.DECRYPT_MODE, ivBytes)
        return cipher.doFinal(rawDataBytes)
    }

    private fun String.getSha256(): ByteArray {
        val digest = MessageDigest.getInstance("SHA-256").also { it.reset() }
        return digest.digest(this.toByteArray())
    }
}

If you pass the same key string to them, they will work together. Notice that the data exposed and consumed is already a Base64 string.

Related