Save and retrieve value via KeyChain
Asked Answered
C

7

32

I'm trying to store an Integer and retrieve it using KeyChain.

This is how I save it:

func SaveNumberOfImagesTaken()
    {
        let key = "IMAGE_TAKEN"
        var taken = 10
        let data = NSKeyedArchiver.archivedDataWithRootObject(taken)
        let query : [String:AnyObject] = [
            kSecClass as String : kSecClassGenericPassword,
            kSecAttrAccount as String : key,
            kSecValueData as String : data
        ]
        let status : OSStatus = SecItemAdd(query as CFDictionaryRef, nil)

    }

This is how I try to retrieve it:

func CheckIfKeyChainValueExitss() -> AnyObject? {
    var key = "IMAGE_TAKEN"
    let query : [String:AnyObject] = [
        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 let op = dataTypeRef?.toOpaque() {
        let data = Unmanaged<NSData>.fromOpaque(op).takeUnretainedValue()
        if let string: AnyObject? =  NSKeyedUnarchiver.unarchiveObjectWithData(data) as? AnyObject? {
            if key == "IMAGE_TAKEN"
            {
                return string as! String!

            }
            else if string == nil
            {
                return nil
            }
        }
    }
    return nil

}

I'm getting the following error:

Could not cast value of type '__NSCFNumber' to 'NSString'

I tried playing with the variables but without success.

Carbine answered 8/6, 2015 at 21:46 Comment(3)
So am im saving it wrong or retrieve it wrong? Im confused lol @PannierCarbine
This drop-in keychain wrapper might give you some ideas… github.com/ashleymills/Keychain.swiftJoub
@AshleyMills see my answer belowCarbine
C
6

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)
Carbine answered 12/6, 2015 at 19:20 Comment(0)
C
65

I've update Eric's version for Swift 5:

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

I've update Eric's version for Swift 3:

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.pointee }
    }
}

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)
}
Chagres answered 21/2, 2017 at 7:11 Comment(13)
Getting an error using this to store/retrieve a string value. No errors in saving the string, but receiving it, the receivedData isn't nil (at least, it has a .count of 24), but the receivedData.to(type: String.self) is coming back as nil. Any thoughts?Shiite
@Shiite I can't reproduce the error. Can you post your code?Chagres
With this code i've an error at this line return self.withUnsafeBytes { $0.pointee }. The crash report says KERN_INVALID_ADDRESS. But this error occurs only into iPhone 5s with iOS 10.3.3. Iphone 6s + 10.2 and iPad + 11.2 it's all ok! Some ideas?Scamper
@KosukeOgawa how to store string and other data typesClaudetta
Getting the warning ''withUnsafeBytes' is deprecated: use withUnsafeBytes<R>(_: (UnsafeRawBufferPointer) throws -> R) rethrows -> R instead' for line 'return self.withUnsafeBytes { $0.pointee }' in Swift 5. How can I fix it? ThanksStilliform
This works when you're saving data first and loading afterwards, but if you're loading first before saving, (for example) you're checking for a value in keychain and has none on the first time, it will crash.Silage
I have save dictionary data, when I am loading it give empty dictionary @KosukeOgawa,Decennial
Like aalesano says you cannot check if there is a value. It just crashes. And If you have to load and then check what is the point of this code. It just acts like a regular variable.Eydie
I am not sure I understand correctly. I am trying to save the in-app purchase ref inside of the keychain. When the user starts the app I try to load the value but unless it has been saved before it crashes. Am I not supposed to use something like this?Histopathology
Crashes at this line return self.withUnsafeBytes { $0.pointee }Mohun
how to store string and other data types? I need to store API_KEY in Keychain which is in String.Junejuneau
I recommend paying attention that here for kSecClassGenericPassword class, only kSecAttrAccount is set. But the primary key for this class is a combination of both kSecAttrAccount and kSecAttrService, so it could be a great idea to add kSecAttrService also. More info: https://mcmap.net/q/267176/-ksecattraccount-and-ksecattrgenericAnnabelle
The default behavior is to return only 1 value when it's a password request so the line kSecMatchLimit as String : kSecMatchLimitOne in the query isn't necessary.Slickenside
C
6

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)
Carbine answered 12/6, 2015 at 19:20 Comment(0)
B
6

This is Sazzad Hissain Khan's answer rewritten for iOS without non-Swifty NS-prefixed attributes and a cleaner code.

import Security

class KeychainService {
    class func updatePassword(service: String, account: String, data: String) {
        guard let dataFromString = data.data(using: .utf8, allowLossyConversion: false) else {
            return
        }

        let status = SecItemUpdate(modifierQuery(service: service, account: account), [kSecValueData: dataFromString] as CFDictionary)

        checkError(status)
    }

    class func removePassword(service: String, account: String) {
        let status = SecItemDelete(modifierQuery(service: service, account: account))

        checkError(status)
    }

    class func savePassword(service: String, account: String, data: String) {
        guard let dataFromString = data.data(using: .utf8, allowLossyConversion: false) else {
            return
        }

        let keychainQuery: [CFString: Any] = [kSecClass: kSecClassGenericPassword,
                                              kSecAttrService: service,
                                              kSecAttrAccount: account,
                                              kSecValueData: dataFromString]

        let status = SecItemAdd(keychainQuery as CFDictionary, nil)

        checkError(status)
    }

    class func loadPassword(service: String, account: String) -> String? {
        var dataTypeRef: CFTypeRef?

        let status = SecItemCopyMatching(modifierQuery(service: service, account: account), &dataTypeRef)

        if status == errSecSuccess,
            let retrievedData = dataTypeRef as? Data {
            return String(data: retrievedData, encoding: .utf8)
        } else {
            checkError(status)

            return nil
        }
    }

    fileprivate static func modifierQuery(service: String, account: String) -> CFDictionary {
        let keychainQuery: [CFString: Any] = [kSecClass: kSecClassGenericPassword,
                                              kSecAttrService: service,
                                              kSecAttrAccount: account,
                                              kSecReturnData: kCFBooleanTrue]

        return keychainQuery as CFDictionary
    }

    fileprivate static func checkError(_ status: OSStatus) {
        if status != errSecSuccess {
            if #available(iOS 11.3, *),
            let err = SecCopyErrorMessageString(status, nil) {
                print("Operation failed: \(err)")
            } else {
                print("Operation failed: \(status). Check the error message through https://osstatus.com.")
            }
        }
    }
}
Bloodandthunder answered 20/4, 2018 at 16:2 Comment(1)
Is there any possibility of stored data in KeyChain get affected ? I mean OS update or etc ?Goto
U
5

Roi Mulia's answer works very well, here's a version with a few minimal adjustments for Swift 2:

class KeyChain {
    class func save(key: String, data: NSData) -> OSStatus {
        let query = [
            kSecClass as String       : kSecClassGenericPassword as String,
            kSecAttrAccount as String : key,
            kSecValueData as String   : data ]

        SecItemDelete(query as CFDictionaryRef)

        return 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:AnyObject? = nil

        let status: OSStatus = SecItemCopyMatching(query, &dataTypeRef)

        if status == noErr {
            return (dataTypeRef! 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
    {
        let 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
    {
        let uuid: CFUUIDRef = CFUUIDCreate(nil)
        let cfStr:CFString = CFUUIDCreateString(nil, uuid)

        let nsTypeString = cfStr as NSString
        let swiftString:String = nsTypeString as String
        return swiftString
    }
}

Example usage:

let data = KeyChain.intToNSDATA(555)
let status = KeyChain.save("MyNumber", data: data)
print(status)

if let receivedData = KeyChain.load("MyNumber") {
    let result = KeyChain.NSDATAtoInteger(receivedData)
    print(result)
}
Unmoor answered 31/5, 2016 at 13:45 Comment(1)
Is there any possibility of stored data in KeyChain get affected ? I mean OS update or etc ?Goto
E
4

I tried to make it as simple as possible.

fileprivate class KeychainService {

  static func updatePassword(_ password: String, serviceKey: String) {
    guard let dataFromString = password.data(using: .utf8) else { return }

    let keychainQuery: [CFString : Any] = [kSecClass: kSecClassGenericPassword,
                                           kSecAttrService: serviceKey,
                                           kSecValueData: dataFromString]
    SecItemDelete(keychainQuery as CFDictionary)
    SecItemAdd(keychainQuery as CFDictionary, nil)
  }

  static func removePassword(serviceKey: String) {

    let keychainQuery: [CFString : Any] = [kSecClass: kSecClassGenericPassword,
                                           kSecAttrService: serviceKey]

    SecItemDelete(keychainQuery as CFDictionary)
  }

  static func loadPassword(serviceKey: String) -> String? {
    let keychainQuery: [CFString : Any] = [kSecClass : kSecClassGenericPassword,
                                           kSecAttrService : serviceKey,
                                           kSecReturnData: kCFBooleanTrue,
                                           kSecMatchLimitOne: kSecMatchLimitOne]

    var dataTypeRef: AnyObject?
    SecItemCopyMatching(keychainQuery as CFDictionary, &dataTypeRef)
    guard let retrievedData = dataTypeRef as? Data else { return nil }

    return String(data: retrievedData, encoding: .utf8)
  }

  static func flush()  {
    let secItemClasses =  [kSecClassGenericPassword]
    for itemClass in secItemClasses {
      let spec: NSDictionary = [kSecClass: itemClass]
      SecItemDelete(spec)
    }
  }
}
Elbertina answered 4/2, 2019 at 16:28 Comment(2)
how to use this?Procreate
@YogeshPatel KeychainService.updatePassword("password", serviceKey: "key"); let password = KeychainService.loadPassword("key");Elbertina
L
1

Example how to save & retrieve a struct User, a pretty common use-case:

import Security
import UIKit

class KeyChain {
    struct User {
        let identifier: Int64
        let password: String
    }

    private static let service = "MyService"

    static func save(user: User) -> Bool {
        let identifier = Data(from: user.identifier)
        let password = user.password.data(using: .utf8)!
        let query = [kSecClass as String : kSecClassGenericPassword as String,
                     kSecAttrService as String : service,
                     kSecAttrAccount as String : identifier,
                     kSecValueData as String : password]
            as [String : Any]

        let deleteStatus = SecItemDelete(query as CFDictionary)

        if deleteStatus == noErr || deleteStatus == errSecItemNotFound {
            return SecItemAdd(query as CFDictionary, nil) == noErr
        }

        return false
    }

    static func retrieveUser() -> User? {
        let query = [kSecClass as String : kSecClassGenericPassword,
                     kSecAttrService as String : service,
                     kSecReturnAttributes as String : kCFBooleanTrue!,
                     kSecReturnData as String: kCFBooleanTrue!]
            as [String : Any]

        var result: AnyObject? = nil
        let status = SecItemCopyMatching(query as CFDictionary, &result)

        if status == noErr,
            let dict = result as? [String: Any],
            let passwordData = dict[String(kSecValueData)] as? Data,
            let password = String(data: passwordData, encoding: .utf8),
            let identifier = (dict[String(kSecAttrAccount)] as? Data)?.to(type: Int64.self) {

            return User(identifier: identifier, password: password)
        } else {
            return nil
        }
    }
}

private 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 {
        withUnsafeBytes { $0.load(as: T.self) }
    }
}
Locate answered 22/6, 2019 at 14:38 Comment(2)
I dont see any of the Data extension methods getting used in the solution. Are they required? If so, for what exactly?Intrigante
What's the implementation of this with uitextfields? I want to receive my user info on viewdidload() so it's already there.Pryor
P
0

You are storing a number, not a string, so you are getting back an NSNumber, not a string. The exception is pretty clear - you can't downcast an NSNumber to a String - you can use stringValue() to get the string representation of an NSNumber

if let op = dataTypeRef?.toOpaque() {
    let data = Unmanaged<NSData>.fromOpaque(op).takeUnretainedValue()
    if let string: AnyObject? =  NSKeyedUnarchiver.unarchiveObjectWithData(data) as? AnyObject? {
        if key == "IMAGE_TAKEN"
        {
            return string.stringValue() as! String!
        }
        else if string == nil
        {
                return nil
        }
    }
}
Pannier answered 8/6, 2015 at 22:47 Comment(17)
So why in the end you added "as string" and not NSNumber?Carbine
Because I assumed you wanted a string. It was already an NSNumber. It probably makes more sense to return an int, in which case you would use intValue() - but you would need to change your function signature. In theory you could omit the as! String! as stringValue() always returns a stringPannier
Not having the code in front of me, you may need to downcast string to NSNumber first to stop the compiler complaining that it doesn't know about stringValue for AnyObject?Pannier
Eventually what that I'm trying to achieve is storing integer. Retrieve it when i need, add amount to value and store it again. Am i saving/storing it correctly as i posted above? The problem is in the retrieved method or in both? This keychain really make me confused lol, sorry. And thanks!Carbine
Is there a reason why you want to store this data in the key chain? NSUserDefaults is much simpler for a simple integer. keychain is best for things like passwords that need to be secured. There are also plenty of wrapper libraries that make keychain much easier to use.Pannier
I know. For your first question- i need to store data that cannot be deleted. Nsuserdefult is great but if the user delete the app, well lol. 2. Im trying to learn, after ill figure this out ill make a warper of my own :) and of course share it.!Carbine
By all means go ahead, but there are plenty of them already - cocoapods.org/?q=keychainPannier
Sure, as I said go ahead.Pannier
The problem is that im stuck, do I store method when referring to Integer. Is correct?Carbine
No offence, but you don't seem to be a very experienced developer and dealing with KeyChain is a pretty 'advanced' task as it is a fairly low-level API. You need to know the type of the data you have stored and use the appropriate downcasts when you retrieve the data.Pannier
Not offended :) Paul if i need an integer, before retrieve it(using the second method) is the first method correct? Do you have any idea?Carbine
NSKeyedUnarchiver will return AnyObject? - you need to know what format your data is an downcast it or you need to write code that inspects the object type and handles it appropriately.Pannier
So no matter what type im storing, when I retrieve it i need to use the correct downcast? Because the value that is returned will be angobject? Automatic?Carbine
The object that is returned will be the object that you put in there, but the function signature for unarchiveObjectWithData says that it returns AnyObject ? - you can use the downcast straight in that invocation if you like, but you will need to downcast at some point.Pannier
Assuming im using this methods only for integer, i can downcast os to int directly?Carbine
You still need to go via NSNumber - NSKeyedArchiver can't use intrinsic types, only objects so ints, floats, doubles etc get boxed in an NSNumberPannier
Let us continue this discussion in chat.Carbine

© 2022 - 2024 — McMap. All rights reserved.