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

Swift加密代码转Kotlin时遇IV长度异常问题求助

解决Kotlin Rijndael加密IV长度不匹配的问题

你遇到的InvalidAlgorithmParameterException异常核心原因是Swift和Kotlin使用的加密算法不匹配:Swift代码用的是Rijndael算法(blockSize=32),而Kotlin里误用了AES(AES是Rijndael的子集,固定blockSize=16),导致IV长度要求不一致。

问题细节拆解

  1. Swift端的加密逻辑:
    • 生成64位随机key和IV,经过SHA256得到32字节的密钥和IV
    • 使用Rijndael(key: KeySHAData, mode: .cbc),并在加密时指定blockSize:32,这意味着使用的是Rijndael-256/CBC(密钥32字节对应256位,blockSize=32字节),此时IV需要和blockSize一致,也就是32字节,正好匹配SHA256的输出长度。
  2. Kotlin端的问题:
    • 你使用了Cipher.getInstance("AES/CBC/PKCS5Padding"),AES的blockSize固定为16字节,所以要求IV必须是16字节,但你传入了SHA256生成的32字节IV,自然抛出"expected IV length of 16 but was 32"的异常。
    • 另外还存在一个隐藏错误:你用RSA加密后的key生成SHA256,而Swift是用原始随机key字符串生成的,这会导致密钥完全不匹配。

解决方案:使用BouncyCastle实现Rijndael-256/CBC

Java标准库没有内置Rijndael算法,需要引入BouncyCastle加密库来实现和Swift一致的逻辑,同时修正RSA加密的padding匹配问题。

步骤1:添加BouncyCastle依赖

在你的Gradle文件中添加:

implementation 'org.bouncycastle:bcprov-jdk15on:1.70'

步骤2:修改Kotlin加密代码

import org.bouncycastle.jce.provider.BouncyCastleProvider
import java.security.MessageDigest
import java.security.Security
import java.security.spec.RSAPublicKeySpec
import java.security.spec.X509EncodedKeySpec
import java.util.Random
import javax.crypto.Cipher
import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec

// 初始化BouncyCastle加密提供者
init {
    Security.addProvider(BouncyCastleProvider())
}

fun encrypt(toEncrypt: String): Triple<String, String, String> {
    val keyString: String = generateRandomCryptographicKey()
    val ivString: String = generateRandomCryptographicKey()
    val keyRSA = rsaEncrypt(keyString)
    val ivRSA = rsaEncrypt(ivString)

    try {
        // 用原始随机字符串生成SHA256,和Swift逻辑一致
        val keyA = keyString.toByteArray(Charsets.UTF_8)
        val ivA = ivString.toByteArray(Charsets.UTF_8)
        val toEncryptA = toEncrypt.toByteArray(Charsets.UTF_8)

        val keySHA: ByteArray = MessageDigest.getInstance("SHA-256").digest(keyA)
        val ivSHA: ByteArray = MessageDigest.getInstance("SHA-256").digest(ivA)

        // 使用BouncyCastle的Rijndael实现,匹配Swift的blockSize=32
        val cipher = Cipher.getInstance("Rijndael/CBC/PKCS7Padding", "BC")
        val secretKey = SecretKeySpec(keySHA, "Rijndael")
        val ivSpec = IvParameterSpec(ivSHA)
        cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivSpec)

        val cipherText = cipher.doFinal(toEncryptA)
        val base64String: String = android.util.Base64.encodeToString(cipherText, android.util.Base64.DEFAULT)
        val payloadDigested = base64String.formatDigested()

        Timber.e("payload: $payloadDigested\n key: $keyRSA\n iv:$ivRSA")
        return Triple(payloadDigested, keyRSA, ivRSA)
    } catch (exception: Exception) {
        exception.printStackTrace()
    }
    return Triple("payload", keyRSA, ivRSA)
}

private fun rsaEncrypt(textToEncrypt: String): String {
    val publicKeyRaw = "-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC4uhj+t8KnN8UvLrTlZNk1gWl4\nEYBRYo5XqzWX4icrcG0oWr04qSyQ7tLnNP+mvcG/2OWr+rjddZ5Ikr00DF+HIeKM\n7SlmleWr4Etpg855ysUrDJPRvtYuyEteyeR4f5OVSl8bgyb1f//BHc4+v80xzqRn\nxnJlaB+0V4kO8hQwAQIDAQAB\n-----END PUBLIC KEY-----"
    val publicKey = publicKeyRaw
        .replace("-----BEGIN PUBLIC KEY-----", "")
        .replace("-----END PUBLIC KEY-----", "")
        .replace("\n", "")
        .trim()

    val keyFactory = java.security.KeyFactory.getInstance("RSA")
    val keyBytes: ByteArray = android.util.Base64.decode(publicKey, android.util.Base64.DEFAULT)
    val spec = X509EncodedKeySpec(keyBytes)
    val publicKeyObj = keyFactory.generatePublic(spec) as java.security.interfaces.RSAPublicKey

    // 修正RSA Padding:匹配Swift默认的PKCS1Padding
    val cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding")
    cipher.init(Cipher.ENCRYPT_MODE, publicKeyObj)
    val cipherData: ByteArray = cipher.doFinal(textToEncrypt.toByteArray(Charsets.UTF_8))
    val encryptedBase64 = android.util.Base64.encodeToString(cipherData, android.util.Base64.DEFAULT)
    return encryptedBase64.formatDigested()
}

private fun generateRandomCryptographicKey(n: Int = 64): String {
    val chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"
    val random = Random.Default
    return buildString(n) {
        repeat(n) {
            append(chars[random.nextInt(chars.length)])
        }
    }
}

// 实现和Swift完全一致的字符串格式化规则
private fun String.formatDigested(): String {
    return this.replace("+", "-").replace("/", "_")
}

关键修正点说明

  1. 算法匹配:用BouncyCastle的Rijndael/CBC/PKCS7Padding替代AES,确保blockSize和Swift端一致为32字节,解决IV长度不匹配问题。
  2. SHA256输入修正:改用原始随机key字符串生成SHA256,和Swift逻辑对齐,避免密钥不匹配。
  3. RSA Padding修正:将NoPadding改为PKCS1Padding,匹配Swift端RSA加密的默认行为。
  4. 字符串格式化:确保formatDigested方法和Swift的formatString逻辑完全一致,避免传输时的编码问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.08 15:22:58