使用Swift将项添加到钥匙串

时间:2014-06-09 03:29:18

标签: ios swift keychain

我正在尝试使用Swift将项添加到iOS钥匙串,但无法弄清楚如何正确输入。从WWDC 2013会议709开始,给出以下Objective-C代码:

NSData *secret = [@"top secret" dataWithEncoding:NSUTF8StringEncoding];
NSDictionary *query = @{
    (id)kSecClass: (id)kSecClassGenericPassword,
    (id)kSecAttrService: @"myservice",
    (id)kSecAttrAccount: @"account name here",
    (id)kSecValueData: secret,
};

OSStatus = SecItemAdd((CFDictionaryRef)query, NULL);

尝试在Swift中执行如下操作:

var secret: NSData = "Top Secret".dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)
var query: NSDictionary = [
    kSecClass: kSecClassGenericPassword,
    kSecAttrService: "MyService",
    kSecAttrAccount: "Some account",
    kSecValueData: secret
]

产生错误“无法将表达式的类型'Dictionary'转换为'DictionaryLiteralConvertible'。

我采用的另一种方法是在Dictionary上使用Swift和- setObject:forKey:方法,使用密钥kSecClass添加kSecClassGenericPassword。

在Objective-C中:

NSMutableDictionary *searchDictionary = [NSMutableDictionary dictionary];
[searchDictionary setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass];

在Objective-C代码中,使用id桥接各种keychain项类键的CFTypeRef。在Swift documentation中,它提到Swift将id作为AnyObject导入。但是,当我尝试将kSecClass转发为方法的AnyObject时,我得到“Type'AnyObject'不符合NSCopying的错误。

任何帮助,无论是直接答案还是有关如何与Core Foundation类型互动的指导,都将不胜感激。

编辑2

此解决方案自Xcode 6 Beta 2起不再有效。如果您使用Beta 1,则以下代码可能有效。

var secret: NSData = "Top Secret".dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)
let query = NSDictionary(objects: [kSecClassGenericPassword, "MyService", "Some account", secret], forKeys: [kSecClass,kSecAttrService, kSecAttrAccount, kSecValueData])

OSStatus status = SecItemAdd(query as CFDictionaryRef, NULL)

要使用Keychain Item Attribute键作为字典键,您必须使用takeRetainedValue或takeUnretainedValue(视情况而定)将其解包。然后你可以将它们转换为NSCopying。这是因为它们在标题中是CFTypeRefs,它们都不是可复制的。

然而,从Xcode 6 Beta 2开始,这会导致Xcode崩溃。

7 个答案:

答案 0 :(得分:13)

你只需要向下翻译文字:

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

现在dict是一个NSDictionary。为了制作一个可变的,它与Objective-C非常相似:

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

答案 1 :(得分:7)

在xcode 6.0.1中你必须这样做!!

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)

答案 2 :(得分:6)

也许事情有所改善。在Xcode 7 beta 4上,除了处理结果hidden_field之外,似乎不需要强制转换。具体来说,以下似乎有效:

AnyObject?

如果缺少密钥,请添加密钥:

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

需要指出的一些事项:您的初始问题可能是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... 返回Optional。添加'!'或者更好的是,使用str.dataUsingEncoding来处理nil返回,可能会使你的代码工作。打印错误代码并在文档中查找它将有助于隔离问题(我得到错误-50 =错误的参数,直到我发现我的kSecClass有问题,与数据类型或强制转换无关!)

答案 3 :(得分:4)

这似乎工作得很好,或者至少编译器没有小猫 - 没有那个

        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)

似乎在Beta 2中正常工作

答案 4 :(得分:1)

为了使其工作,您需要访问钥匙串常量的保留值。例如:

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

然后,您可以像这样引用MSMutableDictionary中的值:

var keychainQuery: NSMutableDictionary = NSMutableDictionary(objects: [kSecClassGenericPasswordValue, service, userAccount, kCFBooleanTrue, kSecMatchLimitOneValue], forKeys: [kSecClassValue, kSecAttrServiceValue, kSecAttrAccountValue, kSecReturnDataValue, kSecMatchLimitValue])

我写了一篇关于它的博客文章: http://rshelby.com/2014/08/using-swift-to-save-and-query-ios-keychain-in-xcode-beta-4/

希望这有帮助!

rshelby

答案 5 :(得分:1)

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)

你也可以在字典本身内完成它:

var query: [String: Any] = [
    String(kSecClass): kSecClassGenericPassword,
    String(kSecAttrService): "MyService",
    String(kSecAttrAccount): "Some account",
    String(kSecValueData): secret
]
然而,这对于编译器来说更昂贵,更是如此,因为您可能在多个地方使用查询。

答案 6 :(得分:0)

使用可可豆荚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;