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

Kotlin字符串加密扩展函数实现咨询:代码检查与完善

你的AES加密实现存在的问题及完整优化方案

先帮你梳理下当前代码里的几个关键问题,这些问题不仅会导致解密失败,还可能带来安全隐患:

  • SHA1PRNG兼容性问题:SecureRandom.getInstance("SHA1PRNG")不是Java标准算法,不同平台(比如Android和Oracle JDK)的实现逻辑差异很大,用同一个seed生成的密钥在不同平台可能不一样,直接导致跨平台解密失败。
  • CBC模式缺失IV:你用的AES/CBC/PKCS5Padding必须搭配随机初始化向量(IV)使用,IV需要和密文一起保存,解密时必须用同一个IV。你的代码完全没处理IV,这会直接导致解密失败,还容易引发安全漏洞。
  • 密钥生成不安全:直接把seed作为SecureRandom的种子生成AES密钥,相当于把seed直接当作密钥材料。如果seed是用户输入的短密码,熵值极低,很容易被暴力破解。应该用专业的密钥派生算法(比如PBKDF2)来从seed生成安全的AES密钥。
  • 代码未完成:cipher.init(Cipher.ENCRY...没写完,也没处理IV和密文的组合存储,根本无法正常工作。

完整的Kotlin AES加密解密实现

下面是一套符合安全规范的完整实现,包含加密、解密函数,解决了上面所有问题:

import javax.crypto.Cipher
import javax.crypto.SecretKeyFactory
import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.PBEKeySpec
import javax.crypto.spec.SecretKeySpec
import java.security.SecureRandom
import java.util.Base64

// 可根据需求调整的常量
private const val AES_KEY_SIZE = 128 // 256位需JCE无限制权限,Android 7.0+默认支持
private const val PBKDF2_ITERATIONS = 65536 // 迭代次数越高,破解难度越大
private const val SALT_LENGTH = 16 // 随机盐的长度
private const val IV_LENGTH = 16 // AES-CBC的IV固定为16字节
private const val DELIMITER = ":" // 用于分隔盐、IV、密文的字符

/**
 * 字符串AES加密
 * @param seed 加密密钥(建议用用户输入的强密码)
 * @return 包含盐、IV、密文的Base64字符串
 */
fun String.encryptAES(seed: String): String {
    // 生成随机盐
    val salt = ByteArray(SALT_LENGTH).apply {
        SecureRandom.getInstanceStrong().nextBytes(this)
    }

    // 用PBKDF2从seed派生AES密钥
    val keySpec = PBEKeySpec(seed.toCharArray(), salt, PBKDF2_ITERATIONS, AES_KEY_SIZE)
    val keyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256")
    val rawKey = keyFactory.generateSecret(keySpec).encoded
    val secretKey = SecretKeySpec(rawKey, "AES")

    // 生成随机IV
    val iv = ByteArray(IV_LENGTH).apply {
        SecureRandom.getInstanceStrong().nextBytes(this)
    }
    val ivSpec = IvParameterSpec(iv)

    // 初始化加密器
    val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
    cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivSpec)
    val encryptedBytes = cipher.doFinal(this.toByteArray(Charsets.UTF_8))

    // 将盐、IV、密文转成Base64并拼接,方便存储/传输
    val saltBase64 = Base64.getEncoder().encodeToString(salt)
    val ivBase64 = Base64.getEncoder().encodeToString(iv)
    val encryptedBase64 = Base64.getEncoder().encodeToString(encryptedBytes)

    return "$saltBase64$DELIMITER$ivBase64$DELIMITER$encryptedBase64"
}

/**
 * AES解密字符串
 * @param seed 解密密钥(需和加密时一致)
 * @return 解密后的原始字符串
 * @throws IllegalArgumentException 加密字符串格式错误
 * @throws Exception 解密失败(如密钥错误、密文被篡改等)
 */
fun String.decryptAES(seed: String): String {
    // 拆分盐、IV、密文
    val parts = this.split(DELIMITER)
    require(parts.size == 3) { "加密字符串格式无效" }

    val salt = Base64.getDecoder().decode(parts[0])
    val iv = Base64.getDecoder().decode(parts[1])
    val encryptedBytes = Base64.getDecoder().decode(parts[2])

    // 派生和加密时相同的密钥
    val keySpec = PBEKeySpec(seed.toCharArray(), salt, PBKDF2_ITERATIONS, AES_KEY_SIZE)
    val keyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256")
    val rawKey = keyFactory.generateSecret(keySpec).encoded
    val secretKey = SecretKeySpec(rawKey, "AES")

    // 初始化解密器
    val ivSpec = IvParameterSpec(iv)
    val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
    cipher.init(Cipher.DECRYPT_MODE, secretKey, ivSpec)
    val decryptedBytes = cipher.doFinal(encryptedBytes)

    return String(decryptedBytes, Charsets.UTF_8)
}

关键细节说明

  1. PBKDF2密钥派生:用PBKDF2WithHmacSHA256算法结合随机盐和高迭代次数,把用户输入的seed转换成安全的AES密钥。即使seed是短密码,也能大幅提升破解难度。
  2. 随机IV:每次加密都生成新的随机IV,IV不需要保密,但必须和密文一起保存。这样相同的明文每次加密都会得到不同的密文,避免被攻击者通过模式识别破解。
  3. Base64编码:把二进制的盐、IV、密文转换成字符串,方便存储在数据库或传输。用分隔符:把三者分开,解密时能准确拆分。
  4. 异常处理:示例中用require检查加密字符串格式,实际项目中可以添加try-catch块处理NoSuchAlgorithmException、BadPaddingException等异常(比如密钥错误时会抛出BadPaddingException,可以据此提示用户密码错误)。

使用注意事项

  • 如果要使用256位AES密钥,JVM环境需要安装Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy Files,Android 7.0及以上版本默认支持。
  • seed建议使用强密码(长度≥12位,包含大小写字母、数字、特殊字符),不要用简单的字符串作为seed。
  • 不要硬编码盐、IV或密钥,必须每次加密生成随机盐和IV。
  • 解密时如果出现异常,大概率是密钥错误或密文被篡改,要合理提示用户,不要泄露具体异常信息给攻击者。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.20 10:35:20