web-dev-qa-db-fra.com

Ajouter des éléments et interroger le trousseau iOS avec Swift

J'ai du mal à convertir tous les Objective C exemples de code disponibles pour ajouter des données et interroger des données à partir de iOS Keychain dans Swift . J'essaie de faire un stockage de base d'une chaîne (un jeton d'accès) et de le relire. J'ai jeté un œil à certaines des autres questions sur Stack Overflow, mais je n'arrive pas à le faire fonctionner. J'ai essayé de reconstituer une solution à partir des différentes sources.

Édition 1: J'ai essayé avec une configuration plus basique, parce que je pensais que mon self.defaultKeychainQuery aurait pu gâcher les choses. J'ai mis à jour le code ci-dessous vers la dernière version.

Édition 2: Je l'ai fait fonctionner. Je n'ajoutais pas correctement la valeur des données à la requête de sauvegarde. J'avais besoin de convertir la chaîne en NSData. J'ai mis à jour le code ci-dessous vers la version de travail la plus récente.

Édition 3: Comme le souligne Xerxes ci-dessous, ce code ne fonctionne pas avec les versions Xcode supérieures à la bêta 1 en raison d'un problème avec les dictionnaires. Si vous connaissez un correctif pour cela, veuillez me le faire savoir.

Mise à jour: j'ai transformé cela en bibliothèque de porte-clés écrite en Swift appelé Serrurier .


Enregistrer

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

Charger

  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
  }

Utilisation (contrôleur de vue)

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

qui utilise ces méthodes pratiques

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

Cela conduit à la sortie dans la console:

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

Merci beaucoup pour votre aide. Je ne sais pas trop quoi faire avec dataTypeRef une fois que je l'ai, ou s'il contient des données étant donné le code ci-dessus.

20
matthewpalmer

Pour que cela fonctionne, vous devrez récupérer les valeurs conservées des constantes du trousseau et les stocker ensuite comme suit:

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

Vous pouvez ensuite référencer les valeurs dans NSMutableDictionary comme ceci:

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

J'ai écrit un blog à ce sujet sur: http://rshelby.com/2014/08/using-Swift-to-save-and-query-ios-keychain-in-xcode-beta-4/

J'espère que cela t'aides!

rshelby

7
user3927134

J'ai écrit une application de démonstration et des fonctions d'assistance pour cette tâche simple: écrire/lire une chaîne de texte pour une clé donnée dans le trousseau.

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

Mon interprétation sur la façon d'ajouter, d'obtenir, de supprimer des mots de passe (pour ceux qui sont paresseux d'utiliser les bibliothèques présentées dans ce fil):

// 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)")

Les codes de résultat et d'autres informations utiles peuvent être trouvés dans la documentation officielle: https://developer.Apple.com/library/ios/documentation/Security/Reference/keychainservices/

3
Vitalii

Pour Swift utilisateurs

Code de ligne unique pour ajouter/récupérer/mettre à jour des champs dans le trousseau:
https://github.com/jrendel/SwiftKeychainWrapper

Utilisation

Ajoutez une valeur de chaîne au trousseau:

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

Récupérez une valeur de chaîne à partir du trousseau:

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

Supprimez une valeur de chaîne du trousseau:

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

Je pense que j'ai trouvé la solution. J'ai modifié mon article ci-dessus pour inclure le code qui fonctionne (au moins pour moi). J'ai également blogué à ce sujet ici: en utilisant le trousseau iOS avec Swift (exemple de code) .

Mise à jour du 11 août: j'ai mis à jour le code dans le billet de blog en fonction des commentaires de rshelby. Jetez un oeil.

Mise à jour: j'ai transformé cela en bibliothèque de porte-clés écrite en Swift appelé Serrurier .


Enregistrer

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

Charger

  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
  }

Utilisation (voir contrôleur)

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

qui utilise ces méthodes pratiques

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

Cela conduit à la sortie dans la console:

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