如何实现Kotlin/Java与Node.js间AES/CBC跨语言加解密?
解决Kotlin/Java与Node.js跨语言AES加解密不兼容问题
搞定跨语言AES加解密的坑我熟!你现在遇到的问题核心就是Kotlin/Java和Node.js之间的加解密参数没有严格对齐,哪怕一点点细节差异(比如编码、哈希算法、填充逻辑)都会导致解密失败。我来一步步帮你解决:
先明确你现有Kotlin代码的关键参数(补全你未贴完的部分)
首先我先补全你提供的加密函数(根据常规写法推断),确保我们的分析基于统一的逻辑:
import java.security.MessageDigest import java.security.SecureRandom import javax.crypto.Cipher import javax.crypto.spec.IvParameterSpec import javax.crypto.spec.SecretKeySpec import java.util.Base64 @Throws(Exception::class) fun encrypt(text: String, password: String?): String? { if (password == null) return null // 假设你用MD5哈希密码,取前16字节作为AES-128的Key和IV val passwordHash = MessageDigest.getInstance("MD5") .digest(password.toByteArray(Charsets.UTF_8)) .copyOf(16) // 截取前16字节(AES-128密钥长度) val keySpec = SecretKeySpec(passwordHash, "AES") val ivSpec = IvParameterSpec(passwordHash) // 注意:你这里用了同一个哈希值当Key和IV val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding") cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec) val encryptedBytes = cipher.doFinal(text.toByteArray(Charsets.UTF_8)) // 加密结果转Base64字符串返回 return Base64.getEncoder().encodeToString(encryptedBytes) } // 同一语言内的解密函数(用于对比) @Throws(Exception::class) fun decrypt(encryptedBase64: String, password: String?): String? { if (password == null) return null val passwordHash = MessageDigest.getInstance("MD5") .digest(password.toByteArray(Charsets.UTF_8)) .copyOf(16) val keySpec = SecretKeySpec(passwordHash, "AES") val ivSpec = IvParameterSpec(passwordHash) val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding") cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec) val encryptedBytes = Base64.getDecoder().decode(encryptedBase64) val decryptedBytes = cipher.doFinal(encryptedBytes) return String(decryptedBytes, Charsets.UTF_8) }
跨语言兼容的Node.js实现
要让Node.js能解密Kotlin加密的内容,必须严格对齐以下几个关键参数:
- 哈希算法:和Kotlin一致用MD5
- Key/IV生成:取密码MD5哈希的前16字节,且Key和IV用同一个值(和你的Kotlin代码保持一致)
- 编码:所有字符串转字节都用UTF-8
- 填充方式:Kotlin的
PKCS5Padding对应Node.js的pkcs7(因为AES块大小是16字节,PKCS5和PKCS7填充逻辑完全兼容) - 加密结果编码:Kotlin转Base64,Node.js解密前先解码Base64
Node.js 解密Kotlin加密内容的代码
const crypto = require('crypto'); function decryptFromKotlin(encryptedBase64, password) { if (!password) return null; // 1. 对齐Kotlin的密码哈希逻辑:MD5 + UTF-8编码,取前16字节当Key和IV const passwordHash = crypto.createHash('md5') .update(password, 'utf8') .digest(); const key = passwordHash.slice(0, 16); const iv = passwordHash.slice(0, 16); // 和Kotlin一样用同一个哈希值当IV // 2. 解码Base64得到加密字节 const encryptedBytes = Buffer.from(encryptedBase64, 'base64'); // 3. 初始化解密器:AES-128-CBC + PKCS7填充 const decipher = crypto.createDecipheriv('aes-128-cbc', key, iv); // 4. 完成解密并返回字符串 let decrypted = decipher.update(encryptedBytes, null, 'utf8'); decrypted += decipher.final('utf8'); return decrypted; }
Node.js 加密(让Kotlin能解密)的代码
反过来,如果要在Node.js加密,让Kotlin能解密,逻辑完全对称:
function encryptForKotlin(text, password) { if (!password) return null; const passwordHash = crypto.createHash('md5') .update(password, 'utf8') .digest(); const key = passwordHash.slice(0, 16); const iv = passwordHash.slice(0, 16); const cipher = crypto.createCipheriv('aes-128-cbc', key, iv); let encrypted = cipher.update(text, 'utf8', 'base64'); encrypted += cipher.final('base64'); return encrypted; }
验证示例
步骤1:Kotlin加密
val originalText = "Hello Cross-Language AES!" val password = "mySecurePass123" val encrypted = encrypt(originalText, password) // 得到类似:"rO0ABXNyABFqYXZhLnV0aWwuSGFzaE1hcAUH2sHDFmDRAwACRgAKbG9hZEZhY3RvckkACXRocmVzaG9sZHhwP0AAAAAAAAx3CAAAABAAAAADdAAEAAAAKdAAUHello Cross-Language AES!" 的Base64字符串
步骤2:Node.js解密
const encryptedFromKotlin = "上面Kotlin输出的Base64字符串"; const password = "mySecurePass123"; const decrypted = decryptFromKotlin(encryptedFromKotlin, password); console.log(decrypted); // 输出:Hello Cross-Language AES!
重要优化建议(可选但推荐)
你当前的代码用同一个哈希值当Key和IV,这是不安全的!IV(初始化向量)应该是随机生成的,每次加密都用不同的IV,这样相同的明文和密码加密结果会不一样,避免被破解。
优化后的Kotlin加密/解密
@Throws(Exception::class) fun encryptSecure(text: String, password: String?): String? { if (password == null) return null val passwordHash = MessageDigest.getInstance("MD5") .digest(password.toByteArray(Charsets.UTF_8)) .copyOf(16) val keySpec = SecretKeySpec(passwordHash, "AES") // 生成随机IV(16字节) val iv = ByteArray(16) SecureRandom().nextBytes(iv) val ivSpec = IvParameterSpec(iv) val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding") cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec) val encryptedBytes = cipher.doFinal(text.toByteArray(Charsets.UTF_8)) // 把IV和加密字节拼接,再转Base64(传输时一起发送) val combinedBytes = iv + encryptedBytes return Base64.getEncoder().encodeToString(combinedBytes) } @Throws(Exception::class) fun decryptSecure(encryptedBase64: String, password: String?): String? { if (password == null) return null val combinedBytes = Base64.getDecoder().decode(encryptedBase64) // 拆分IV(前16字节)和加密内容 val iv = combinedBytes.copyOf(16) val encryptedBytes = combinedBytes.copyOfRange(16, combinedBytes.size) val passwordHash = MessageDigest.getInstance("MD5") .digest(password.toByteArray(Charsets.UTF_8)) .copyOf(16) val keySpec = SecretKeySpec(passwordHash, "AES") val ivSpec = IvParameterSpec(iv) val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding") cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec) val decryptedBytes = cipher.doFinal(encryptedBytes) return String(decryptedBytes, Charsets.UTF_8) }
对应优化后的Node.js代码
// 解密Kotlin优化后的加密内容 function decryptSecureFromKotlin(encryptedBase64, password) { if (!password) return null; const combinedBytes = Buffer.from(encryptedBase64, 'base64'); // 拆分IV和加密内容 const iv = combinedBytes.slice(0, 16); const encryptedBytes = combinedBytes.slice(16); const passwordHash = crypto.createHash('md5') .update(password, 'utf8') .digest() .slice(0, 16); const decipher = crypto.createDecipheriv('aes-128-cbc', passwordHash, iv); let decrypted = decipher.update(encryptedBytes, null, 'utf8'); decrypted += decipher.final('utf8'); return decrypted; } // Node.js加密(让Kotlin能解密优化后的内容) function encryptSecureForKotlin(text, password) { if (!password) return null; const passwordHash = crypto.createHash('md5') .update(password, 'utf8') .digest() .slice(0, 16); // 生成随机IV const iv = crypto.randomBytes(16); const cipher = crypto.createCipheriv('aes-128-cbc', passwordHash, iv); let encrypted = cipher.update(text, 'utf8'); encrypted = Buffer.concat([encrypted, cipher.final()]); // 拼接IV和加密内容,转Base64 const combinedBytes = Buffer.concat([iv, encrypted]); return combinedBytes.toString('base64'); }
内容的提问来源于stack exchange,提问作者Liam
相关产品推荐
相关产品推荐

