Swift CryptoKit AES加密与JavaScript AES解密适配问题求助
iOS CryptoKit AES-GCM 与 JavaScript 跨端加解密适配方案
问题场景
iOS端使用CryptoKit的AES-GCM算法加密ISO格式日期字符串,仅将密文传递至服务端后,服务端通过CryptoJS解密时出现两种异常:要么提示UTF-8格式错误,要么解密得到随机乱码内容,无法还原原始日期字符串。
核心错误原因
- AES-GCM解密依赖要素缺失:AES-GCM是带认证的加密算法,解密时必须同时具备**密钥、密文、初始化向量(IV)、认证标签(Tag)**四个要素。原iOS代码仅返回了密文,未将自动生成的IV和Tag传递给服务端。
- 加密模式不匹配: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
相关产品推荐
相关产品推荐

