8

叹息,我整个下午都在做这个……这是我的噩梦:

我正在尝试使用 Apple 制造的 KeychainItemWrapper。但我将其 Objective-C 代码“翻译”为 Swift:

import Foundation
import Security
class MyKeychainItemWrapper: NSObject {
var keychainItemData: NSMutableDictionary?
var genericPasswordQuery: NSMutableDictionary = NSMutableDictionary()

init(identifier: String, accessGroup: String?) {

    super.init()

    // Begin Keychain search setup. The genericPasswordQuery leverages the special user
    // defined attribute kSecAttrGeneric to distinguish itself between other generic Keychain
    // items which may be included by the same application.
    genericPasswordQuery.setObject(kSecClassGenericPassword, forKey: kSecClass)
    genericPasswordQuery.setObject(identifier, forKey: kSecAttrGeneric)

    // The keychain access group attribute determines if this item can be shared
    // amongst multiple apps whose code signing entitlements contain the same keychain access group.
    println(accessGroup)
    if (!(accessGroup == nil)) {
        genericPasswordQuery.setObject(accessGroup!, forKey: kSecAttrAccessGroup)
    }

    // Use the proper search constants, return only the attributes of the first match.
    genericPasswordQuery.setObject(kSecMatchLimitOne, forKey: kSecMatchLimit)
    genericPasswordQuery.setObject(kCFBooleanTrue, forKey: kSecReturnAttributes)

    var tempQuery: NSDictionary = NSDictionary(dictionary: genericPasswordQuery)

    var outDictionary: Unmanaged<AnyObject>? = nil

    var status: OSStatus = SecItemCopyMatching(tempQuery as CFDictionaryRef, &outDictionary)
    println(status == noErr)

    if (status == noErr) {
        // Stick these default values into keychain item if nothing found.
        resetKeychainItem()

        // Add the generic attribute and the keychain access group.
        keychainItemData!.setObject(identifier, forKey: kSecAttrGeneric)

        if (!(accessGroup == nil)) {
            keychainItemData!.setObject(accessGroup!, forKey: kSecAttrAccessGroup)
        }
    } else {
        // load the saved data from Keychain.
        keychainItemData = secItemFormatToDictionary(outDictionary?.takeRetainedValue() as NSDictionary)
    }
}

然后在我的应用程序的 AppDelegate.swift 中,我试图通过以下方式使用它:

import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?
    var passwordItem: MyKeychainItemWrapper = MyKeychainItemWrapper(identifier: "Password", accessGroup: nil)
...

所以,初始化程序被调用,但是但不知何故,我总是,总是得到

线程 1:EXC_BREAKPOINT(代码=EXC_ARM_BREAKPOINT,子代码=0xe7ffdefe)

在此处输入图像描述

我尝试注释掉问题行,然后在另一个 if() 处收到此错误: 在此处输入图像描述

我什至尝试过:

var mmm: Bool = (accessGroup == nil)
if (!mmm) {
  genericPasswordQuery.setObject(accessGroup!, forKey: kSecAttrAccessGroup)
}

但是在同一个地方出现同样的错误,即 if(..)

我现在很困惑。我在这里错过了什么吗?

环境:Xcode6-beta6,iOS 8 beta 5 在未越狱的 iPhone 5 上。

4

4 回答 4

19

斯威夫特 3

import UIKit
import Security

let kSecClassGenericPasswordValue = String(format: kSecClassGenericPassword as String)
let kSecClassValue = String(format: kSecClass as String)
let kSecAttrServiceValue = String(format: kSecAttrService as String)
let kSecValueDataValue = String(format: kSecValueData as String)
let kSecMatchLimitValue = String(format: kSecMatchLimit as String)
let kSecReturnDataValue = String(format: kSecReturnData as String)
let kSecMatchLimitOneValue = String(format: kSecMatchLimitOne as String)
let kSecAttrAccountValue = String(format: kSecAttrAccount as String)

struct KeychainAccess {

    func setPasscode(identifier: String, passcode: String) {
        if let dataFromString = passcode.data(using: String.Encoding.utf8) {
            let keychainQuery = [
                kSecClassValue: kSecClassGenericPasswordValue,
                kSecAttrServiceValue: identifier,
                kSecValueDataValue: dataFromString
            ] as CFDictionary
            SecItemDelete(keychainQuery)
            print(SecItemAdd(keychainQuery, nil))
        }
    }

    func getPasscode(identifier: String) -> String? {
        let keychainQuery = [
            kSecClassValue: kSecClassGenericPasswordValue,
            kSecAttrServiceValue: identifier,
            kSecReturnDataValue: kCFBooleanTrue,
            kSecMatchLimitValue: kSecMatchLimitOneValue
        ] as  CFDictionary
        var dataTypeRef: AnyObject?
        let status: OSStatus = SecItemCopyMatching(keychainQuery, &dataTypeRef)
        var passcode: String?
        if (status == errSecSuccess) {
            if let retrievedData = dataTypeRef as? Data,
                let result = String(data: retrievedData, encoding: String.Encoding.utf8) {
                passcode = result as String
            }
        }
        else {
            print("Nothing was retrieved from the keychain. Status code \(status)")
        }
        return passcode
    }
}

斯威夫特 2

import UIKit;
import Security;


let kSecClassGenericPasswordValue = NSString(format: kSecClassGenericPassword);
let kSecClassValue = NSString(format: kSecClass);
let kSecAttrServiceValue = NSString(format: kSecAttrService);
let kSecValueDataValue = NSString(format: kSecValueData);
let kSecMatchLimitValue = NSString(format: kSecMatchLimit);
let kSecReturnDataValue = NSString(format: kSecReturnData);
let kSecMatchLimitOneValue = NSString(format: kSecMatchLimitOne);
let kSecAttrAccountValue = NSString(format: kSecAttrAccount);


class KeychainAccess: NSObject {

func setPasscode(identifier: String, passcode: String) {
    let dataFromString: NSData = passcode.dataUsingEncoding(NSUTF8StringEncoding)!;
    let keychainQuery = NSDictionary(
    objects: [kSecClassGenericPasswordValue, identifier, dataFromString],
    forKeys: [kSecClassValue, kSecAttrServiceValue, kSecValueDataValue]);
    SecItemDelete(keychainQuery as CFDictionaryRef);
    let status: OSStatus = SecItemAdd(keychainQuery as CFDictionaryRef, nil);
}


func getPasscode(identifier: String) -> NSString? {
    let keychainQuery = NSDictionary(
    objects: [kSecClassGenericPasswordValue, identifier, kCFBooleanTrue, kSecMatchLimitOneValue],
    forKeys: [kSecClassValue, kSecAttrServiceValue, kSecReturnDataValue, kSecMatchLimitValue]);
    var dataTypeRef: AnyObject?
    let status: OSStatus = SecItemCopyMatching(keychainQuery, &dataTypeRef)
    var passcode: NSString?;
    if (status == errSecSuccess) {
        let retrievedData: NSData? = dataTypeRef as? NSData
        if let result = NSString(data: retrievedData!, encoding: NSUTF8StringEncoding) {
            passcode = result as String
        }
    }
    else {
        print("Nothing was retrieved from the keychain. Status code \(status)")
    }
    return passcode;
   }
}

然后从任何地方简单地调用:

func setPasscode(passcode: String) {
    let keychainAccess = KeychainAccess();
    keychainAccess.setPasscode("YourAppIdentifier", passcode:passcode);
}


func getPasscode() -> NSString {
    let keychainAccess = KeychainAccess();
    return keychainAccess.getPasscode("YourAppIdentifier")!;
}


func deletePasscode() {
    let keychainAccess = KeychainAccess();
    keychainAccess.setPasscode("YourAppIdentifier", passcode:"");
}
于 2014-09-21T10:58:18.750 回答
5

官方是GenericKeychain

现有的几个swift版本,最好的一个是:

jrendel/SwiftKeychainWrapper · GitHub

如何使用它:

  1. 下载文件:KeychainWrapper.swift
  2. 编写代码来设置/获取/删除:

    let StrUsernameKey:String = "username"
    let StrPasswordKey:String = "password"
    
    let saveSuccessful: Bool = KeychainWrapper.setString(usernameTextField.text!, forKey: StrUsernameKey)
    print("saveSuccessful=\(saveSuccessful)") //saveSuccessful=true
    let retrievedString: String? = KeychainWrapper.stringForKey(StrUsernameKey)
    print("retrievedString=\(retrievedString)") //retrievedString=Optional("yourLastStoredUsernameString")
    let removeSuccessful: Bool = KeychainWrapper.removeObjectForKey(StrUsernameKey)
    print("removeSuccessful=\(removeSuccessful)") //removeSuccessful=true
    let retrievedStringAfterDelete: String? = KeychainWrapper.stringForKey(StrUsernameKey)
    print("retrievedStringAfterDelete=\(retrievedStringAfterDelete)") //retrievedStringAfterDelete=nil
    
于 2015-11-18T07:36:17.757 回答
4

Swift 2 的更新。

这是一个可能有帮助的示例实现:

import Security

class ZLKeychainService: NSObject {

    var service = "Service"
    var keychainQuery :[NSString: AnyObject]! = nil

    func save(name name: NSString, value: NSString) -> OSStatus? {
        let statusAdd :OSStatus?

        guard let dataFromString: NSData = value.dataUsingEncoding(NSUTF8StringEncoding) else {
            return nil
        }

        keychainQuery = [
            kSecClass       : kSecClassGenericPassword,
            kSecAttrService : service,
            kSecAttrAccount : name,
            kSecValueData   : dataFromString]
        if keychainQuery == nil {
            return nil
        }

        SecItemDelete(keychainQuery as CFDictionaryRef)

        statusAdd = SecItemAdd(keychainQuery! as CFDictionaryRef, nil)

        return statusAdd;
    }

    func load(name name: NSString) -> String? {
        var contentsOfKeychain :String?

        keychainQuery = [
            kSecClass       : kSecClassGenericPassword,
            kSecAttrService : service,
            kSecAttrAccount : name,
            kSecReturnData  : kCFBooleanTrue,
            kSecMatchLimit  : kSecMatchLimitOne]
        if keychainQuery == nil {
            return nil
        }

        var dataTypeRef: AnyObject?
        let status: OSStatus = SecItemCopyMatching(keychainQuery, &dataTypeRef)

        if (status == errSecSuccess) {
            let retrievedData: NSData? = dataTypeRef as? NSData
            if let result = NSString(data: retrievedData!, encoding: NSUTF8StringEncoding) {
                contentsOfKeychain = result as String
            }
        }
        else {
            print("Nothing was retrieved from the keychain. Status code \(status)")
        }

        return contentsOfKeychain
    }
}

//Test:
let userName = "TestUser"
let userValue: NSString = "TestValue"
print("userName: '\(userName)'")
print("userValue: '\(userValue)'")

let kcs = ZLKeychainService()

kcs.save(name:userName, value: userValue)
print("Keychain Query \(kcs.keychainQuery)")

if let recoveredToken = kcs.load(name:userName) {
    print("Recovered Value: '\(recoveredToken)'")
}

输出:

用户名:'TestUser'
用户值:'TestValue'
钥匙串查询 [acct:TestUser,v_Data:<54657374 56616c75 65>,svce:服务,类:genp]
恢复值:'TestValue'

于 2014-08-26T19:01:54.873 回答
0

我的解决方案似乎有效:

init(identifier: String) {

    super.init()

    genericPasswordQuery.setObject(kSecClassGenericPassword, forKey: kSecClass as String)
    genericPasswordQuery.setObject(identifier, forKey: kSecAttrGeneric as String)

    // Use the proper search constants, return only the attributes of the first match.
    genericPasswordQuery.setObject(kSecMatchLimitOne, forKey: kSecMatchLimit as String)
    genericPasswordQuery.setObject(kCFBooleanTrue, forKey: kSecReturnAttributes as String)

    var tempQuery: NSDictionary = NSDictionary(dictionary: genericPasswordQuery)

    var outDictionary: Unmanaged<AnyObject>? = nil

    let status: OSStatus = SecItemCopyMatching(tempQuery as CFDictionaryRef, &outDictionary)
    var result: NSDictionary? = outDictionary?.takeRetainedValue() as NSDictionary?
    if (result == nil) {
        // Stick these default values into keychain item if nothing found.
        resetKeychainItem()

        // Add the generic attribute and the keychain access group.
        keychainItemData!.setObject(identifier, forKey: kSecAttrGeneric as String)

    } else {
        // load the saved data from Keychain.
        keychainItemData = secItemFormatToDictionary(result!)

    }
}

我唯一做的就是在拿到 outDictionary 后立即打开它。

于 2014-10-21T23:04:12.513 回答