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

如何实现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加密的内容,必须严格对齐以下几个关键参数:

  1. 哈希算法:和Kotlin一致用MD5
  2. Key/IV生成:取密码MD5哈希的前16字节,且Key和IV用同一个值(和你的Kotlin代码保持一致)
  3. 编码:所有字符串转字节都用UTF-8
  4. 填充方式:Kotlin的PKCS5Padding对应Node.js的pkcs7(因为AES块大小是16字节,PKCS5和PKCS7填充逻辑完全兼容)
  5. 加密结果编码: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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.21 07:37:56