Querying iOS Keychain Using Swift

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

Querying IOS Keychain with out account attribute?

Using the keychain is much simpler with KeychainAccess:

// Create keychain object
let keychain = Keychain(service: "com.company.AppName")

// Store value in keychain
keychain["password"] = "test"

// Retrieve value from keychain
let password = keychain["password"]

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)
}

Adding auth credentials to Keychain on iOS with Swift

Just for learning/teaching purposes, I will write how to use the keychain manually.. However, I'd consider using a library as the API is a fairly low level C-API..


First you need to create a function to check if the item exists already..
If it exists, you need to "update" the item instead. If it doesn't exist, you need to add it..

The following code (Swift 4.2) should do it.. There's also no need to store the data as a InternetPassword.. You can just store it as generic password.. but that part is entirely up to you.

I used generic password below and I simply store a Dictionary with the "user" as the key.

So the code:

static let keychainIdentifier = "com.myproject.keychain.identifier"

// Checks if an item exists in the keychain already..
func exists(key: String, completion: @escaping (_ error: OSStatus, _ query: [CFString: Any], _ result: [CFString: Any]?) -> Void) {
var query: [CFString: Any] = [
kSecClass: kSecClassGenericPassword,
kSecAttrGeneric: keychainIdentifier.data(using: .utf8)!
kSecMatchLimit: kSecMatchLimitOne,
kSecReturnAttributes: kCFBooleanTrue,
kSecAttrAccessible: kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
kSecAttrAccount: key
]

var result: CFTypeRef?
let error = SecItemCopyMatching(accountQuery as CFDictionary, &result)
completion(error, accountQuery, result as? [CFString: Any])
}

// Adds or Updates an item in the keychain..
func setItem(key: String, data: Data) throws {
exists(key: key) { error, query, _ in
var query = query
query[kSecMatchLimit] = nil
query[kSecReturnAttributes] = nil

if error == noErr || error == errSecDuplicateItem || error == errSecInteractionNotAllowed {
let updateQuery = [kSecValueData: data]
let err = SecItemUpdate(query as CFDictionary, updateQuery as CFDictionary)
if err != noErr {
throw KeychainError((code: err, message: "Cannot Update Item")
}
}
else if error == errSecItemNotFound {
query[kSecValueData] = data
let err = SecItemAdd(query as CFDictionary, nil)
if err != noErr {
throw KeychainError((code: err, message: "Cannot Set Item")
}
} else {
if err != noErr {
throw KeychainError(code: err, message: "Error Occurred")
}
}
}
}

// Convenience function to save JSON to the keychain
func setItem(key: String, data: Codable) throws {
let encodedData = try JSONEncoder().encode(data)
setItem(key: key, data: encodedData)
}

and the usage:

struct User: Codable {
let username: String
let token: String?
let otherInfo: String
}

let user = User(username: "Brandon", token: "...", otherInfo: "...")
setItem(key: user.username, data: user)

IMO, this makes it a lot easier to use.. but beware, you're not really supposed to store "A LOT" of data in the keychain such as an entire model or something (as generic password)..

Alternatively, if it's just the token, then:

setItem(key: user.username, data: "...".data(using: .utf8)!)

Keychain Query Always Returns errSecItemNotFound After Upgrading to iOS 13

I've had a similar issue where I was getting errSecItemNotFound with any Keychain-related action but only on a simulator. On real device it was perfect, I've tested with latest Xcodes (beta, GM, stable) on different simulators and the ones that were giving me a hard time were iOS 13 ones.

The problem was that I was using kSecClassKey in query attribute kSecClass, but without the 'required' values (see what classes go with which values here) for generating a primary key:

  • kSecAttrApplicationLabel
  • kSecAttrApplicationTag
  • kSecAttrKeyType
  • kSecAttrKeySizeInBits
  • kSecAttrEffectiveKeySize

And what helped was to pick kSecClassGenericPassword for kSecClass and provide the 'required' values for generating a primary key:

  • kSecAttrAccount
  • kSecAttrService

See here on more about kSecClass types and what other attributes should go with them.

I came to this conclusion by starting a new iOS 13 project and copying over the Keychain wrapper that was used in our app, as expected that did not work so I've found this lovely guide on using keychain here and tried out their wrapper which no surprise worked, and then went line by line comparing my implementation with theirs.

This issue already reported in radar: http://openradar.appspot.com/7251207

Hope this helps.

Simple way to store and read a password in apple keychain?

You are storing password as a string inside keychain. I will modify storeKeychain() method

private func storeKeychain(username: String, password: String) throws -> Any? {
let credentials = Credentials.init(username: username, password: password)
let data = credentials.password.data(using: .utf8)!

// store password as data and if you want to store username
let query: [String: Any] = [kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: username,
kSecValueData as String: data]
let status = SecItemAdd(query as CFDictionary, nil)
guard status == errSecSuccess else {
throw KeychainError.unhandledError(status: status) }
return status
}

Check if it works.

Swift 4: kSecMatchIssuers keychain search query failing to match X.509 certificate

ASN.1 DER Encoded

The issuer names must be provided in ASN.1 DER encoded format.

Be aware that kSecMatchIssuers is only works since macOS 10.13.



Related Topics



Leave a reply



Submit