Search code examples
pythonandroidcryptographypython-cryptography

How to decrypt AES Bounty castle encoded zip file in python?


My teammate wrote kotlin code to encrypt zip files from android side using AES BC and I'm trying to decrypt it. But it's not happening. I need to write client side python code to decrypt those files

This is the kotlin code

import android.util.Base64
import android.util.Log
import java.io.BufferedInputStream
import java.io.BufferedOutputStream
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import java.security.SecureRandom
import javax.crypto.Cipher
import javax.crypto.KeyGenerator
import javax.crypto.SecretKey
import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec
object AESEncryption {
    fun generateSecretKey(): SecretKey {
        val secureRandom = SecureRandom()
        val keyGenerator = KeyGenerator.getInstance("AES")
        //generate a key with secure random
        keyGenerator?.init(256, secureRandom)
        val key = keyGenerator.generateKey()
        val secretKeyBase64 = Base64.encodeToString(key.encoded, Base64.NO_WRAP)
        Log.e("AES KEY", "Generated AES key: $secretKeyBase64")
        return key
    }

fun readFile(filePath: String): ByteArray {
    val file = File(filePath)
    val fileContents = file.readBytes()
    val inputBuffer = BufferedInputStream(
        FileInputStream(file)
    )

    inputBuffer.read(fileContents)
    inputBuffer.close()

    return fileContents
}

fun saveFile(fileData: ByteArray, path: String) {
    val file = File(path)
    val bos = BufferedOutputStream(FileOutputStream(file, false))
    bos.write(fileData)
    bos.flush()
    bos.close()
}

fun encrypt(yourKey: SecretKey, fileData: ByteArray): ByteArray {
    val data = yourKey.getEncoded()
    val skeySpec = SecretKeySpec(data, 0, data.size, "AES")
    val cipher = Cipher.getInstance("AES", "BC")
    cipher.init(Cipher.ENCRYPT_MODE, skeySpec, IvParameterSpec(ByteArray(cipher.getBlockSize())))
    return cipher.doFinal(fileData)
}

fun encryptDownloadedFile(filePath: String, secretKey: SecretKey) {
    try {
        val fileData = readFile(filePath)

        //get secret key
        //encrypt file
        val encodedData = encrypt(secretKey, fileData)

        saveFile(encodedData, "$filePath")

    } catch (e: Exception) {
        e.message?.let { Log.d("AEC", it) }
    }
}

fun getSecretKey(): SecretKey{
    val secretKeyEncoded = "key is here"
    val decodedKey = Base64.decode(secretKeyEncoded, Base64.NO_WRAP)
    val secretKey = SecretKeySpec(decodedKey, 0, decodedKey.size, "AES")
    return secretKey
}

fun decrypt(yourKey: SecretKey, fileData: ByteArray): ByteArray {
    val decrypted: ByteArray
    val cipher = Cipher.getInstance("AES", "BC")
    cipher.init(Cipher.DECRYPT_MODE, yourKey, IvParameterSpec(ByteArray(cipher.blockSize)))
    decrypted = cipher.doFinal(fileData)
    return decrypted
}

fun decryptEncryptedFile(filePath: String) {
    val fileData = readFile(filePath)
    val secretKey = getSecretKey()
    val decodedData = decrypt(secretKey, fileData)
    saveFile(decodedData, "$filePath")
}
}

and this is my python code

from Crypto.Cipher import AES
import base64

def get_secret_key():
    # This is the same key used in the Android code
    secret_key_encoded = "7same key"
    decoded_key = base64.b64decode(secret_key_encoded)
    return decoded_key

def decrypt_file(input_file_path, output_file_path=None):
    try:
        # If no output path specified, create one by removing .encrypted extension if present
        if output_file_path is None:
            output_file_path = input_file_path.replace('.encrypted', '')
            if output_file_path == input_file_path:
                output_file_path = input_file_path.replace('.zip', '_decrypted.zip')

        # Read the encrypted file
        with open(input_file_path, 'rb') as file:
            encrypted_data = file.read()

        # Get the secret key
        key = get_secret_key()

        # Create cipher object and decrypt the data
        cipher = AES.new(key, AES.MODE_CBC, iv=bytes(16))  # 16 bytes of zeros as IV
        decrypted_data = cipher.decrypt(encrypted_data)

        padding_length = decrypted_data[-1]
        decrypted_data = decrypted_data[:-padding_length]

        # Write the decrypted data to output file
        with open(output_file_path, 'wb') as file:
            file.write(decrypted_data)

        print(f"File decrypted successfully: {output_file_path}")
        return True

    except Exception as e:
        print(f"Error during decryption: {str(e)}")
        return False

# Example usage
if __name__ == "__main__":
    # Example usage
    encrypted_file_path = "com.android.settings_1736318346774.zip"
    decrypt_file(encrypted_file_path)

with this code the zip is generated but not able to unzip it


Solution

  • In the Kotlin code, the specification of mode and padding is missing:

    val cipher = Cipher.getInstance("AES", "BC")
    

    For this reason, provider-dependent default values are used. On my machine (Android 15 / API level 35), e.g. the (insecure) ECB mode and PKCS#7 padding are applied.

    Since you use the CBC mode in the Python code, the codes are incompatible and decryption fails. As a fix, explicitly specify the mode and padding in the Kotlin code:

    val cipher = Cipher.getInstance("AES/CBC/PKCS7Padding", "BC").
    

    In principle, you could also use the ECB mode on the Python side to make both codes compatible. However, this is not advisable, as the ECB mode is insecure!


    In addition, there is no need for manual unpadding in the Python code. PyCryptodome supports various paddings including PKCS#7, see Crypto.Util.Padding.

    Also note that the use of a static IV is a vulnerability. Instead, a random IV should be generated for each encryption. The IV, which is not a secret, is passed to the decrypting side together with the ciphertext, usually concatenated.