javax.crypto.BadPaddingException,即使块大小正确(16 的倍数)

问题描述 投票:0回答:1

我在尝试为首选项数据存储创建加密时遇到了这个问题。当尝试解密消息时,我得到这个

javax.crypto.BadPaddingException

import android.security.keystore.KeyGenParameterSpec
import android.security.keystore.KeyProperties
import android.util.Base64
import android.util.Log
import java.security.KeyStore
import javax.crypto.Cipher
import javax.crypto.KeyGenerator
import javax.crypto.SecretKey
import javax.crypto.spec.IvParameterSpec

class CryptoManager {
    private val keyStore = KeyStore.getInstance("AndroidKeyStore").apply {
        load(null)
    }

    companion object {
        private const val ALGORITHM = KeyProperties.KEY_ALGORITHM_AES
        private const val BLOCK_MODE = KeyProperties.BLOCK_MODE_CBC
        private const val PADDING = KeyProperties.ENCRYPTION_PADDING_PKCS7
        private const val TRANSFORMATION = "$ALGORITHM/$BLOCK_MODE/$PADDING"
    }

    private val encryptCipher get() = Cipher.getInstance(TRANSFORMATION).apply {
        init(Cipher.ENCRYPT_MODE, getKey())
    }

    private fun getDecryptCipherForIv(iv: ByteArray): Cipher {
        return Cipher.getInstance(TRANSFORMATION).apply {
            init(Cipher.DECRYPT_MODE, getKey(), IvParameterSpec(iv))
        }
    }

    private fun getKey(): SecretKey {
        val existingKey = keyStore.getEntry("myAppSecret", null) as? KeyStore.SecretKeyEntry
        return existingKey?.secretKey ?: createKey()
    }

    private fun createKey(): SecretKey {
        return KeyGenerator.getInstance(ALGORITHM).apply {
            init(
                KeyGenParameterSpec.Builder(
                    "myAppSecret",
                    KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
                )
                    .setBlockModes(BLOCK_MODE)
                    .setEncryptionPaddings(PADDING)
                    .setUserAuthenticationRequired(false)
                    .setRandomizedEncryptionRequired(true)
                    .build()
            )
        }.generateKey()
    }

    private fun getSizeBytes(size: Int): ByteArray {
        return byteArrayOf(
                (size shr 24).toByte(),
                (size shr 16).toByte(),
                (size shr 8).toByte(),
                size.toByte()
        )
    }

    private fun getSizeFromBytes(bytes: ByteArray): Int {
        return (bytes[0].toInt() shl 24) or
                ((bytes[1].toInt() and 0xFF) shl 16) or
                ((bytes[2].toInt() and 0xFF) shl 8) or
                (bytes[3].toInt() and 0xFF)
    }

    fun encrypt(bytes: ByteArray): ByteArray {
        val encrypted = encryptCipher.doFinal(bytes)
        val sizeBytes = getSizeBytes(encrypted.size)
        val iv = encryptCipher.iv

        Log.d("myDebug", "encrypt iv: ${iv.map { it.toInt().toByte() }.joinToString(" ")}")

        return Base64.encode(byteArrayOf(encryptCipher.iv.size.toByte()) +
                iv +
                sizeBytes +
                encrypted,
            Base64.DEFAULT)

    }

    fun decrypt(bytes64: ByteArray): ByteArray {

        // ByteArray should look like IV_SIZE | IV | MESSAGE_SIZE_BYTES | MESSAGE
        val bytes = Base64.decode(bytes64, Base64.DEFAULT)

        Log.d("myDebug", "decrypt bytes: ${bytes.map { it.toInt().toByte() }.joinToString(" ")}")

        // Pointer for moving, and extracting data from ByteArray
        var pointer = 0
        
        val ivSize = bytes[pointer++].toInt()

        val iv = bytes.sliceArray(pointer until ivSize + pointer)

        Log.d("myDebug", "iv: ${iv.map { it.toInt().toByte() }.joinToString(" ")}")

        pointer += ivSize

        // size of the message is coded over 4 bytes (int)
        val messageSize = getSizeFromBytes(bytes.sliceArray(pointer until pointer + 4))

        Log.d("myDebug", "messageSize: $messageSize")

        pointer += 4 


        val encryptedMessage = bytes.sliceArray(pointer until pointer + messageSize)
        Log.d("myDebug", "message: ${encryptedMessage.map { it.toInt().toByte() }.joinToString(" ")}")
        Log.d("myDebug", "extracted message size: ${encryptedMessage.size}")

        return getDecryptCipherForIv(iv).doFinal(encryptedMessage)
    }
}

提取消息后,我可以看到它已正确填充并提取 - 以 16 的倍数表示。我是否遗漏了某些内容?这段代码有什么问题?我留下了多个 Log.d,以防有人想运行它。

javax.crypto.BadPaddingException at android.security.keystore2.AndroidKeyStoreCipherSpiBase.engineDoFinal(AndroidKeyStoreCipherSpiBase.java:624) at javax.crypto.Cipher.doFinal(Cipher.java:2056)

感谢您的宝贵时间

java android kotlin encryption aes
1个回答
0
投票

如果解密后填充与指定的填充(在本例中为 PKCS#7 填充)不匹配,则会抛出

BadPaddingException

一个典型的原因是解密失败,例如如果解密过程中使用了错误的密钥或密文已损坏。这会产生与实际明文不同的字节序列,包括大部分无效的填充(大部分,因为不正确的解密也可能以较低的概率导致有效填充)。

此外,错误的IV也会触发此异常。对于 CBC 模式,不正确的 IV 会导致第一个块损坏。对于小于 1 块(AES 为 16 字节)的明文,填充会自动受到损坏的影响,因此通常会导致

BadPaddingException
。对于大于/等于一个块的明文,填充字节不位于第一个块中,因此填充不会受到损坏的影响,并且不会抛出
BadPaddingException
,但解密数据的第一个块是已损坏。

在发布的代码中,错误的 IV 确实是问题的原因。如上所述,这可以很容易地验证:如果使用代码加密小于一个块的明文,则通常(但不一定)会抛出

BadPaddingException
;对于较长的明文,除了第一个块已损坏之外,解密是成功的。
IV 不正确的原因是 getter
encryptCipher
创建(并初始化)一个新的
Cipher
实例。结果,
encryptCipher.doFinal(bytes)
中加密时使用的IV与
encryptCipher.iv
中确定的IV不同,后者随后与密文连接,从而最终在解密时使用了错误的IV。

可能的解决方法是:

...
val cipher = encryptCipher
val encrypted = cipher.doFinal(bytes)
val sizeBytes = getSizeBytes(encrypted.size)
val iv = cipher.iv
return Base64.encode(byteArrayOf(iv.size.toByte()) + iv + sizeBytes + encrypted, Base64.DEFAULT)
...
© www.soinside.com 2019 - 2024. All rights reserved.