Swift4中AES解密偶尔返回null值问题求助
我帮你梳理几个最可能导致这个解密方法偶尔返回nil的原因,以及对应的排查和解决思路:
1. Key/IV长度不符合AES标准
AES-128要求密钥(Key)和初始向量(IV)必须是16字节(UTF-8编码下对应16个字符),但你调用时传的key: "mykey"(仅5字节)和iv: "value"(仅5字节)完全不满足要求!
PHP的加密函数(比如openssl_encrypt)如果收到短key/IV,会自动做补全(比如用零字节填充),但你的Swift代码直接用原始短key/IV传入CCCrypt,会触发参数错误导致解密失败。
解决方法:
- 确保key和IV的长度符合AES规格:AES-128用16字节,AES-256用32字节。
- 统一Swift和PHP的key/IV处理逻辑:比如两边都对原始key做哈希(比如SHA256),再截取对应长度的字节;或者直接补零到指定长度。示例代码里我已经帮你加了key/IV的长度处理逻辑。
2. 加密模式或填充方式不匹配
你的Swift代码默认用的是CBC模式+PKCS7填充(因为传入了IV参数,且options设为kCCOptionPKCS7Padding),但如果PHP端用的是其他模式(比如ECB、GCM)或者填充方式(比如零填充),两边不匹配就会解密失败。
解决方法:
- 检查PHP端的加密代码,比如如果是
openssl_encrypt,确认第三个参数是AES-128-CBC,且填充方式是PKCS7(openssl_encrypt默认就是PKCS7,不用额外设置)。 - 如果PHP用的是零填充,把Swift代码的
options改为0,解密后手动去掉末尾的零字节。
3. Base64编码/解码存在异常
虽然你设置了.ignoreUnknownCharacters,但如果responseString里包含大量非Base64字符(比如换行、空格、传输过程中混入的乱码),还是会导致Base64解码失败,进而返回nil。
解决方法:
- 先打印
responseString的原始内容,检查是否有多余字符,预处理时可以先去掉首尾空白和换行:let cleanedResponse = responseString?.trimmingCharacters(in: .whitespacesAndNewlines) unencode = cleanedResponse?.aesDecrypt(key: "mykey", iv: "value") - 确认PHP端的Base64编码逻辑:比如不要用
chunk_split给Base64字符串加换行,避免Swift解码时出错。
4. 数据传输过程中被截断或损坏
AES加密后的数据长度必须是16字节(AES块大小)的整数倍,如果responseString对应的Base64解码后的数据长度不符合这个要求,说明数据在传输或加密过程中被截断了。
解决方法:
- 在Swift里打印解码后的
data长度:
如果长度不是16的倍数,说明数据有问题,需要排查PHP端的加密输出或网络传输环节。if let data = Data(base64Encoded: responseString!) { print("Decoded data length: \(data.count)") }
5. 打印错误码精准定位问题
你的代码只判断了是否成功,没有输出具体错误码,其实CCCrypt返回的状态码能直接告诉你失败原因(比如参数错误、填充错误等)。
解决方法:
在解密失败的分支里添加错误打印:
} else { print("Decryption failed with code: \(cryptStatus)") // 常见错误码: // -4300: 参数错误(比如key/IV长度不对) // -4303: 解码错误(比如填充不匹配) return nil }
修改后的示例代码
我调整了你的扩展方法,加入了key/IV长度处理和错误码打印:
extension String { func aesDecrypt(key: String, iv: String, options: Int = kCCOptionPKCS7Padding) -> String? { // 处理Key,确保符合AES-128的16字节要求 var keyData = key.data(using: .utf8)! if keyData.count < kCCKeySizeAES128 { let padding = Data(repeating: 0, count: kCCKeySizeAES128 - keyData.count) keyData.append(padding) } else if keyData.count > kCCKeySizeAES128 { keyData = keyData.prefix(kCCKeySizeAES128) } // 处理IV,确保符合16字节要求 var ivData = iv.data(using: .utf8)! if ivData.count < kCCBlockSizeAES128 { let padding = Data(repeating: 0, count: kCCBlockSizeAES128 - ivData.count) ivData.append(padding) } else if ivData.count > kCCBlockSizeAES128 { ivData = ivData.prefix(kCCBlockSizeAES128) } guard let data = Data(base64Encoded: self, options: .ignoreUnknownCharacters), let cryptData = NSMutableData(length: data.count + kCCBlockSizeAES128) else { return nil } let keyLength = size_t(kCCKeySizeAES128) let operation: CCOperation = UInt32(kCCDecrypt) let algoritm: CCAlgorithm = UInt32(kCCAlgorithmAES128) let options: CCOptions = UInt32(options) var numBytesEncrypted: size_t = 0 let cryptStatus = CCCrypt(operation, algoritm, options, keyData.bytes, keyLength, ivData.bytes, data.bytes, data.count, cryptData.mutableBytes, cryptData.length, &numBytesEncrypted) if cryptStatus == kCCSuccess { cryptData.length = Int(numBytesEncrypted) return String(data: cryptData as Data, encoding: .utf8) } else { print("Decryption failed with code: \(cryptStatus)") return nil } } }
另外,确保PHP端的加密逻辑和Swift对齐,比如PHP的加密示例:
function aesEncrypt($data, $key, $iv) { // 统一截取/补全到16字节 $key = substr($key, 0, 16); $iv = substr($iv, 0, 16); // 用RAW模式加密,再转Base64 $encrypted = openssl_encrypt($data, 'AES-128-CBC', $key, OPENSSL_RAW_DATA, $iv); return base64_encode($encrypted); }
内容的提问来源于stack exchange,提问作者techno

