web-dev-qa-db-ja.com

Swiftを使用したiOSキーチェーンへのアイテムの追加とクエリ

データを追加し、Objective Cからデータをクエリするために利用できるiOS KeychainコードサンプルをすべてSwiftに変換するのに問題があります。私は文字列(アクセストークン)の基本的な保存を行い、それを読み戻そうとしています。スタックオーバーフローに関する他の質問をいくつか見てきましたが、うまく機能しません。私はさまざまなソースからソリューションをつなぎ合わせようとしました。

編集1:self.defaultKeychainQueryが混乱しているのではないかと思ったので、より基本的な設定を試してみました。以下のコードを最新バージョンに更新しました。

編集2:うまくいきました。保存クエリにデータ値を適切に追加していませんでした。文字列をNSDataに変換する必要がありました。以下のコードを最新の作業バージョンに更新しました。

Edit 3:Xerxesが以下で指摘しているように、このコードは、Dictionariesに何らかの問題があるため、ベータ1以降のXcodeバージョンでは動作しません。この修正を知っている場合は、お知らせください。

更新:これを Swift Locksmithと呼ばれる)で書かれたキーチェーンライブラリ に変更しました。


保存

class func save(service: NSString, data: NSString) {
  var dataFromString: NSData = data.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)
  // Instantiate a new default keychain query
  var keychainQuery: NSMutableDictionary = NSMutableDictionary(objects: [kSecClassGenericPassword, service, userAccount, dataFromString], forKeys: [kSecClass, kSecAttrService, kSecAttrAccount, kSecValueData])

  // Delete any existing items
  SecItemDelete(keychainQuery as CFDictionaryRef)

  // Add the new keychain item
  var status: OSStatus = SecItemAdd(keychainQuery as CFDictionaryRef, nil)

  // Check that it worked ok
  println("Saving status code is: \(status)")
}

負荷

  class func load(service: NSString) -> AnyObject? {
    // Instantiate a new default keychain query
    // Tell the query to return a result
    // Limit our results to one item
    var keychainQuery: NSMutableDictionary = NSMutableDictionary(objects: [kSecClassGenericPassword, service, userAccount, kCFBooleanTrue, kSecMatchLimitOne], forKeys: [kSecClass, kSecAttrService, kSecAttrAccount, kSecReturnData, kSecMatchLimit])



    // I'm not too sure what's happening here...
    var dataTypeRef :Unmanaged<AnyObject>?

    // Search for the keychain items
    let status: OSStatus = SecItemCopyMatching(keychainQuery, &dataTypeRef)


    println("Loading status code is: \(status)")

    // I'm not too sure what's happening here...
    let opaque = dataTypeRef?.toOpaque()

    if let op = opaque? {
      let retrievedData = Unmanaged<NSData>.fromOpaque(op).takeUnretainedValue()
      println("Retrieved the following data from the keychain: \(retrievedData)")
      var str = NSString(data: retrievedData, encoding: NSUTF8StringEncoding)
      println("The decoded string is \(str)")
    } else {
      println("Nothing was retrieved from the keychain.")
    }

    return nil
  }

使用法(View Controller)

KeychainService.saveToken("sometoken")
KeychainService.loadToken()

これらの便利なメソッドを使用します

class func saveToken(token: NSString) {
    self.save("service", data: token)
  }

class func loadToken() {
    var token = self.load("service")
    if let t = token {
      println("The token is: \(t)")
    }
  }

これにより、コンソールに出力が表示されます。

Saving status code is: 0
Loading status code is: 0
Retrieved the following data from the keychain: <736f6d65 746f6b65 6e>
The decoded string is sometoken

ご協力ありがとうございます。 dataTypeRefを取得した後、または上記のコードで指定されたデータがある場合、dataTypeRefをどう処理するかはあまりわかりません。

20
matthewpalmer

これを機能させるには、キーチェーン定数の保持値を取得して、最初に次のように保存する必要があります。

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

次に、NSMutableDictionaryの値を次のように参照できます。

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/

お役に立てれば!

シェルビー

7
user3927134

この単純なタスクのデモアプリとヘルパー関数を作成しました。キーチェーンの特定のキーのテキスト文字列を読み書きします。

https://github.com/marketplacer/keychain-Swift

let keychain = KeychainSwift()
keychain.set("hello world", forKey: "my key")
keychain.get("my key")
keychain.delete("my key")
5
Evgenii

パスワードを追加、取得、削除する方法に関する私の解釈(このスレッドで提示されたライブラリを使用するのが面倒な人向け):

// Saving password associated with the login and service
let userAccount = "user's login"
let service = "service name"
let passwordData: NSData = self.textfield_password.text!.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!

let keychainQuery: [NSString: NSObject] = [
        kSecClass: kSecClassGenericPassword,
        kSecAttrAccount: userAccount,
        kSecAttrService: service,
        kSecValueData: passwordData]    

SecItemDelete(keychainQuery as CFDictionaryRef) //Deletes the item just in case it already exists
let keychain_save_status: OSStatus = SecItemAdd(keychainQuery as CFDictionaryRef, nil)
print("Keychain saving code is: \(keychain_save_status)")

...

// Getting the password associated with the login and service
let userAccount = "user's login"
let service = "service name"
let keychainQuery: [NSString: NSObject] = [
    kSecClass: kSecClassGenericPassword,
    kSecAttrService: service,
    kSecAttrAccount: userAccount,
    kSecReturnData: kCFBooleanTrue,
    kSecMatchLimit: kSecMatchLimitOne]

var rawResult: AnyObject?
let keychain_get_status: OSStatus = SecItemCopyMatching(keychainQuery, &rawResult)
print("Keychain getting code is: \(keychain_get_status)")

if (keychain_get_status == errSecSuccess) {
    let retrievedData = rawResult as? NSData
    let pass = NSString(data: retrievedData!, encoding: NSUTF8StringEncoding)
    print("Username: \(userAccount), password: \(pass!)")
    // Do your work with the retrieved password here
} else {
    print("No login data found in Keychain.")

...

//Deleting user's credentials from Keychain. Password is optional for the query when you delete, in most cases you won't know it after all.
let userAccount = "user's login"
let service = "service name"

let keychainQuery: [NSString: NSObject] = [
        kSecClass: kSecClassGenericPassword,
        kSecAttrAccount: userAccount,
        kSecAttrService: service]
let keychain_delete_status: OSStatus = SecItemDelete(keychainQuery as CFDictionaryRef)
print("Keychain deleting code is: \(keychain_delete_status)")

結果コードとその他の有用な情報は、公式ドキュメントで見つけることができます: https://developer.Apple.com/library/ios/documentation/Security/Reference/keychainservices/

3
Vitalii

For Swift users

キーチェーンのフィールドを追加/取得/更新する単一行コード:
https://github.com/jrendel/SwiftKeychainWrapper

使用法

キーチェーンに文字列値を追加します。

let saveSuccessful: Bool = KeychainWrapper.setString("Some String", forKey: "myKey")  

キーチェーンから文字列値を取得します。

let retrievedString: String? = KeychainWrapper.stringForKey("myKey")

キーチェーンから文字列値を削除します。

let removeSuccessful: Bool = KeychainWrapper.removeObjectForKey("myKey")
3
K.K

私は解決策を考え出したと思います。上記の投稿を編集して、動作するコードを含めました(少なくとも私にとっては)。また、ここでブログに書いています: iOS KeychainをSwift(コード例) とともに使用しています。

8月11日更新:rshelbyのコメントに基づいてブログ投稿のコードを更新しました。ご覧ください

更新:これを Swift Locksmithと呼ばれる)で書かれたキーチェーンライブラリ に変更しました。


保存

class func save(service: NSString, data: NSString) {
  var dataFromString: NSData = data.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)
  // Instantiate a new default keychain query
  var keychainQuery: NSMutableDictionary = NSMutableDictionary(objects: [kSecClassGenericPassword, service, userAccount, dataFromString], forKeys: [kSecClass, kSecAttrService, kSecAttrAccount, kSecValueData])

  // Delete any existing items
  SecItemDelete(keychainQuery as CFDictionaryRef)

  // Add the new keychain item
  var status: OSStatus = SecItemAdd(keychainQuery as CFDictionaryRef, nil)

  // Check that it worked ok
  println("Saving status code is: \(status)")
}

負荷

  class func load(service: NSString) -> AnyObject? {
    // Instantiate a new default keychain query
    // Tell the query to return a result
    // Limit our results to one item
    var keychainQuery: NSMutableDictionary = NSMutableDictionary(objects: [kSecClassGenericPassword, service, userAccount, kCFBooleanTrue, kSecMatchLimitOne], forKeys: [kSecClass, kSecAttrService, kSecAttrAccount, kSecReturnData, kSecMatchLimit])



    // I'm not too sure what's happening here...
    var dataTypeRef :Unmanaged<AnyObject>?

    // Search for the keychain items
    let status: OSStatus = SecItemCopyMatching(keychainQuery, &dataTypeRef)


    println("Loading status code is: \(status)")

    // I'm not too sure what's happening here...
    let opaque = dataTypeRef?.toOpaque()

    if let op = opaque? {
      let retrievedData = Unmanaged<NSData>.fromOpaque(op).takeUnretainedValue()
      println("Retrieved the following data from the keychain: \(retrievedData)")
      var str = NSString(data: retrievedData, encoding: NSUTF8StringEncoding)
      println("The decoded string is \(str)")
    } else {
      println("Nothing was retrieved from the keychain.")
    }

    return nil
  }

使用法(View Controller)

KeychainService.saveToken("sometoken")
KeychainService.loadToken()

これらの便利なメソッドを使用します

class func saveToken(token: NSString) {
    self.save("service", data: token)
  }

class func loadToken() {
    var token = self.load("service")
    if let t = token {
      println("The token is: \(t)")
    }
  }

これにより、コンソールに出力が表示されます。

Saving status code is: 0
Loading status code is: 0
Retrieved the following data from the keychain: <736f6d65 746f6b65 6e>
The decoded string is sometoken
1
matthewpalmer