Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Save and Load from KeyChain | Swift [duplicate]

How to simply store a String in Keychain and load when needed. There are several SO solution which mostly refers to Git repo. But I need the smallest and the simplest solution on latest Swift. Certainly, I don't want to add git framework for simply storing a password in my project.

There are similar solution Save and retrieve value via KeyChain , which did not work for me. Tired with compiler errors.

like image 784
Sazzad Hissain Khan Avatar asked May 31 '16 07:05

Sazzad Hissain Khan


People also ask

What is SecItemCopyMatching?

SecItemCopyMatching(_:_:) Returns one or more keychain items that match a search query, or copies attributes of specific keychain items.

What is keychain in iOS Swift?

One of the most important security elements for Apple developers is the iOS Keychain, a specialized database for storing metadata and sensitive information. Using Keychain is the best way to store small pieces of data that are critical to your app, like secrets and passwords.

What is keychain on an iPhone?

With iCloud Keychain, you can keep your passwords and other secure information updated across your devices. iCloud Keychain remembers things, so that you don't have to. It auto-fills your information—like your Safari usernames and passwords, credit cards, and Wi-Fi passwords on any device that you approve.


1 Answers

##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) } 
like image 180
Sazzad Hissain Khan Avatar answered Sep 28 '22 08:09

Sazzad Hissain Khan