首页 > 解决方案 > 如何在 Kotlin 中解密 AES/CBC 加密字符串?

问题描述

我在服务器上有这个 Python 方法将字符串加密为字节(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)

encrypt() 的输出是bytes这样的:b'PMgMOkBkciIKfWy/DfntVMyAcKtVsM8LwEwnTYE5IXY='

我想将其存储到数据库中,并通过 API 将其作为字符串发送到 Kotlin。在那里,我想通过相同的共享密钥对其进行解密。

  1. 我以什么格式将上面的字节保存到数据库中?
  2. 到达 Kotlin 客户端后,如何将该字符串转换为ByteArray?

我的理论是我必须将字节作为base64字符串存储在数据库中。另一方面,我必须将字符串作为 base64 解码为字节。这种方法正确吗?加密/解密是否会像下面的代码那样端到端地工作?

    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
        }

更新

我已尝试按照建议删除 Base64 以消除内存开销。

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())

所以这已经不可能了。

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

所以我需要找到一种方法将字节数组直接存储在数据库中。我想我应该能够做到这一点。但是仍然存在一些挑战,例如如何通过 API 将字节数组作为 JSON 的一部分发送到 android 设备。我想我必须再次将其转换为 Base64 字符串。不知道在那种情况下我是否有所收获......

标签: pythonandroidkotlinencryption

解决方案


以下 Kotlin 代码:

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

解密 Python 代码的密文。这里decrypt()是:

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)
}

为此,密文是使用已发布的 Python 类生成的AESCipher,如下所示:

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

我应用了最初发布的 Python 实现,它使用 SHA256 派生密钥。但是,如果密钥来自密码,出于安全原因,不应使用 SHA256,而是应使用可靠的密钥导出函数,例如 Argon2 或 PBKDF2。

Kotlin 代码首先对 Python 数据进行 Base64 解码,然后将 IV 和实际密文分开。然后,通过生成密码的 SHA256 哈希来导出密钥。最后解密数据。


当前的 Python 代码 Base64 对数据进行编码,以便可以将其作为字符串存储在数据库中。或者,可以修改 Python 代码,以便不执行 Base64 编码,并且可以存储原始数据(这需要更少的内存,Base64开销:33%)。
根据选择的解决方案,Kotlin 代码可能需要也可能不需要 Base64 解码数据。


推荐阅读