There are a few keychain wrappers around but for simple needs, you can write it yourself
Here is a basic implementation. I use actor to go with async/await, and a struct KeychainError to contain status code in case we want to deal with error cases.
accessGroup is to define kSecAttrAccessGroup to share keychain across your apps
public actor Keychain {
public struct KeychainError: Error {
let status: OSStatus
}
let service: String
let accessGroup: String?
public init(
service: String,
accessGroup: String? = nil
) {
self.service = service
self.accessGroup = accessGroup
}
}
Since we need some common query parameters across few methods, I usually use helper method. We use kSecClassGenericPassword class so we set key to kSecAttrAccount
func baseQuery(key: String) -> [CFString: Any] {
var query: [CFString: Any] = [:]
query[kSecClass] = kSecClassGenericPassword
query[kSecAttrService] = service
query[kSecAttrAccount] = key
if let accessGroup {
query[kSecAttrAccessGroup] = accessGroup
}
return query
}
Below is how to get and set Data to keychain
func get(key: String) throws -> Data {
var query = baseQuery(key: key)
query[kSecMatchLimit] = kSecMatchLimitOne
query[kSecReturnAttributes] = kCFBooleanTrue
query[kSecReturnData] = kCFBooleanTrue
var obj: AnyObject?
let status = SecItemCopyMatching(query as CFDictionary, &obj)
if status == errSecSuccess,
let json = obj as? [CFString: AnyObject],
let data = json[kSecValueData] as? Data {
return data
} else {
throw KeychainError(status: status)
}
}
func set(key: String, data: Data) throws {
do {
_ = try get(key: key)
try update(key: key, data: data)
} catch let error as KeychainError {
if error.status == errSecItemNotFound {
try add(key: key, data: data)
}
}
}
func delete(key: String) throws {
let query = baseQuery(key: key)
let status = SecItemDelete(query as CFDictionary)
if status != errSecSuccess {
throw KeychainError(status: status)
}
}
private func update(key: String, data: Data) throws {
let query = baseQuery(key: key)
let updates: [CFString: Any] = [
kSecValueData: data
]
let status = SecItemUpdate(query as CFDictionary, updates as CFDictionary)
if status != errSecSuccess {
throw KeychainError(status: status)
}
}
private func add(key: String, data: Data) throws {
var query = baseQuery(key: key)
query[kSecValueData] = data
let status = SecItemAdd(query as CFDictionary, nil)
if status != errSecSuccess {
throw KeychainError(status: status)
}
}
If there is no error, then OSStatus will be errSecSuccess which has value 0
There are a few keychain wrappers around but for simple needs, you can write it yourself
Here is a basic implementation. I use actor to go with async/await, and a struct
KeychainError
to contain status code in case we want to deal with error cases.accessGroup
is to define kSecAttrAccessGroup to share keychain across your appsSince we need some common query parameters across few methods, I usually use helper method. We use
kSecClassGenericPassword
class so we set key tokSecAttrAccount
Below is how to get and set Data to keychain
If there is no error, then
OSStatus
will beerrSecSuccess
which has value 0There are some other query attributes like