Swift加密代码转Kotlin时遇IV长度异常问题求助
解决Kotlin Rijndael加密IV长度不匹配的问题
你遇到的InvalidAlgorithmParameterException异常核心原因是Swift和Kotlin使用的加密算法不匹配:Swift代码用的是Rijndael算法(blockSize=32),而Kotlin里误用了AES(AES是Rijndael的子集,固定blockSize=16),导致IV长度要求不一致。
问题细节拆解
- 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的输出长度。
- 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("/", "_") }
关键修正点说明
- 算法匹配:用BouncyCastle的
Rijndael/CBC/PKCS7Padding替代AES,确保blockSize和Swift端一致为32字节,解决IV长度不匹配问题。 - SHA256输入修正:改用原始随机key字符串生成SHA256,和Swift逻辑对齐,避免密钥不匹配。
- RSA Padding修正:将NoPadding改为PKCS1Padding,匹配Swift端RSA加密的默认行为。
- 字符串格式化:确保
formatDigested方法和Swift的formatString逻辑完全一致,避免传输时的编码问题。
内容的提问来源于stack exchange,提问作者Raykud
相关产品推荐
相关产品推荐

