使用CryptoSwift实现AES加解密遇问题及存储格式咨询
解决CryptoSwift加解密失败及密钥存储、Firebase存储格式问题
1. 修复解密函数还原明文的问题
你的解密流程存在两处核心错误,同时密钥生成逻辑有遗漏,导致无法还原明文:
错误点及修复:
- 密文解析错误:解密时直接使用
cipherText.bytes会将hex字符串的每个字符转为UInt8,而非解析hex对应的二进制数据,需改为[UInt8](hex: cipherText) - 结果转换错误:解密后的UInt8数组是明文的二进制数据,应转成UTF-8字符串,而非hex字符串
- Salt未保存:PKCS5.PBKDF2生成密钥时依赖salt,你当前每次生成随机salt但未保存,导致后续生成的密钥不一致,必须将salt与密钥绑定存储(比如存到钥匙串或Firebase用户文档)
修正后的加密解密及密钥生成代码:
class EncryptionService: EncryptionServicing { private var keychainService = KeychainService() // 修改为返回密钥和salt,需同时存储两者 func generateKey() throws -> (key: String, salt: String) { print("[EncryptionService] 🔑 Generate key called") do { if let userId = UserService.shared.userInfo?.userId { let userIdBytes: [UInt8] = Array(userId.utf8) // 生成随机salt(推荐长度至少8字节,增强安全性) let salt: [UInt8] = (1...8).map( {_ in UInt8.random(in: 0...255)} ) let key = try PKCS5.PBKDF2( password: userIdBytes, salt: salt, iterations: 4096, keyLength: 32, /* AES-256 */ variant: .sha2(.sha256) ).calculate() return (key.toHexString(), salt.toHexString()) } } catch { print(error) } throw NSError(domain: "EncryptionError", code: 0, userInfo: [NSLocalizedDescriptionKey: "Failed to generate key"]) } func encrypt(clearText: String, aesKey: String) throws -> (cipherText: String, iv: String) { print("[EncryptionService] ✅ Encrypt called") do { let iv = AES.randomIV(AES.blockSize) let keyArray = [UInt8](hex: aesKey) let aes = try AES(key: keyArray, blockMode: CBC(iv: iv), padding: .pkcs7) let cipherTextBytes = try aes.encrypt(Array(clearText.utf8)) // 改用Base64存储,节省空间 guard let cipherTextBase64 = cipherTextBytes.toBase64(), let ivBase64 = iv.toBase64() else { throw NSError(domain: "EncryptionError", code: 1, userInfo: [NSLocalizedDescriptionKey: "Failed to convert to Base64"]) } return (cipherTextBase64, ivBase64) } catch { print(error) throw error } } func decrypt(cipherText: String, iv: String, aesKey: String) throws -> String { print("[EncryptionService] ✅ Decryption called") do { print("Ciphertext: \(cipherText)") print("Iv: \(iv)") print("aesKey: \(aesKey)") let keyArray = [UInt8](hex: aesKey) // 解析Base64格式的IV和密文 guard let ivBytes = [UInt8](base64: iv), let cipherTextBytes = [UInt8](base64: cipherText) else { throw NSError(domain: "DecryptionError", code: 0, userInfo: [NSLocalizedDescriptionKey: "Invalid Base64 data"]) } let aes = try AES(key: keyArray, blockMode: CBC(iv: ivBytes), padding: .pkcs7) print("AES Key size: \(aes.keySize)") let decryptedBytes = try aes.decrypt(cipherTextBytes) // 将解密后的二进制转成UTF-8字符串 guard let decryptedText = String(bytes: decryptedBytes, encoding: .utf8) else { throw NSError(domain: "DecryptionError", code: 1, userInfo: [NSLocalizedDescriptionKey: "Failed to convert bytes to string"]) } return decryptedText } catch { print(error) throw error } } }
ViewModel调整:
需保存salt并在需要时重新生成密钥,或者直接存储密钥(推荐存储密钥二进制到钥匙串):
@Published var dummyClearText: String = "Hello World!" @Published var dummyCipherText: String = "" @Published var dummyIv: String = "" @Published var dummyDecryptedText: String = "" @Published var aesKey: String = "" @Published var salt: String = "" // 生成密钥并存储 func generateAndStoreKey() { do { let result = try EncryptionService().generateKey() self.aesKey = result.key self.salt = result.salt // 这里将key和salt存到钥匙串,示例为简化直接保存 // try keychainService.saveKey(data: Data(hex: result.key), service: "diary-key") // try keychainService.saveKey(data: Data(hex: result.salt), service: "diary-salt") } catch { print(error) } } func generateCipherText() { do { let result = try EncryptionService().encrypt(clearText: self.dummyClearText, aesKey: self.aesKey) self.dummyCipherText = result.cipherText self.dummyIv = result.iv } catch { print(error) } } func generateClearText() { do { let result = try EncryptionService().decrypt(cipherText: self.dummyCipherText, iv: self.dummyIv, aesKey: self.aesKey) self.dummyDecryptedText = result } catch { print(error) } }
2. 密钥存储到钥匙串的格式
- 优先存储二进制Data:不要存储hex或Base64字符串,直接将密钥的
[UInt8]转成Data(bytes: keyArray),存储到钥匙串。这样既节省空间,又避免字符串解析的潜在错误。 - 示例存储逻辑:
// 存储密钥 func saveKeyToKeychain(key: [UInt8], service: String) throws { let data = Data(bytes: key) // 调用钥匙串存储方法(需实现KeychainService的对应逻辑) try keychainService.save(data: data, service: service, account: "user-diary-key") } // 获取密钥 func getKeyFromKeychain(service: String) throws -> [UInt8] { let data = try keychainService.read(service: service, account: "user-diary-key") return [UInt8](data) } - 钥匙串本身是系统级安全存储,二进制存储比字符串更高效、可靠。
3. 密文和IV存储到Firebase的格式选择
- 优先使用Base64:Base64的空间利用率比Hex高约33%(相同二进制数据,Base64长度是Hex的2/3),能节省Firebase的存储资源和传输带宽。
- Hex格式适用场景:仅当你需要人工查看密文内容时选择Hex,否则一律用Base64。
- 代码中已调整为使用
.toBase64(),注意处理可选值(因为二进制转Base64可能失败,需添加错误处理)。
内容的提问来源于stack exchange,提问作者Siddhant Mehta
相关产品推荐
相关产品推荐

