iOS KeyChain数据持久化操作详解
针对iOS敏感信息本地存储安全问题,使用KeyChain服务加密存储密码、密钥等敏感数据。通过构建单例辅助类,利用SecItemAdd、SecItemUpdate、SecItemCopyMatching和SecItemDelete实现增删改查,并扩展支持Codable类型直接存取,简化操作流程,提升数据安全性与开发效率。
在iOS开发中,敏感信息的本地存储一直是个让人头疼的问题——密码、accessToken、secretKey,随便一个泄露都可能引发安全事故。很多初级开发者下意识会选择UserDefaults,但说实话,用UserDefaults存敏感数据,几乎等于把密码写在便利贴上贴在屏幕边。数据未经编码直接暴露,安全隐患不言而喻。
真正靠谱的做法,是使用苹果提供的KeyChain服务。这个框架虽然有些年头了,API风格也不像当下的框架那么简洁顺手,但它的安全性是经过长期验证的。掌握iOS Keychain安全存储技巧,是每个Swift开发者必备的能力。
本文就来手把手教你创建一个同时适用于iOS和macOS的KeyChain辅助类,涵盖增删改查全套操作。准备好了吗?直接开干!
保存数据到KeyChain
先搭建一个单例辅助类:
final class KeyChainHelper {
static let standard = KeyChainHelper()
private init(){}
}
核心保存操作依赖SecItemAdd(_:_:)方法,它接收一个CFDictionary类型的query对象。思路很简单:构建一个包含待存储数据键值对的query,然后交给SecItemAdd执行。
func sa ve(_ data: Data, service: String, account: String) {
// Create query
let query = [
kSecValueData: data,
kSecClass: kSecClassGenericPassword,
kSecAttrService: service,
kSecAttrAccount: account,
] as CFDictionary
// Add data in query to keychain
let status = SecItemAdd(query, nil)
if status != errSecSuccess {
// Print out the error
print("Error: (status)")
}
}
这个query对象由四个关键键值对组成:
kSecValueData:代表实际要存储的数据kSecClass:这里设为kSecClassGenericPassword,表示存储的是一个通用密码项kSecAttrService和kSecAttrAccount:当kSecClass为通用密码项时,这两个键是必填的。它们组合起来相当于数据的“主键”,后续读取时也要用同样的组合来查找。推荐直接用字符串,比如存Facebook的accessToken时,可以把service设为"access-token",account设为"facebook"。
创建完query后,调用SecItemAdd。返回的OSStatus如果等于errSecSuccess,说明保存成功。使用方法如下:
let accessToken = "dummy-access-token" let data = Data(accessToken.utf8) KeychainHelper.standard.sa ve(data, service: "access-token", account: "facebook")
注意:KeyChain不能在playground中运行,所以上述代码要写在Controller里才行。
更新KeyChain中已有的数据
现在试试用相同的service和account存另一条token:
let accessToken = "another-dummy-access-token" let data = Data(accessToken.utf8) KeychainHelper.standard.sa ve(data, service: "access-token", account: "facebook")
你会遇到一个报错Error: -25299,对应的常量是errSecDuplicateItem。因为相同的key组合已经存在,所以直接添加会失败。解决办法很简单:检测到这个错误码后,改用SecItemUpdate(_:_:)来更新现有条目。我们改造一下sa ve方法:
func sa ve(_ data: Data, service: String, account: String) {
// ... ...
// ... ...
if status == errSecDuplicateItem {
// Item already exist, thus update it.
let query = [
kSecAttrService: service,
kSecAttrAccount: account,
kSecClass: kSecClassGenericPassword,
] as CFDictionary
let attributesToUpdate = [kSecValueData: data] as CFDictionary
// Update existing item
SecItemUpdate(query, attributesToUpdate)
}
}
更新操作和保存类似:先构建一个包含service和account的query,再另建一个只包含kSecValueData的字典,传给SecItemUpdate。这样一来,sa ve方法就同时具备了新增和更新的能力,既存则改,未存则加。这是实现KeyChain增删改查的基础逻辑。
从KeyChain中读取数据
读取的思路和保存几乎对称:同样先构建query,然后调用对应的读取方法:
func read(service: String, account: String) -> Data? {
let query = [
kSecAttrService: service,
kSecAttrAccount: account,
kSecClass: kSecClassGenericPassword,
kSecReturnData: true
] as CFDictionary
var result: AnyObject?
SecItemCopyMatching(query, &result)
return (result as? Data)
}
注意这里新增了一个kSecReturnData键,值为true,意思是我们希望KeyChain把匹配项的数据返回回来。然后通过SecItemCopyMatching方法,将结果通过引用赋给result。虽然这个方法也会返回一个OSStatus,但为简洁起见,这里省略了错误处理——如果读取失败,直接返回nil。
用法如下:
let data = KeychainHelper.standard.read(service: "access-token", account: "facebook")! let accessToken = String(data: data, encoding: .utf8)! print(accessToken)
从KeyChain中删除数据
没有删除功能的工具类是不完整的。删除比保存和读取更简单:
func delete(service: String, account: String) {
let query = [
kSecAttrService: service,
kSecAttrAccount: account,
kSecClass: kSecClassGenericPassword,
] as CFDictionary
// Delete item from keychain
SecItemDelete(query)
}
看到这段代码你应该很眼熟了——还是那个熟悉的query配方,然后直接调用SecItemDelete。整个流程非常自解释。通过这个KeyChain辅助类,你可以轻松管理iOS敏感数据存储。
创建一个通用的KeyChainHelper类
上面实现的几个方法只支持Data类型,实际开发中我们经常需要存储对象。如果能直接保存Codable类型就更方便了。下面是升级后的通用版本,让KeyChain支持任意Codable类型:
存储
func sa ve(_ item: T, service: String, account: String) where T : Codable { do { let data = try JSONEncoder().encode(item) sa ve(data, service: service, account: account) } catch { assertionFailure("Fail to encode item for keychain: (error)") } }
读取
func read(service: String, account: String, type: T.Type) -> T? where T : Codable { guard let data = read(service: service, account: account) else { return nil } do { let item = try JSONDecoder().decode(type, from: data) return item } catch { assertionFailure("Fail to decode item for keychain: (error)") return nil } }
实际使用
struct Auth: Codable {
let accessToken: String
let refreshToken: String
}
let auth = Auth(accessToken: "dummy-access-token",
refreshToken: "dummy-refresh-token")
let account = "domain.com"
let service = "token"
// 保存
KeychainHelper.standard.sa ve(auth, service: service, account: account)
// 读取
let result = KeychainHelper.standard.read(service: service,
account: account,
type: Auth.self)!
print(result.accessToken) // Output: "dummy-access-token"
print(result.refreshToken) // Output: "dummy-refresh-token"
这样一来,任何满足Codable的结构体或类都可以直接存入KeyChain,而无需手动做Data转换。安全存储敏感信息,这一套组合拳算是彻底拿下了。如果你正在寻找Swift KeyChain教程或iOS Keychain安全存储的最佳实践,这个方案能直接应用到项目中。
游乐网为非赢利性网站,所展示的游戏/软件/文章内容均来自于互联网或第三方用户上传分享,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系youleyoucom@outlook.com。
同类文章
AWS RDS 数据库配置入门与基础操作指南
本文介绍了AWSRDS的基本概念与核心价值,即提供托管式关系数据库服务,简化运维。详细阐述了创建RDS实例的关键配置步骤,包括引擎选择、实例规格、存储与网络设置。最后,指导读者如何通过多种方式安全连接至数据库实例,并开始进行数据操作,为后续应用开发奠定基础。
PHP MVC中AJAX请求无法调用控制器方法的原因与解决方案
PHPMVC中AJAX请求返回整页HTML的常见原因是控制器方法未正确输出响应或未终止执行,导致框架渲染视图。解决方法是在控制器中设置JSON响应头、输出数据后调用exit()明确终止,同时前端使用小写url和dataType: "json "。
Go语言手动构造rsa.PublicKey:正确初始化大整数模数N完整指南
手动构造RSA公钥时,模数N为*big Int类型,不能直接使用超长十进制字面量,需通过SetString或UnmarshalText方法解析字符串。公钥指数E可直接赋值,推荐65537。生产环境应使用rsa GenerateKey生成密钥对,避免手动构造引发的安全和格式错误。
Go语言实现HTTP定时轮询监控多URL响应时间与状态检测
使用Go语言实现HTTP定时轮询监控,通过按行分割与Tab解析URL列表,避免闭包陷阱和nil指针,每个URL启动独立ticker安全并发请求,并配置超时控制与资源关闭,确保响应时间与状态码准确检测。
Tkinter中Label标签在主循环动态更新的正确方法
在Tkinter中正确动态更新标签的方法:将标签组件的textvariable参数绑定到一个StringVar变量,然后通过调用该变量的 set()方法更新其值,界面会自动刷新。这样避免直接修改text属性或调用update()。此做法实现数据与界面的解耦,代码更简洁,响应更及时,避免手动同步的闪烁,推荐做法。
- 热门数据榜
相关攻略
2026-07-10 06:41
2026-07-10 06:40
2026-07-10 06:39
2026-07-10 06:39
2026-07-10 06:39
2026-07-10 06:39
2026-07-10 06:39
2026-07-10 06:39
热门教程
- 游戏攻略
- 安卓教程
- 苹果教程
- 电脑教程

