如何用CryptoJS实现AES-256加密并在Swift中解密及解决解密失败问题
CryptoJS AES-256加密与Swift解密:踩坑指南&完整方案
嘿,我来帮你搞定这个跨平台AES加密解密的问题——我之前也踩过CryptoJS默认逻辑的坑,它背后藏了不少你没注意到的细节,咱们一步步拆解:
一、先搞懂你当前CryptoJS代码的默认逻辑
你写的var encrypted = CryptoJS.AES.encrypt("mystringforencryption", "secret");是CryptoJS的简化写法,它默认做了这些操作:
- 生成随机8字节盐
- 通过PBKDF2算法,用密码、盐、1000次迭代、SHA-1哈希,生成48字节数据(32字节AES-256密钥 + 16字节IV)
- 用CBC模式+PKCS#7填充加密明文
- 最终调用
encrypted.toString()得到的Base64字符串,格式是:Salted__(8字节) + 盐(8字节) + 密文,三者拼接后再Base64编码
你之前打印的encrypted.key是推导后的32字节密钥,encrypted.iv是PBKDF2生成的16字节IV——但如果你在Swift端没对应这个流程,肯定解密失败。
二、标准CryptoJS加密代码(显式化逻辑,方便对齐)
为了避免默认逻辑的坑,咱们把每一步都写出来,这样和Swift端对齐更清晰:
const plaintext = "mystringforencryption"; const password = "secret"; // 生成随机8字节盐 const salt = CryptoJS.lib.WordArray.random(8); // PBKDF2生成密钥+IV:48字节(32位密钥+16位IV),1000次迭代,SHA-1 const keyIv = CryptoJS.PBKDF2(password, salt, { keySize: (256 + 128)/32, // 256位密钥+128位IV,总长度48字节 iterations: 1000, hasher: CryptoJS.algo.SHA1 }); const key = CryptoJS.lib.WordArray.create(keyIv.words.slice(0, 8)); // 前32字节(8个Word) const iv = CryptoJS.lib.WordArray.create(keyIv.words.slice(8, 12)); // 后16字节(4个Word) // CBC模式、PKCS7填充加密 const encrypted = CryptoJS.AES.encrypt(plaintext, key, { iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 }); // 生成CryptoJS默认格式的Base64字符串 const encryptedStr = CryptoJS.enc.Base64.stringify( CryptoJS.enc.Utf8.parse("Salted__").concat(salt).concat(encrypted.ciphertext) ); console.log("加密结果:", encryptedStr);
注:这个代码和你原来的
CryptoJS.AES.encrypt(...).toString()输出完全一致,只是把默认逻辑显式化了,方便你理解。
三、对应Swift(CryptoSwift)解密代码
现在用CryptoSwift实现完全匹配的解密逻辑,一步一步对应:
import CryptoSwift func decryptCryptoJSAES256(encryptedBase64: String, password: String) throws -> String { // 1. 解码Base64得到原始二进制数据 guard let encryptedData = Data(base64Encoded: encryptedBase64) else { throw NSError(domain: "DecryptError", code: 1, userInfo: [NSLocalizedDescriptionKey: "Base64解码失败"]) } // 2. 解析CryptoJS的默认格式:前8字节是"Salted__",接下来8字节是盐,剩下的是密文 guard encryptedData.count >= 16 else { throw NSError(domain: "DecryptError", code: 2, userInfo: [NSLocalizedDescriptionKey: "加密数据格式不正确"]) } let salt = encryptedData.subdata(in: 8..<16) let ciphertext = encryptedData.subdata(in: 16..<encryptedData.count) // 3. 用PBKDF2生成密钥+IV:和CryptoJS参数完全一致 let keyIvBytes = try PKCS5.PBKDF2( password: password.utf8, salt: salt.bytes, iterations: 1000, keyLength: 32 + 16, // 32字节密钥+16字节IV variant: .sha1 ).calculate() let key = Array(keyIvBytes[0..<32]) let iv = Array(keyIvBytes[32..<48]) // 4. CBC模式解密,PKCS7填充 let decryptedBytes = try AES( key: key, blockMode: CBC(iv: iv), padding: .pkcs7 ).decrypt(ciphertext.bytes) // 5. 转成UTF8字符串 guard let decryptedStr = String(data: Data(decryptedBytes), encoding: .utf8) else { throw NSError(domain: "DecryptError", code: 3, userInfo: [NSLocalizedDescriptionKey: "解密后数据无法转成字符串"]) } return decryptedStr } // 测试调用示例 do { // 替换成你CryptoJS输出的Base64字符串 let encryptedStr = "U2FsdGVkX19ZcG5aS2t5bQ==..."; let password = "secret"; let plaintext = try decryptCryptoJSAES256(encryptedBase64: encryptedStr, password: password) print("解密成功:", plaintext) } catch { print("解密失败:", error.localizedDescription) }
四、你之前解密失败的常见原因
我猜你大概率踩了这些坑:
- 密钥推导不一致:你可能直接把密码当作密钥用,但CryptoJS是用PBKDF2从密码+盐推导的密钥,不是直接用密码的UTF8字节
- IV处理错误:CryptoJS默认是从PBKDF2结果里取后16字节当IV,不是单独生成IV并传输
- 格式解析错误:你没处理
Salted__开头的格式,导致盐和密文拆分错误 - 模式/填充不匹配:CryptoJS默认用CBC+PKCS7,如果你Swift端用了ECB或者其他填充方式,肯定失败
五、如果想自定义密钥&IV(不用PBKDF2)
要是你不想用默认的PBKDF2推导,想自己指定密钥和IV,那两边代码要这样写:
CryptoJS加密代码
const plaintext = "mystringforencryption"; // 必须是32字节的密钥(AES-256要求) const key = CryptoJS.enc.Utf8.parse("your32ByteSecretKeyHere123456"); // 必须是16字节的IV(AES块大小固定128位) const iv = CryptoJS.enc.Utf8.parse("your16ByteIVHere"); const encrypted = CryptoJS.AES.encrypt(plaintext, key, { iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 }); // 输出密文的Base64,同时要把IV也传给Swift端 const ciphertextBase64 = encrypted.ciphertext.toString(CryptoJS.enc.Base64); const ivBase64 = iv.toString(CryptoJS.enc.Base64); console.log("密文:", ciphertextBase64); console.log("IV:", ivBase64);
Swift解密代码
import CryptoSwift func decryptCustomAES256(ciphertextBase64: String, key: String, ivBase64: String) throws -> String { guard let ciphertext = Data(base64Encoded: ciphertextBase64), let ivData = Data(base64Encoded: ivBase64), ivData.count == 16, key.utf8.count == 32 else { throw NSError(domain: "DecryptError", code: 1, userInfo: [NSLocalizedDescriptionKey: "参数格式错误"]) } let decryptedBytes = try AES( key: key.utf8.bytes, blockMode: CBC(iv: ivData.bytes), padding: .pkcs7 ).decrypt(ciphertext.bytes) guard let decryptedStr = String(data: Data(decryptedBytes), encoding: .utf8) else { throw NSError(domain: "DecryptError", code: 2, userInfo: [NSLocalizedDescriptionKey: "解密后转字符串失败"]) } return decryptedStr }
内容的提问来源于stack exchange,提问作者user1820178
相关产品推荐
相关产品推荐

