Save and Load from KeyChain | Swift
##Simplest Source##
import Foundation
import Security
// Constant Identifiers
let userAccount = "AuthenticatedUser"
let accessGroup = "SecuritySerivice"
/**
* User defined keys for new entry
* Note: add new keys for new secure item and use them in load and save methods
*/
let passwordKey = "KeyForPassword"
// Arguments for the keychain queries
let kSecClassValue = NSString(format: kSecClass)
let kSecAttrAccountValue = NSString(format: kSecAttrAccount)
let kSecValueDataValue = NSString(format: kSecValueData)
let kSecClassGenericPasswordValue = NSString(format: kSecClassGenericPassword)
let kSecAttrServiceValue = NSString(format: kSecAttrService)
let kSecMatchLimitValue = NSString(format: kSecMatchLimit)
let kSecReturnDataValue = NSString(format: kSecReturnData)
let kSecMatchLimitOneValue = NSString(format: kSecMatchLimitOne)
public class KeychainService: NSObject {
/**
* Exposed methods to perform save and load queries.
*/
public class func savePassword(token: NSString) {
self.save(passwordKey, data: token)
}
public class func loadPassword() -> NSString? {
return self.load(passwordKey)
}
/**
* Internal methods for querying the keychain.
*/
private class func save(service: NSString, data: NSString) {
let dataFromString: NSData = data.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!
// Instantiate a new default keychain query
let keychainQuery: NSMutableDictionary = NSMutableDictionary(objects: [kSecClassGenericPasswordValue, service, userAccount, dataFromString], forKeys: [kSecClassValue, kSecAttrServiceValue, kSecAttrAccountValue, kSecValueDataValue])
// Delete any existing items
SecItemDelete(keychainQuery as CFDictionaryRef)
// Add the new keychain item
SecItemAdd(keychainQuery as CFDictionaryRef, nil)
}
private class func load(service: NSString) -> NSString? {
// Instantiate a new default keychain query
// Tell the query to return a result
// Limit our results to one item
let keychainQuery: NSMutableDictionary = NSMutableDictionary(objects: [kSecClassGenericPasswordValue, service, userAccount, kCFBooleanTrue, kSecMatchLimitOneValue], forKeys: [kSecClassValue, kSecAttrServiceValue, kSecAttrAccountValue, kSecReturnDataValue, kSecMatchLimitValue])
var dataTypeRef :AnyObject?
// Search for the keychain items
let status: OSStatus = SecItemCopyMatching(keychainQuery, &dataTypeRef)
var contentsOfKeychain: NSString? = nil
if status == errSecSuccess {
if let retrievedData = dataTypeRef as? NSData {
contentsOfKeychain = NSString(data: retrievedData, encoding: NSUTF8StringEncoding)
}
} else {
print("Nothing was retrieved from the keychain. Status code \(status)")
}
return contentsOfKeychain
}
}
##Example of Calling##
KeychainService.savePassword("Pa55worD")
let password = KeychainService.loadPassword() // password = "Pa55worD"
##SWIFT 4: VERSION WITH UPDATE AND REMOVE PASSWORD
import Cocoa
import Security
// see https://stackoverflow.com/a/37539998/1694526
// Arguments for the keychain queries
let kSecClassValue = NSString(format: kSecClass)
let kSecAttrAccountValue = NSString(format: kSecAttrAccount)
let kSecValueDataValue = NSString(format: kSecValueData)
let kSecClassGenericPasswordValue = NSString(format: kSecClassGenericPassword)
let kSecAttrServiceValue = NSString(format: kSecAttrService)
let kSecMatchLimitValue = NSString(format: kSecMatchLimit)
let kSecReturnDataValue = NSString(format: kSecReturnData)
let kSecMatchLimitOneValue = NSString(format: kSecMatchLimitOne)
public class KeychainService: NSObject {
class func updatePassword(service: String, account:String, data: String) {
if let dataFromString: Data = data.data(using: String.Encoding.utf8, allowLossyConversion: false) {
// Instantiate a new default keychain query
let keychainQuery: NSMutableDictionary = NSMutableDictionary(objects: [kSecClassGenericPasswordValue, service, account], forKeys: [kSecClassValue, kSecAttrServiceValue, kSecAttrAccountValue])
let status = SecItemUpdate(keychainQuery as CFDictionary, [kSecValueDataValue:dataFromString] as CFDictionary)
if (status != errSecSuccess) {
if let err = SecCopyErrorMessageString(status, nil) {
print("Read failed: \(err)")
}
}
}
}
class func removePassword(service: String, account:String) {
// Instantiate a new default keychain query
let keychainQuery: NSMutableDictionary = NSMutableDictionary(objects: [kSecClassGenericPasswordValue, service, account, kCFBooleanTrue], forKeys: [kSecClassValue, kSecAttrServiceValue, kSecAttrAccountValue, kSecReturnDataValue])
// Delete any existing items
let status = SecItemDelete(keychainQuery as CFDictionary)
if (status != errSecSuccess) {
if let err = SecCopyErrorMessageString(status, nil) {
print("Remove failed: \(err)")
}
}
}
class func savePassword(service: String, account:String, data: String) {
if let dataFromString = data.data(using: String.Encoding.utf8, allowLossyConversion: false) {
// Instantiate a new default keychain query
let keychainQuery: NSMutableDictionary = NSMutableDictionary(objects: [kSecClassGenericPasswordValue, service, account, dataFromString], forKeys: [kSecClassValue, kSecAttrServiceValue, kSecAttrAccountValue, kSecValueDataValue])
// Add the new keychain item
let status = SecItemAdd(keychainQuery as CFDictionary, nil)
if (status != errSecSuccess) { // Always check the status
if let err = SecCopyErrorMessageString(status, nil) {
print("Write failed: \(err)")
}
}
}
}
class func loadPassword(service: String, account:String) -> String? {
// Instantiate a new default keychain query
// Tell the query to return a result
// Limit our results to one item
let keychainQuery: NSMutableDictionary = NSMutableDictionary(objects: [kSecClassGenericPasswordValue, service, account, kCFBooleanTrue, kSecMatchLimitOneValue], forKeys: [kSecClassValue, kSecAttrServiceValue, kSecAttrAccountValue, kSecReturnDataValue, kSecMatchLimitValue])
var dataTypeRef :AnyObject?
// Search for the keychain items
let status: OSStatus = SecItemCopyMatching(keychainQuery, &dataTypeRef)
var contentsOfKeychain: String?
if status == errSecSuccess {
if let retrievedData = dataTypeRef as? Data {
contentsOfKeychain = String(data: retrievedData, encoding: String.Encoding.utf8)
}
} else {
print("Nothing was retrieved from the keychain. Status code \(status)")
}
return contentsOfKeychain
}
}
You need to imagine the following wired up to a text input field and a label, then having four buttons wired up, one for each of the methods.
class ViewController: NSViewController {
@IBOutlet weak var enterPassword: NSTextField!
@IBOutlet weak var retrievedPassword: NSTextField!
let service = "myService"
let account = "myAccount"
// will only work after
@IBAction func updatePassword(_ sender: Any) {
KeychainService.updatePassword(service: service, account: account, data: enterPassword.stringValue)
}
@IBAction func removePassword(_ sender: Any) {
KeychainService.removePassword(service: service, account: account)
}
@IBAction func passwordSet(_ sender: Any) {
let password = enterPassword.stringValue
KeychainService.savePassword(service: service, account: account, data: password)
}
@IBAction func passwordGet(_ sender: Any) {
if let str = KeychainService.loadPassword(service: service, account: account) {
retrievedPassword.stringValue = str
}
else {retrievedPassword.stringValue = "Password does not exist" }
}
}
##Swift 5##
Kosuke's version for swift 5
import Security
import UIKit
class KeyChain {
class func save(key: String, data: Data) -> OSStatus {
let query = [
kSecClass as String : kSecClassGenericPassword as String,
kSecAttrAccount as String : key,
kSecValueData as String : data ] as [String : Any]
SecItemDelete(query as CFDictionary)
return SecItemAdd(query as CFDictionary, nil)
}
class func load(key: String) -> Data? {
let query = [
kSecClass as String : kSecClassGenericPassword,
kSecAttrAccount as String : key,
kSecReturnData as String : kCFBooleanTrue!,
kSecMatchLimit as String : kSecMatchLimitOne ] as [String : Any]
var dataTypeRef: AnyObject? = nil
let status: OSStatus = SecItemCopyMatching(query as CFDictionary, &dataTypeRef)
if status == noErr {
return dataTypeRef as! Data?
} else {
return nil
}
}
class func createUniqueID() -> String {
let uuid: CFUUID = CFUUIDCreate(nil)
let cfStr: CFString = CFUUIDCreateString(nil, uuid)
let swiftString: String = cfStr as String
return swiftString
}
}
extension Data {
init<T>(from value: T) {
var value = value
self.init(buffer: UnsafeBufferPointer(start: &value, count: 1))
}
func to<T>(type: T.Type) -> T {
return self.withUnsafeBytes { $0.load(as: T.self) }
}
}
Example usage:
let int: Int = 555
let data = Data(from: int)
let status = KeyChain.save(key: "MyNumber", data: data)
print("status: ", status)
if let receivedData = KeyChain.load(key: "MyNumber") {
let result = receivedData.to(type: Int.self)
print("result: ", result)
}
Save and retrieve value via KeyChain
Well, I just used out source etc and made my self nice helper :
Enjoy!
class func save(key: String, data: NSData) {
let query = [
kSecClass as String : kSecClassGenericPassword as String,
kSecAttrAccount as String : key,
kSecValueData as String : data ]
SecItemDelete(query as CFDictionaryRef)
let status: OSStatus = SecItemAdd(query as CFDictionaryRef, nil)
}
class func load(key: String) -> NSData? {
let query = [
kSecClass as String : kSecClassGenericPassword,
kSecAttrAccount as String : key,
kSecReturnData as String : kCFBooleanTrue,
kSecMatchLimit as String : kSecMatchLimitOne ]
var dataTypeRef :Unmanaged<AnyObject>?
let status: OSStatus = SecItemCopyMatching(query, &dataTypeRef)
if status == noErr {
return (dataTypeRef!.takeRetainedValue() as! NSData)
} else {
return nil
}
}
class func stringToNSDATA(string : String)->NSData
{
let _Data = (string as NSString).dataUsingEncoding(NSUTF8StringEncoding)
return _Data!
}
class func NSDATAtoString(data: NSData)->String
{
var returned_string : String = NSString(data: data, encoding: NSUTF8StringEncoding)! as String
return returned_string
}
class func intToNSDATA(r_Integer : Int)->NSData
{
var SavedInt: Int = r_Integer
let _Data = NSData(bytes: &SavedInt, length: sizeof(Int))
return _Data
}
class func NSDATAtoInteger(_Data : NSData) -> Int
{
var RecievedValue : Int = 0
_Data.getBytes(&RecievedValue, length: sizeof(Int))
return RecievedValue
}
class func CreateUniqueID() -> String
{
var uuid: CFUUIDRef = CFUUIDCreate(nil)
var cfStr:CFString = CFUUIDCreateString(nil, uuid)
var nsTypeString = cfStr as NSString
var swiftString:String = nsTypeString as String
return swiftString
}
//EXAMPLES
//
// //Save And Parse Int
// var Int_Data = KeyChain.intToNSDATA(555)
// KeyChain.save("MAMA", data: Int_Data)
// var RecievedDataAfterSave = KeyChain.load("MAMA")
// var NSDataTooInt = KeyChain.NSDATAtoInteger(RecievedDataAfterSave!)
// println(NSDataTooInt)
//
//
// //Save And Parse String
// var string_Data = KeyChain.stringToNSDATA("MANIAK")
// KeyChain.save("ZAHAL", data: string_Data)
// var RecievedDataStringAfterSave = KeyChain.load("ZAHAL")
// var NSDATAtoString = KeyChain.NSDATAtoString(RecievedDataStringAfterSave!)
// println(NSDATAtoString)
Store & Load [Uint8] to & from Keychain (Swift)
You're overcomplicating things. Your issue lies here:
let result = receivedData.to(type: Int.self)
You were storing an [UInt8]
type that you're now converting to an Int
type.
You can get your array back by doing:
let result = [UInt8](receivedData)
How to load and save data? - Swift
UserDefaults
is a common tool to use for this. It stores persistent data in a key-value store.
You could use it like this:
class Favorites: ObservableObject {
private var task: Set<Int>
private let saveKey = "Favorites"
init() {
self.task = Set((UserDefaults.standard.array(forKey: saveKey) as? [Int]) ?? [])
}
func contains(_ sclocations: SClocation) -> Bool {
task.contains(sclocations.id)
}
func add(_ sclocations: SClocation) -> Bool {
objectWillChange.send()
task.insert(sclocations.id)
save()
return true
}
func remove(_ sclocations: SClocation) -> Bool {
objectWillChange.send()
task.remove(sclocations.id)
save()
return true
}
func save() {
// Write out Data
UserDefaults.standard.setValue(Array(task), forKey: saveKey)
}
}
I'd also probably refactor your code a little bit. Using didSet
on a @Published
variable seems cleaner. Also, you have unnecessary Bool
returns on your add/remove
functions.
class Favorites: ObservableObject {
@Published var task: Set<Int> {
didSet {
save()
}
}
private let saveKey = "Favorites"
init() {
self.task = Set((UserDefaults.standard.array(forKey: saveKey) as? [Int]) ?? [])
}
func contains(_ sclocations: SClocation) -> Bool {
task.contains(sclocations.id)
}
func add(_ location: SClocation) {
task.insert(location.id)
}
func remove(_ location: SClocation) {
task.remove(location.id)
}
func save() {
UserDefaults.standard.setValue(Array(task), forKey: saveKey)
}
}
You could even get rid of the add/remove
functions above and just call insert
and remove
directly on the task
@Published
property.
Another option besides using UserDefaults
would be to look into Core Data (https://developer.apple.com/documentation/coredata) -- there are plenty of tutorials around for that -- it would be a longer and more involved answer for how to integrate this, though.
Adding Items to and Querying the iOS Keychain with Swift
In order to get this to work, you will need to retrieve the retained values of the keychain constants and store then first like so:
let kSecClassValue = kSecClass.takeRetainedValue() as NSString
let kSecAttrAccountValue = kSecAttrAccount.takeRetainedValue() as NSString
let kSecValueDataValue = kSecValueData.takeRetainedValue() as NSString
let kSecClassGenericPasswordValue = kSecClassGenericPassword.takeRetainedValue() as NSString
let kSecAttrServiceValue = kSecAttrService.takeRetainedValue() as NSString
let kSecMatchLimitValue = kSecMatchLimit.takeRetainedValue() as NSString
let kSecReturnDataValue = kSecReturnData.takeRetainedValue() as NSString
let kSecMatchLimitOneValue = kSecMatchLimitOne.takeRetainedValue() as NSString
You can then reference the values in the NSMutableDictionary like so:
var keychainQuery: NSMutableDictionary = NSMutableDictionary(
objects: [kSecClassGenericPasswordValue, service, userAccount, kCFBooleanTrue, kSecMatchLimitOneValue],
forKeys: [kSecClassValue, kSecAttrServiceValue, kSecAttrAccountValue, kSecReturnDataValue, kSecMatchLimitValue]
)
I wrote a blog post about it at:
http://rshelby.com/2014/08/using-swift-to-save-and-query-ios-keychain-in-xcode-beta-4/
Hope this helps!
rshelby
Swift 3 How to save and load data
Best way to save scores is by using NSUserDefaults
To save settings such as volume, you can follow the model I have below.
//When you tap on the mute button, execute this code.
if soundSwitch { //true
UserDefaults.standard.set(false, forKey: "SoundSwitch")
soundSwitch = false
} else { //false
UserDefaults.standard.set(true, forKey: "SoundSwitch")
soundSwitch = true
}
If you are saving players money, name and all this other stuff, it would be better to save them in some kind of database. I'm not sure if you can do that with GameCenter.
Related Topics
Looping Through Nsattributedstring Attributes to Increase Font Size
Test Whether a Uiview Is in the Middle of Animation
Xcode - Bundle Format Unrecognized, Invalid, or Unsuitable
Pod Install Is Staying on "Setting Up Cocoapods Master Repo"
Best Way to Parse Url String to Get Values for Keys
Localizing Strings in iOS: Default (Fallback) Language
Programmatically Switching Between Tabs Within Swift
How to Detect Apps First Launch in iOS
Raw Image Data from Camera Like "645 Pro"
Open Uitableview Edit Action Buttons Programmatically
Swift - Get Local Date and Time
What Is Kcferrordomaincfnetwork Code=303
What's the Uiscrollview Contentinset Property For
Why Is Uicollectionviewcell's Outlet Nil
Uicollectionview Decoration View
How to Run the iOS 7.1 Simulator in Xcode 7.0 Beta 2
Crashlytics in iOS Won't Proceed Past "Build Your Project" in Fabric App