You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Keystore解密时遇IllegalBlockSizeException及BadPaddingException问题

问题分析与修复方案

问题背景

我正在构建一个加密解密自定义User类的工具类,需求是加密User类的所有字段并以字符串形式存储,解密时还原字段。目前遇到两个问题:

  • 将字节数组解码为字符串再编码回字节数组后,块大小与原16字节不符,触发IllegalBlockSizeException
  • 即使块大小为16的倍数,仍触发BadPaddingException,纯字节数组操作时也出现相同错误

核心错误原因

  1. 二进制转字符串编码错误:加密后的字节数组(包含IV和密文)属于二进制数据,直接用decodeToString()和encodeToByteArray()做UTF-8编码转换会丢失数据——并非所有二进制值都是合法UTF-8字符,转回的字节数组与原数据不一致,必然导致解密失败。
  2. IV长度获取逻辑错误:解密时通过encryptCipher.iv.size获取IV长度,但encryptCipher是get属性,每次调用都会创建新Cipher实例并生成新IV,这会导致解密时读取的IV长度与加密时实际长度不匹配(虽AES CBC的IV固定16字节,但写法逻辑错误)。

修复后的代码

CryptoManager类

package com.plcoding.androidcrypto

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

@RequiresApi(Build.VERSION_CODES.M)
class CryptoManager {

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

    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("secret", null) as? KeyStore.SecretKeyEntry
        return existingKey?.secretKey ?: createKey()
    }

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

    private fun encrypt(input: ByteArray): String {
        val encryptedBytes = encryptCipher.doFinal(input)
        // 将IV和密文拼接后转Base64字符串
        val combined = ByteArray(encryptCipher.iv.size + encryptedBytes.size)
        System.arraycopy(encryptCipher.iv, 0, combined, 0, encryptCipher.iv.size)
        System.arraycopy(encryptedBytes, 0, combined, encryptCipher.iv.size, encryptedBytes.size)
        return Base64.encodeToString(combined, Base64.DEFAULT)
    }

    private fun decrypt(input: String): String {
        // 先将Base64字符串转字节数组
        val combined = Base64.decode(input, Base64.DEFAULT)
        // AES CBC的IV固定为16字节
        val iv = ByteArray(16)
        val encryptedBytes = ByteArray(combined.size - iv.size)
        System.arraycopy(combined, 0, iv, 0, iv.size)
        System.arraycopy(combined, iv.size, encryptedBytes, 0, encryptedBytes.size)
        return getDecryptCipherForIv(iv).doFinal(encryptedBytes).decodeToString()
    }

    fun encryptUser(user: User): User {
        val encryptedName = user.name?.encodeToByteArray()?.let { encrypt(it) }
        val encryptedApp = user.app?.encodeToByteArray()?.let { encrypt(it) }
        val encryptedPassword = user.password?.encodeToByteArray()?.let { encrypt(it) }
        val encryptedNotes = user.notes?.encodeToByteArray()?.let { encrypt(it) }
        return User(encryptedName, encryptedApp, encryptedPassword, encryptedNotes)
    }

    fun decryptUser(user: User?): User {
        val decryptedName = user?.name?.let { decrypt(it) }
        val decryptedApp = user?.app?.let { decrypt(it) }
        val decryptedPassword = user?.password?.let { decrypt(it) }
        val decryptedNotes = user?.notes?.let { decrypt(it) }
        return User(decryptedName, decryptedApp, decryptedPassword, decryptedNotes)
    }

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

User类

class User(
    val name: String? = null,
    val app: String? = null,
    val password: String? = null,
    var notes: String? = null,
    val id: String? = null,
)

关键修复点

  • 替换UTF-8编码为Base64:加密后的二进制数据用Base64.encodeToString()转为安全字符串,解密时用Base64.decode()还原字节数组,确保数据完整无丢失。
  • 固定IV长度:AES CBC模式下IV长度固定为16字节,解密时直接使用该长度拆分IV和密文,避免依赖动态生成的Cipher实例的IV长度。
  • 简化解密方法参数:直接接收加密后的Base64字符串,不需要手动转字节数组,减少出错环节。

内容的提问来源于stack exchange,提问作者Raz tamim

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 19:53:14