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

如何用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)
}

四、你之前解密失败的常见原因

我猜你大概率踩了这些坑:

  1. 密钥推导不一致:你可能直接把密码当作密钥用,但CryptoJS是用PBKDF2从密码+盐推导的密钥,不是直接用密码的UTF8字节
  2. IV处理错误:CryptoJS默认是从PBKDF2结果里取后16字节当IV,不是单独生成IV并传输
  3. 格式解析错误:你没处理Salted__开头的格式,导致盐和密文拆分错误
  4. 模式/填充不匹配: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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.21 08:19:44