Adding item to keychain using Swift

心已入冬 提交于 2019-11-30 02:05:42

In the xcode 6.0.1 you must do this!!

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)

You simply need to downcast the literal:

let dict = ["hi": "Pasan"] as NSDictionary

Now dict is an NSDictionary. To make a mutable one, it's very similar to Objective-C:

let mDict = dict.mutableCopy() as NSMutableDictionary
mDict["hola"] = "Ben"

Perhaps things have improved since. On Xcode 7 beta 4, no casting seems to be necessary except when dealing with the result AnyObject?. Specifically, the following seems to work:

var query : [NSString : AnyObject] = [
    kSecClass : kSecClassGenericPassword,
    kSecAttrService : "MyAwesomeService",
    kSecReturnAttributes : true,   // return dictionary in result parameter
    kSecReturnData : true          // include the password value
]
var result : AnyObject?
let err = SecItemCopyMatching(query, &result)
if (err == errSecSuccess) {
    // on success cast the result to a dictionary and extract the
    // username and password from the dictionary.
    if let result = result as ? [NSString : AnyObject],
       let username = result[kSecAttrAccount] as? String,
       let passdata = result[kSecValueData] as? NSData,
       let password = NSString(data:passdata, encoding:NSUTF8StringEncoding) as? String {
        return (username, password)
    }
} else if (status == errSecItemNotFound) {
    return nil;
} else {
    // probably a program error,
    // print and lookup err code (e.g., -50 = bad parameter)
}

To add a key if it was missing:

var query : [NSString : AnyObject] = [
    kSecClass : kSecClassGenericPassword,
    kSecAttrService : "MyAwesomeService",
    kSecAttrLabel : "MyAwesomeService Password",
    kSecAttrAccount : username,
    kSecValueData : password.dataUsingEncoding(NSUTF8StringEncoding)!
]
let result = SecItemAdd(query, nil)
// check that result is errSecSuccess, etc...

A few things to point out: your initial problem might have been that str.dataUsingEncoding returns an Optional. Adding '!' or better yet, using an if let to handle nil return, would likely make your code work. Printing out the error code and looking it up in the docs will help a lot in isolating the problem (I was getting err -50 = bad parameter, until I noticed a problem with my kSecClass, nothing to do with data types or casts!).

This seemed to work fine or at least compiler didn't have kittens - UNTESTED beyond that

        var secret: NSData = "Top Secret".dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)
        var array1 = NSArray(objects:"\(kSecClassGenericPassword)", "MyService", "Some account", secret)
        var array2 = NSArray(objects:"\(kSecClass)","\(kSecAttrService)", "\(kSecAttrAccount)","\(kSecValueData)")
        let query = NSDictionary(objects: array1, forKeys: array2)
        println(query)
        let status  = SecItemAdd(query as CFDictionaryRef, nil)

Seems to work fine in Beta 2

In order to get this to work, you need to access the retained values of the keychain constants instead. For example:

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 MSMutableDictionary 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

let kSecClassKey = String(kSecClass)
let kSecAttrAccountKey = String(kSecAttrAccount)
let kSecValueDataKey = String(kSecValueData)
let kSecClassGenericPasswordKey = String(kSecClassGenericPassword)
let kSecAttrServiceKey = String(kSecAttrService)
let kSecMatchLimitKey = String(kSecMatchLimit)
let kSecReturnDataKey = String(kSecReturnData)
let kSecMatchLimitOneKey = String(kSecMatchLimitOne)

you can also do it inside the dictionary itself alá:

var query: [String: Any] = [
    String(kSecClass): kSecClassGenericPassword,
    String(kSecAttrService): "MyService",
    String(kSecAttrAccount): "Some account",
    String(kSecValueData): secret
]

however, this is more expensive for the compiler, even more so since you're probably using the query in multiple places.

more convenient to use the cocoa pods SSKeychain

+ (NSArray *)allAccounts;
+ (NSArray *)accountsForService:(NSString *)serviceName;
+ (NSString *)passwordForService:(NSString *)serviceName account:(NSString *)account;
+ (BOOL)deletePasswordForService:(NSString *)serviceName account:(NSString *)account;
+ (BOOL)setPassword:(NSString *)password forService:(NSString *)serviceName account:(NSString *)account;
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!