iOS端删除重建对称密钥后,ChaChaPoly密封盒解密失败求助
解决ChaChaPoly密钥重建不一致导致的解密认证失败问题
嘿,我来帮你排查这个问题!你遇到的authenticationFailure错误本质上是因为重新登录后生成的密钥和之前的完全不一样,导致ChaChaPoly无法验证加密数据的完整性(毕竟ChaChaPoly的seal操作会附带一个认证标签,密钥不对的话标签验证直接失败)。
先看你当前的密钥生成逻辑,这里有个明显的问题:你提到“先对密码进行SHA256哈希,提取摘要的数据表示后,用SymmetricKey(data: password)生成密钥”——这里的password如果是原密码的Data,那你等于白做了SHA256哈希!这正是密钥不一致的根源。
问题分析
ChaChaPoly要求密钥必须是**256位(32字节)**的长度。如果直接用原密码的Data生成密钥:
- 如果原密码长度不足32字节,
SymmetricKey(data:)不会自动填充,反而可能直接使用这个短数据作为密钥(但ChaChaPoly不接受非32字节的密钥,这时候其实你的密钥生成已经有问题了,只是你用了try?没捕获错误); - 即使密码长度刚好32字节,直接用原密码生成密钥也不安全,而且如果密码转Data时编码不一致(比如某次用UTF-8,某次用UTF-16),也会导致密钥不同。
而你做的SHA256哈希正好能生成32字节的固定长度摘要,这才是生成合规ChaChaPoly密钥的正确原料,但你却没用到它!
正确的密钥生成流程
重新梳理密钥生成逻辑,确保每次登录都能从相同密码生成完全一致的密钥:
import CryptoKit // 1. 将密码转为统一编码的Data(必须始终用同一种编码,比如UTF-8) guard let passwordData = passwordString.data(using: .utf8) else { fatalError("密码转Data失败") } // 2. 计算SHA256哈希,得到32字节的摘要 let passwordHash = SHA256.hash(data: passwordData) let hashData = Data(passwordHash) // 3. 用哈希后的Data生成ChaChaPoly合规的256位密钥 let symmetricKey = SymmetricKey(data: hashData)
确保密钥存储/读取的正确性
你提到把密钥存在钥匙串里,注意SymmetricKey不能直接存储,需要先转换成原始字节Data,读取时再还原:
存储密钥到钥匙串
func saveKey(_ key: SymmetricKey, account: String) throws { // 提取密钥的原始字节 let keyData = key.withUnsafeBytes { Data(Array($0)) } // 钥匙串存储逻辑(这里用示例代码,你可以替换成自己的钥匙串封装) let query: [String: Any] = [ kSecClass as String: kSecClassGenericPassword, kSecAttrAccount as String: account, kSecValueData as String: keyData, kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlockedThisDeviceOnly ] // 删除旧密钥(如果存在) SecItemDelete(query as CFDictionary) // 添加新密钥 let status = SecItemAdd(query as CFDictionary, nil) guard status == errSecSuccess else { throw NSError(domain: "KeychainError", code: Int(status), userInfo: nil) } }
从钥匙串读取密钥
func readKey(account: String) throws -> SymmetricKey? { let query: [String: Any] = [ kSecClass as String: kSecClassGenericPassword, kSecAttrAccount as String: account, kSecReturnData as String: kCFBooleanTrue!, kSecMatchLimit as String: kSecMatchLimitOne ] var data: AnyObject? let status = SecItemCopyMatching(query as CFDictionary, &data) guard status == errSecSuccess, let keyData = data as? Data else { return nil } // 验证数据长度必须是32字节(ChaChaPoly密钥要求) guard keyData.count == 32 else { throw CryptoKitError.incorrectKeySize } return SymmetricKey(data: keyData) }
修正加密/解密代码(去掉try!,改用安全的错误处理)
不要用try!强制解包,否则遇到错误直接崩溃,改用do-catch捕获错误,方便调试:
加密代码
func encrypt(rawData: Data, account: String) throws -> Data { guard let key = try readKey(account: account) else { throw NSError(domain: "EncryptionError", code: 0, userInfo: ["message": "密钥不存在"]) } let sealedBox = try ChaChaPoly.seal(rawData, using: key) return sealedBox.combined }
解密代码
func decrypt(encryptedData: Data, account: String) throws -> Data { guard let key = try readKey(account: account) else { throw NSError(domain: "DecryptionError", code: 0, userInfo: ["message": "密钥不存在"]) } let sealedBox = try ChaChaPoly.SealedBox(combined: encryptedData) return try ChaChaPoly.open(sealedBox, using: key) }
额外注意事项
- 密码编码一致性:每次把密码转Data时必须用同一种编码(比如
utf8),不能随意切换,否则哈希结果会不同; - 避免密码哈希的重复操作:不要在生成密钥时多次哈希密码,确保每次都是“原密码→SHA256→密钥”的流程;
- 钥匙串存储的访问权限:根据你的应用需求设置合适的
kSecAttrAccessible属性,确保密钥的安全性。
按照这个流程修改后,重新登录时用相同密码生成的密钥就会和之前完全一致,解密认证失败的问题应该就能解决了!
内容的提问来源于stack exchange,提问作者Sir Sirae
相关产品推荐
相关产品推荐

