我在尝试为首选项数据存储创建加密时遇到了这个问题。当尝试解密消息时,我得到这个
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)
感谢您的宝贵时间
如果解密后填充与指定的填充(在本例中为 PKCS#7 填充)不匹配,则会抛出
BadPaddingException
。
一个典型的原因是解密失败,例如如果解密过程中使用了错误的密钥或密文已损坏。这会产生与实际明文不同的字节序列,包括大部分无效的填充(大部分,因为不正确的解密也可能以较低的概率导致有效填充)。
此外,错误的IV也会触发此异常。对于 CBC 模式,不正确的 IV 会导致第一个块损坏。对于小于 1 块(AES 为 16 字节)的明文,填充会自动受到损坏的影响,因此通常会导致
BadPaddingException
。对于大于/等于一个块的明文,填充字节不位于第一个块中,因此填充不会受到损坏的影响,并且不会抛出BadPaddingException
,但解密数据的第一个块是已损坏。
在发布的代码中,错误的 IV 确实是问题的原因。如上所述,这可以很容易地验证:如果使用代码加密小于一个块的明文,则通常(但不一定)会抛出
BadPaddingException
;对于较长的明文,除了第一个块已损坏之外,解密是成功的。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)
...