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

Swift CryptoKit AES加密与JavaScript AES解密适配问题求助

iOS CryptoKit AES-GCM 与 JavaScript 跨端加解密适配方案

问题场景

iOS端使用CryptoKit的AES-GCM算法加密ISO格式日期字符串,仅将密文传递至服务端后,服务端通过CryptoJS解密时出现两种异常:要么提示UTF-8格式错误,要么解密得到随机乱码内容,无法还原原始日期字符串。

核心错误原因

  1. AES-GCM解密依赖要素缺失:AES-GCM是带认证的加密算法,解密时必须同时具备**密钥、密文、初始化向量(IV)、认证标签(Tag)**四个要素。原iOS代码仅返回了密文,未将自动生成的IV和Tag传递给服务端。
  2. 加密模式不匹配:CryptoJS默认使用AES-CBC模式,而iOS端用的是AES-GCM模式,两者不兼容。

修正后的Swift加密实现

将IV、密文、Tag分别Base64编码后拼接成字符串(用分隔符区分),一起传递给服务端:

import CryptoKit

func encryptDate() throws -> String {
    let keyString = "SomePrivateKey"
    // 确保密钥长度符合AES要求:16字节(AES-128)、24字节(AES-192)或32字节(AES-256)
    // 若密钥长度不足,需用KDF(如HKDF)处理,此处假设密钥长度合法
    guard let keyData = keyString.data(using: .utf8),
          let symmetricKey = SymmetricKey(data: keyData) else {
        throw EncryptionError.invalidKey
    }
    
    let dateString = Date().toISOFormat()
    guard let plaintextData = dateString.data(using: .utf8) else {
        throw EncryptionError.invalidPlaintext
    }
    
    // 执行AES-GCM加密,自动生成随机IV和Tag
    let sealedBox = try AES.GCM.seal(plaintextData, using: symmetricKey)
    
    // 将IV、密文、Tag分别Base64编码后拼接
    let ivBase64 = sealedBox.nonce.base64EncodedString()
    let ciphertextBase64 = sealedBox.ciphertext.base64EncodedString()
    let tagBase64 = sealedBox.tag.base64EncodedString()
    
    return "\(ivBase64):\(ciphertextBase64):\(tagBase64)"
}

enum EncryptionError: Error {
    case invalidKey
    case invalidPlaintext
}

修正后的JavaScript解密实现

方案1:使用CryptoJS

解析iOS传递的拼接字符串,提取IV、密文、Tag,指定GCM模式解密:

function decryptWithCryptoJS(encryptedString, keyString) {
    // 拆分IV、密文、Tag
    const [ivBase64, ciphertextBase64, tagBase64] = encryptedString.split(':');
    
    // 转换为CryptoJS格式的对象
    const key = CryptoJS.enc.Utf8.parse(keyString);
    const iv = CryptoJS.enc.Base64.parse(ivBase64);
    const ciphertext = CryptoJS.enc.Base64.parse(ciphertextBase64);
    const tag = CryptoJS.enc.Base64.parse(tagBase64);
    
    // 构建CryptoJS的GCM参数
    const encryptedData = CryptoJS.lib.CipherParams.create({
        ciphertext: ciphertext,
        iv: iv,
        authTag: tag
    });
    
    // 指定GCM模式解密
    const decrypted = CryptoJS.AES.decrypt(encryptedData, key, {
        mode: CryptoJS.mode.GCM,
        iv: iv,
        authTag: tag
    });
    
    return decrypted.toString(CryptoJS.enc.Utf8);
}

方案2:使用原生Web Crypto API(推荐,更符合现代标准)

async function decryptWithWebCrypto(encryptedString, keyString) {
    const [ivBase64, ciphertextBase64, tagBase64] = encryptedString.split(':');
    
    // 转换为ArrayBuffer格式
    const iv = Uint8Array.from(atob(ivBase64), c => c.charCodeAt(0));
    const ciphertext = Uint8Array.from(atob(ciphertextBase64), c => c.charCodeAt(0));
    const tag = Uint8Array.from(atob(tagBase64), c => c.charCodeAt(0));
    
    // 合并密文和Tag(Web Crypto要求密文与Tag拼接在一起)
    const combinedData = new Uint8Array(ciphertext.length + tag.length);
    combinedData.set(ciphertext, 0);
    combinedData.set(tag, ciphertext.length);
    
    // 导入密钥
    const keyData = new TextEncoder().encode(keyString);
    const cryptoKey = await crypto.subtle.importKey(
        'raw',
        keyData,
        { name: 'AES-GCM' },
        false,
        ['decrypt']
    );
    
    // 解密
    const decryptedBuffer = await crypto.subtle.decrypt(
        { name: 'AES-GCM', iv: iv },
        cryptoKey,
        combinedData
    );
    
    // 转换为字符串
    return new TextDecoder().decode(decryptedBuffer);
}

关键注意事项

  • 密钥长度合规性:AES密钥必须是16/24/32字节,若原始密钥长度不符合,需用HKDF等密钥派生函数处理,避免因密钥长度错误导致加解密失败。
  • 数据传递格式:除了拼接字符串,也可以用JSON格式打包IV、密文、Tag,传递更规范。
  • 编码一致性:两端需统一使用Base64编码,避免因编码格式差异导致数据解析错误。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.24 20:48:22