【问题标题】:Trying to use KeychainItemWrapper by Apple "translated" to Swift尝试使用 Apple “翻译”为 Swift 的 KeychainItemWrapper
【发布时间】:2014-10-20 05:00:48
【问题描述】:

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

我正在尝试使用 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 上运行。

【问题讨论】:

    标签: swift ios8 keychainitemwrapper xcode6-beta6


    【解决方案1】:

    斯威夫特 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:"");
    }
    

    【讨论】:

    • 这看起来非常好,我可能会使用这个示例的大部分内容。是否有理由将这两个函数包装为类上的实例方法,而不是将它们保留为纯顶级函数或类方法?
    • 另外,keychainQuery 变量似乎需要是 NSDictionary 类型(我使用的是 Xcode 6.1.1) - NSMutableDictionary 是对我有用但不可变的特定类型。
    • 当我将该代码复制并粘贴到类文件中时,我得到了错误。例如这一行: var keychainQuery = [ 给我错误:“NSString 不是 NSData 的子类型”
    • @grep 对于 kSecClass 我收到一个错误:CFString 类型的值不符合预期的元素类型“NSCopying”。这适用于 Swift 2.0。知道如何解决这个问题吗?
    • 这行得通 - 感谢您的快速更新!为什么要使用 let kSecClassValue = NSString(format: kSecClass);而不是 let kSecClassValue = (format: kSecClass as String); ?
    【解决方案2】:

    官方是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
      

    【讨论】:

      【解决方案3】:

      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: , svce: Service, class: genp]
      恢复值:'TestValue'

      【讨论】:

      • 感谢您的帮助。我想出了一个现在可行的解决方法。但这个问题可能仍然存在。
      • @HillInHarwich 为什么这个解决方案不适合你?
      • @ileonard 它对我不起作用-但原因(我通常在发布到此处后很快发现)是我正在使用另一个项目-尽管我已将框架/项目添加到正常列表,我没有添加将框架放在“包含在二进制文件中”列表中 - 现在我不知道它的正确名称了。
      【解决方案4】:

      我的解决方案似乎有效:

      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 后立即打开它。

      【讨论】:

        猜你喜欢
        • 2022-10-06
        • 1970-01-01
        • 1970-01-01
        • 2014-02-27
        • 2011-11-26
        • 2012-07-06
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多