使用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 }
问题根源
两边的加密逻辑有三个核心差异:
- 密文格式不同:Crypto.js的输出是
Salted__固定前缀 + 8字节salt + 加密数据;原Swift代码是salt + IV + 加密数据,结构完全不匹配 - IV生成逻辑不同:Crypto.js不会随机生成IV,而是通过EVP_BytesToKey算法从密码和salt派生密钥+IV;原Swift代码是随机生成IV,这是密文不一致的关键
- 密钥派生算法差异: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) }
修正说明
- 对齐密文格式:输出前缀改为Crypto.js的固定
Salted__,结构调整为前缀 + salt + 加密数据 - 替换IV生成逻辑:用EVP_BytesToKey从密码和salt派生IV,不再随机生成
- 替换密钥派生算法:改用Crypto.js默认的EVP_BytesToKey,确保密钥和IV与JS端完全一致
内容的提问来源于stack exchange,提问作者programmer443
相关产品推荐
相关产品推荐

