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

使用Crypto.js与CryptoSwift实现AES加密结果不一致问题

AES跨平台加密不一致问题:JS与Swift互解失败

我在Node环境用Crypto.js实现AES加密,JS内部加解密完全正常;同时用Swift的CryptoSwift写了AES-CBC-PKCS7加密逻辑,Swift内部加解密也没问题,但JS生成的密文拿到Swift里解密就失败,查下来是两边生成的密文根本不一样。

测试参数

  • 输入文本:test
  • 密钥:password
  • JS生成的密文:U2FsdGVkX18087rLuHfWYc9JmmhZL4jJD/Pu8IGuL/E=
  • Swift生成的密文:RtWc9mSXPY7/6A6x/9OTE7rhBJqEWxqEYkKe33VCpnesN1Q4wujpww==

原Swift代码

func aes256CBC(key: String, input: String) -> String? {
    guard let keyData = key.data(using: .utf8), let inputData = input.data(using: .utf8) else {
        return nil
    }

    let salt = generateSalt(length: 8)
    let iv = generateSalt(length: 16)
    
    let key = try! PKCS5.PBKDF2(password: keyData.bytes, salt: salt.bytes, iterations: 1, keyLength: 32, variant: .md5).calculate()
        
    let aes = try! AES(key: key, blockMode: CBC(iv: iv.bytes), padding: .pkcs7)

    let encrypted = try! aes.encrypt(inputData.bytes)

    let result = Data(salt + iv + encrypted)

    return result.base64EncodedString()
}

func generateSalt(length: Int) -> Data {
    var salt = Data(count: length)
    let result = salt.withUnsafeMutableBytes {
        SecRandomCopyBytes(kSecRandomDefault, length, $0.baseAddress!)
    }
    if result != errSecSuccess {
        fatalError("Failed to generate salt. SecRandomCopyBytes status: \(result)")
    }
    return salt
}

问题根源

两边的加密逻辑有三个核心差异:

  1. 密文格式不同:Crypto.js的输出是Salted__固定前缀 + 8字节salt + 加密数据;原Swift代码是salt + IV + 加密数据,结构完全不匹配
  2. IV生成逻辑不同:Crypto.js不会随机生成IV,而是通过EVP_BytesToKey算法从密码和salt派生密钥+IV;原Swift代码是随机生成IV,这是密文不一致的关键
  3. 密钥派生算法差异:Crypto.js默认用的是OpenSSL的EVP_BytesToKey(基于MD5),和原代码里的PBKDF2逻辑不一样,哪怕都是MD5+1轮迭代,最终派生的密钥也不同

修正后的Swift代码(兼容Crypto.js)

import CryptoSwift
import CommonCrypto

func aes256CBC_CryptoJSCompatible(key: String, input: String) -> String? {
    guard let inputData = input.data(using: .utf8), let keyData = key.data(using: .utf8) else {
        return nil
    }
    
    // 生成8字节salt,和Crypto.js保持一致
    let salt = generateSalt(length: 8)
    
    // 用Crypto.js兼容的EVP_BytesToKey算法派生密钥和IV(AES-256需要32字节key + 16字节IV)
    let (derivedKey, derivedIV) = evpBytesToKey(password: keyData, salt: salt, keySize: 32, ivSize: 16)
    
    guard let key = derivedKey, let iv = derivedIV else {
        return nil
    }
    
    do {
        let aes = try AES(key: key, blockMode: CBC(iv: iv), padding: .pkcs7)
        let encryptedBytes = try aes.encrypt(inputData.bytes)
        // 遵循Crypto.js的密文格式:固定前缀 + salt + 加密数据
        let saltedHeader = "Salted__".data(using: .utf8)!
        let resultData = saltedHeader + salt + Data(encryptedBytes)
        return resultData.base64EncodedString()
    } catch {
        print("加密失败:\(error)")
        return nil
    }
}

func generateSalt(length: Int) -> Data {
    var salt = Data(count: length)
    let result = salt.withUnsafeMutableBytes {
        SecRandomCopyBytes(kSecRandomDefault, length, $0.baseAddress!)
    }
    guard result == errSecSuccess else {
        fatalError("生成salt失败,状态码:\(result)")
    }
    return salt
}

// 实现Crypto.js使用的EVP_BytesToKey算法
func evpBytesToKey(password: Data, salt: Data, keySize: Int, ivSize: Int) -> ([UInt8]?, [UInt8]?) {
    var key = [UInt8]()
    var iv = [UInt8]()
    var data = [UInt8]()
    var previous = [UInt8]()
    
    while key.count < keySize || iv.count < ivSize {
        let passwordBytes = password.bytes
        previous.append(contentsOf: passwordBytes)
        previous.append(contentsOf: salt.bytes)
        
        var md5Result = [UInt8](repeating: 0, count: Int(CC_MD5_DIGEST_LENGTH))
        CC_MD5(previous, CC_LONG(previous.count), &md5Result)
        
        data.append(contentsOf: md5Result)
        previous = md5Result
    }
    
    if keySize > 0 {
        key = Array(data.prefix(keySize))
    }
    if ivSize > 0 {
        iv = Array(data[keySize..<keySize+ivSize])
    }
    
    return (key, iv)
}

修正说明

  1. 对齐密文格式:输出前缀改为Crypto.js的固定Salted__,结构调整为前缀 + salt + 加密数据
  2. 替换IV生成逻辑:用EVP_BytesToKey从密码和salt派生IV,不再随机生成
  3. 替换密钥派生算法:改用Crypto.js默认的EVP_BytesToKey,确保密钥和IV与JS端完全一致

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 04:04:57