You need to enable JavaScript to run this app. PHP加密JSON传输至Kotlin应用的解密适配与密钥配置问题
优惠活动
大模型
产品
解决方案
定价
更多

PHP加密JSON传输至Kotlin应用的解密适配与密钥配置问题

PHP与Kotlin AES-128-GCM加解密互通配置方案

原代码无法互通的核心原因

  • 算法模式完全错配:PHP侧使用aes-128-ctr流模式,Kotlin侧实现为AES/CBC/PKCS7Padding块模式,加密逻辑、填充规则完全不兼容
  • 密钥逻辑错配:PHP侧是直接将原始密钥字节传入openssl作为AES密钥,Kotlin侧却使用PBKDF2WithHmacSHA1算法从口令派生256位密钥,且引入了无对应PHP侧逻辑的salt参数,两端密钥根本不一致
  • 密钥长度错配:PHP侧aes-128要求16字节长度的AES密钥,Kotlin侧派生出32字节的256位密钥,算法长度无法对应
  • IV长度不兼容:切换到GCM模式后,原16字节IV不符合GCM安全规范,参数不一致会直接导致解密失败
  • 认知偏差:GCM模式自带的认证标签是防篡改的核心组件,不存在“避免处理标签”的实现方式,跳过标签处理会直接失去防篡改能力
  • 代码笔误:PHP侧将密钥变量$key写为$kay,会直接导致密钥加载失败

统一互通的固定对齐规则

以下参数两端必须完全一致,无任何自主调整空间:

  • 算法套件:AES/GCM/NoPadding(GCM为流模式,无需块填充)
  • 密钥长度:128位(16字节,对应PHP侧aes-128-gcm)
  • 认证标签长度:128位(GCM标准安全长度)
  • IV长度:12字节(GCM推荐的安全IV长度)
  • 传输格式:将「原始二进制密文 + 128位认证标签」拼接为整体后做Base64编码传输

对应实现代码

PHP端加密实现

// 固定参数,需与Kotlin侧完全对齐
$cipher = "aes-128-gcm";
// 16字节原始AES密钥,Base64编码后存储,需与Kotlin侧常量值一致
$key = base64_decode("MTIzNDU2Nzg5MDEyMzQ1Ng==");
// 12字节IV,Base64编码后存储,需与Kotlin侧常量值一致
$iv = base64_decode("dGhpMTJieXRlc0l2MDAw");

$plaintext = "Encryption information";
$authTag = ""; // 存储openssl生成的128位GCM认证标签

// 加密,输出原始二进制密文
$ciphertext = openssl_encrypt(
    $plaintext,
    $cipher,
    $key,
    OPENSSL_RAW_DATA, // 不自动Base64输出密文
    $iv,
    $authTag // 传入标签变量引用,openssl自动写入生成的标签
);

// 拼接密文和标签,整体Base64作为最终传输内容
$finalPayload = base64_encode($ciphertext . $authTag);
echo $finalPayload;

密钥对应关系说明:PHP侧解码后的16字节$key就是需要放到Kotlin代码里的唯一密钥常量,无需做任何派生转换,原Kotlin代码中的salt、PBKDF2派生逻辑可直接删除。

Kotlin端解密实现

import android.util.Base64
import javax.crypto.Cipher
import javax.crypto.spec.GCMParameterSpec
import javax.crypto.spec.SecretKeySpec

object AESEncryption {
    // 直接填入与PHP侧完全一致的参数,无派生逻辑
    const val secretKeyBase64 = "MTIzNDU2Nzg5MDEyMzQ1Ng==" // 16字节密钥的Base64值
    const val ivBase64 = "dGhpMTJieXRlc0l2MDAw" // 12字节IV的Base64值

    fun decrypt(encodedPayload: String): String? {
        try {
            // 解码传输的Base64内容,拆分密文与16字节认证标签
            val fullData = Base64.decode(encodedPayload, Base64.DEFAULT)
            val cipherTextPart = fullData.copyOfRange(0, fullData.size - 16)
            val tagPart = fullData.copyOfRange(fullData.size - 16, fullData.size)

            // 构造AES密钥:直接使用16字节原始密钥,无需PBKDF2派生
            val secretKey = SecretKeySpec(
                Base64.decode(secretKeyBase64, Base64.DEFAULT),
                "AES"
            )

            // 构造GCM参数:128位标签长度 + 12字节IV
            val gcmSpec = GCMParameterSpec(
                128,
                Base64.decode(ivBase64, Base64.DEFAULT)
            )

            // 算法完全对应PHP侧的aes-128-gcm
            val cipher = Cipher.getInstance("AES/GCM/NoPadding")
            cipher.init(Cipher.DECRYPT_MODE, secretKey, gcmSpec)
            // 传入拼接好的密文与标签,完成解密和篡改校验
            val plainResult = cipher.doFinal(cipherTextPart + tagPart)

            return String(plainResult)
        } catch (e: Exception) {
            println("Decryption failed: $e")
        }
        return null
    }
}

关键疑问对应说明

  • 原Kotlin代码的secretKey、salt配置:原代码使用PBKDF2从口令派生密钥的逻辑,仅适用于从用户输入的口令生成密钥的场景;如果PHP侧直接使用固定16字节密钥,需完全删除派生逻辑、salt参数,直接将PHP侧的原始密钥值填入Kotlin的常量即可
  • PHP侧$cipher参数的Kotlin对应:aes-128-gcm完全对应Kotlin的AES/GCM/NoPadding配置,无需额外转换
  • 防篡改能力说明:GCM模式解密时会自动校验认证标签,如果传输的密文被篡改,标签校验会直接失败抛出异常,完全满足“加密锁定、无法篡改”的需求,认证标签是实现该能力的核心,不能跳过处理

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.11.03 11:36:04