Recherche…


Syntaxe

  • kSecClassGenericPassword // Une clé de valeur représentant un mot de passe non Internet
  • kSecClassInternetPassword // Une clé de valeur représentant un mot de passe Internet
  • kSecClassCertificate // Une clé de valeur représentant un certificat
  • kSecClassCertificate // Une clé de valeur représentant une clé
  • kSecClassIdentity // Une clé de valeur représentant une identité, qui est un certificat plus une clé

Remarques

iOS stocke des informations privées telles que des mots de passe, des clés de chiffrement, des certificats et des identités dans une zone de stockage sécurisée appelée porte-clé. Cette zone de stockage est entièrement gérée par un co-processeur appelé Secure Enclave, qui est intégré au processeur d'application. Étant donné que le trousseau est mis en sandbox sur iOS, les éléments de trousseau ne peuvent être récupérés que par l'application qui les a placés en premier lieu.

Dans certains cas, vous devez activer le partage de trousseau dans les fonctionnalités Xcode afin d'éviter les erreurs.

Pour interagir avec le trousseau, nous utilisons le framework ac appelé Keychain Services. Pour plus d'informations, consultez le Guide de programmation des services de trousseau d'Apple .

Étant donné que les services de trousseau sont inférieurs au niveau Foundation , ils sont limités à l'utilisation des types CoreFoundation . Par conséquent, la plupart des objets sont représentés en interne en tant que CFDictionary qui CFString conserver CFString s en tant que leurs clés et divers types de CoreFoundation tant que valeurs.

Bien que les services de trousseau soient inclus dans le cadre de la Security , l'importation de Foundation est généralement une bonne option, car elle inclut des fonctions d'assistance dans le backend.

De plus, si vous ne souhaitez pas gérer directement les services de trousseau, Apple fournit le projet exemple Generic Keychain Swift qui fournit les types Swift qui utilisent les services de trousseau dans les coulisses.

Ajouter un mot de passe au trousseau

Chaque élément de trousseau est le plus souvent représenté comme un CFDictionary . Vous pouvez, cependant, simplement utiliser NSDictionary dans Objective-C et profiter du pontage ou, dans Swift, vous pouvez utiliser Dictionary et le CFDictionary explicitement dans CFDictionary .

Vous pouvez créer un mot de passe avec le dictionnaire suivant:

Rapide

var dict = [String : AnyObject]()

Tout d'abord, vous avez besoin d'une paire clé / valeur permettant au porte-clés de savoir qu'il s'agit d'un mot de passe. Notez que parce que notre clé dict est une String nous devons CFString tout CFString en String explicitement dans Swift 3. CFString ne peut pas être utilisé comme clé dans un dictionnaire Swift car il n'est pas lavable.

Rapide

dict[kSecClass as String] = kSecClassGenericPassword

Ensuite, notre mot de passe peut avoir une série d'attributs pour le décrire et nous aider à le retrouver plus tard. Voici une liste d'attributs pour les mots de passe génériques .

Rapide

// The password will only be accessible when the device is unlocked
dict[kSecAttrAccessible as String] = kSecAttrAccessibleWhenUnlocked
// Label may help you find it later
dict[kSecAttrLabel as String] = "com.me.myapp.myaccountpassword" as CFString
// Username
dict[kSecAttrAccount as String] = "My Name" as CFString
// Service name
dict[kSecAttrService as String] = "MyService" as CFString

Enfin, nous avons besoin de nos données privées réelles. Veillez à ne pas garder cela en mémoire trop longtemps. Ce doit être CFData .

Rapide

dict[kSecValueData as String] = "my_password!!".data(using: .utf8) as! CFData

Enfin, la fonction d'ajout de Keychain Services veut savoir comment renvoyer l'élément de trousseau nouvellement construit. Comme vous ne devriez pas conserver les données très longtemps en mémoire, voici comment vous ne pouvez retourner que les attributs:

Rapide

dict[kSecReturnAttributes as String] = kCFBooleanTrue

Maintenant, nous avons construit notre article. Ajoutons-le:

Rapide

var result: AnyObject?
let status = withUnsafeMutablePointer(to: &result) {
    SecItemAdd(dict as CFDictionary, UnsafeMutablePointer($0))
}
let newAttributes = result as! Dictionary<String, AnyObject>

Cela place les nouveaux attributs dans le result . SecItemAdd prend en compte le dictionnaire que nous avons SecItemAdd , ainsi qu'un pointeur vers l'endroit où nous souhaiterions obtenir notre résultat. La fonction retourne alors un OSStatus indiquant le succès ou un code d'erreur. Les codes de résultat sont décrits ici .

Trouver un mot de passe dans le trousseau

Pour construire une requête, nous devons la représenter en tant que CFDictionary . Vous pouvez également utiliser NSDictionary dans Objective-C ou Dictionary dans Swift et le CFDictionary en CFDictionary .

Nous avons besoin d'une clé de classe:

Rapide

var dict = [String : AnyObject]()
dict[kSecClass as String] = kSecClassGenericPassword

Ensuite, nous pouvons spécifier des attributs pour affiner notre recherche:

Rapide

// Label
dict[kSecAttrLabel as String] = "com.me.myapp.myaccountpassword" as CFString
// Username
dict[kSecAttrAccount as String] = "My Name" as CFString
// Service name
dict[kSecAttrService as String] = "MyService" as CFString

Nous pouvons également spécifier des touches de modification de recherche spéciales décrites ici .

Enfin, nous devons dire comment nous aimerions que nos données soient retournées. Ci-dessous, nous vous demanderons de ne retourner que le mot de passe privé en tant qu'objet CFData :

Rapide

dict[kSecReturnData as String] = kCFBooleanTrue

Maintenant, cherchons:

Rapide

var queryResult: AnyObject?
let status = withUnsafeMutablePointer(to: &queryResult) {
    SecItemCopyMatching(dict as CFDictionary, UnsafeMutablePointer($0))
}
// Don't keep this in memory for long!!
let password = String(data: queryResult as! Data, encoding: .utf8)!

SecItemCopyMatching prend ici un dictionnaire de requêtes et un pointeur vers l'emplacement SecItemCopyMatching . Il retourne un OSStatus avec un code de résultat. Voici les possibilités.

Mise à jour d'un mot de passe dans le trousseau

Comme d'habitude, nous avons d'abord besoin d'un CFDictionary pour représenter l'élément que nous voulons mettre à jour. Cela doit contenir toutes les anciennes valeurs de l'élément, y compris les anciennes données privées. Ensuite, il faut un CFDictionary de tous les attributs ou des données que vous souhaitez modifier.

Donc, tout d'abord, construisons une clé de classe et une liste d'attributs. Ces attributs peuvent restreindre notre recherche, mais vous devez inclure tous les attributs et les anciennes valeurs si vous les modifiez.

Rapide

var dict = [String : AnyObject]()
dict[kSecClass as String] = kSecClassGenericPassword
// Label
dict[kSecAttrLabel as String] = "com.me.myapp.myaccountpassword" as CFString
// Username
dict[kSecAttrAccount as String] = "My Name" as CFString

Maintenant, nous devons ajouter les anciennes données:

Rapide

dict[kSecValueData as String] = "my_password!!".data(using: .utf8) as! CFData

Maintenant, créons les mêmes attributs, mais un mot de passe différent:

Rapide

var newDict = [String : AnyObject]()
newDict[kSecClass as String] = kSecClassGenericPassword
// Label
newDict[kSecAttrLabel as String] = "com.me.myapp.myaccountpassword" as CFString
// Username
newDict[kSecAttrAccount as String] = "My Name" as CFString
// New password
newDict[kSecValueData as String] = "new_password!!".data(using: .utf8) as! CFData

Maintenant, nous passons juste à Keychain Services:

Rapide

let status = SecItemUpdate(dict as CFDictionary, newDict as CFDictionary)

SecItemUpdate renvoie un code d'état. Les résultats sont décrits ici .

Supprimer un mot de passe du trousseau

Nous n'avons besoin que d'une chose pour supprimer un élément du trousseau: un CFDictionary avec des attributs décrivant les éléments à supprimer. Tous les éléments correspondant au dictionnaire de requête seront supprimés de manière permanente. Si vous ne souhaitez supprimer qu'un seul élément, veillez à être spécifique à votre requête. Comme toujours, nous pouvons utiliser un NSDictionary en Objective-C ou, dans Swift, nous pouvons utiliser un Dictionary puis le CFDictionary en CFDictionary .

Un dictionnaire de requêtes, dans ce contexte, comprend exclusivement une clé de classe pour décrire l'objet et les attributs pour décrire des informations sur l'élément. L'inclusion de restrictions de recherche telles que kSecMatchCaseInsensitive n'est pas autorisée.

Rapide

var dict = [String : AnyObject]()
dict[kSecClass as String] = kSecClassGenericPassword
// Label
dict[kSecAttrLabel as String] = "com.me.myapp.myaccountpassword" as CFString
// Username
dict[kSecAttrAccount as String] = "My Name" as CFString

Et maintenant, nous pouvons simplement le supprimer:

Rapide

let status = SecItemDelete(dict as CFDictionary)

SecItemDelete renvoie un OSStatus . Les codes de résultat sont décrits ici .

Keychain Ajouter, mettre à jour, supprimer et rechercher des opérations en utilisant un fichier.

Porte-clés.h

#import <Foundation/Foundation.h>
typedef void (^KeychainOperationBlock)(BOOL successfulOperation, NSData *data, OSStatus status);

@interface Keychain : NSObject

-(id) initWithService:(NSString *) service_ withGroup:(NSString*)group_;

-(void)insertKey:(NSString *)key withData:(NSData *)data withCompletion:(KeychainOperationBlock)completionBlock;
-(void)updateKey:(NSString*)key withData:(NSData*) data withCompletion:(KeychainOperationBlock)completionBlock;
-(void)removeDataForKey:(NSString*)key withCompletionBlock:(KeychainOperationBlock)completionBlock;
-(void)findDataForKey:(NSString*)key withCompletionBlock:(KeychainOperationBlock)completionBlock;

@end

Porte-clés.m

#import "Keychain.h"
#import <Security/Security.h>

@implementation Keychain

{
    NSString * keychainService;
    NSString * keychainGroup;
}

-(id) initWithService:(NSString *)service withGroup:(NSString*)group
{
    self =[super init];
    if(self) {
        keychainService = [NSString stringWithString:service];
        if(group) {
            keychainGroup = [NSString stringWithString:group];
        }
    }
    
    return  self;
}

-(void)insertKey:(NSString *)key
        withData:(NSData *)data
  withCompletion:(KeychainOperationBlock)completionBlock
{
    NSMutableDictionary * dict =[self prepareDict:key];
    [dict setObject:data forKey:(__bridge id)kSecValueData];
    [dict setObject:keychainService forKey:(id)kSecAttrService];
    
    OSStatus status = SecItemAdd((__bridge CFDictionaryRef)dict, NULL);
    if(errSecSuccess != status) {
        DLog(@"Unable add item with key =%@ error:%d",key,(int)status);
        if (completionBlock) {
            completionBlock(errSecSuccess == status, nil, status);
        }
    }
    if (status == errSecDuplicateItem) {
        [self updateKey:key withData:data withCompletion:^(BOOL successfulOperation, NSData *updateData, OSStatus updateStatus) {
            if (completionBlock) {
                completionBlock(successfulOperation, updateData, updateStatus);
            }
            DLog(@"Found duplication item -- updating key with data");
        }];
    }
}

-(void)findDataForKey:(NSString *)key
  withCompletionBlock:(KeychainOperationBlock)completionBlock
{
    NSMutableDictionary *dict = [self prepareDict:key];
    [dict setObject:(__bridge id)kSecMatchLimitOne forKey:(__bridge id)kSecMatchLimit];
    [dict setObject:keychainService forKey:(id)kSecAttrService];
    [dict setObject:(id)kCFBooleanTrue forKey:(__bridge id)kSecReturnData];
    CFTypeRef result = NULL;
    OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)dict,&result);
    
    if( status != errSecSuccess) {
        DLog(@"Unable to fetch item for key %@ with error:%d",key,(int)status);
        if (completionBlock) {
            completionBlock(errSecSuccess == status, nil, status);
        }
    } else {
        if (completionBlock) {
            completionBlock(errSecSuccess == status, (__bridge NSData *)result, status);
        }
    }
}

-(void)updateKey:(NSString *)key
        withData:(NSData *)data
  withCompletion:(KeychainOperationBlock)completionBlock
{
    NSMutableDictionary * dictKey =[self prepareDict:key];
    
    NSMutableDictionary * dictUpdate =[[NSMutableDictionary alloc] init];
    [dictUpdate setObject:data forKey:(__bridge id)kSecValueData];
    [dictUpdate setObject:keychainService forKey:(id)kSecAttrService];
    OSStatus status = SecItemUpdate((__bridge CFDictionaryRef)dictKey, (__bridge CFDictionaryRef)dictUpdate);
    if( status != errSecSuccess) {
        DLog(@"Unable to remove item for key %@ with error:%d",key,(int)status);
    }
    if (completionBlock) {
        completionBlock(errSecSuccess == status, nil, status);
    }
}

-(void)removeDataForKey:(NSString *)key
    withCompletionBlock:(KeychainOperationBlock)completionBlock {
    NSMutableDictionary *dict = [self prepareDict:key];
    OSStatus status = SecItemDelete((__bridge CFDictionaryRef)dict);
    if( status != errSecSuccess) {
        DLog(@"Unable to remove item for key %@ with error:%d",key,(int)status);
    }
    if (completionBlock) {
        completionBlock(errSecSuccess == status, nil, status);
    }
}

#pragma mark Internal methods

-(NSMutableDictionary*) prepareDict:(NSString *) key {
    
    NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
    [dict setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass];
    
    NSData *encodedKey = [key dataUsingEncoding:NSUTF8StringEncoding];
    [dict setObject:encodedKey forKey:(__bridge id)kSecAttrGeneric];
    [dict setObject:encodedKey forKey:(__bridge id)kSecAttrAccount];
    [dict setObject:keychainService forKey:(__bridge id)kSecAttrService];
    [dict setObject:(__bridge id)kSecAttrAccessibleAlwaysThisDeviceOnly forKey:(__bridge id)kSecAttrAccessible];
    
    //This is for sharing data across apps
    if(keychainGroup != nil) {
        [dict setObject:keychainGroup forKey:(__bridge id)kSecAttrAccessGroup];
    }
    
    return  dict;
}

@end

Keychain Access Control (TouchID avec retour de mot de passe)

Le trousseau permet de sauvegarder des éléments avec un attribut spécial SecAccessControl qui permettra d'obtenir un élément du trousseau uniquement après que l'utilisateur aura été authentifié avec Touch ID (ou un mot de passe si un tel retour est autorisé). App est seulement averti si l'authentification a réussi ou non, l'interface utilisateur entière est gérée par iOS.

Tout d'abord, l'objet SecAccessControl doit être créé:

Rapide

let error: Unmanaged<CFError>?

guard let accessControl = SecAccessControlCreateWithFlags(kCFAllocatorDefault, kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly, .userPresence, &error) else {
    fatalError("Something went wrong")
}

Ensuite, ajoutez-la au dictionnaire avec la clé kSecAttrAccessControl (qui est mutuellement exclusive avec la clé kSecAttrAccessible que vous avez utilisée dans d'autres exemples):

Rapide

var dictionary = [String : Any]()

dictionary[kSecClass as String] = kSecClassGenericPassword
dictionary[kSecAttrLabel as String] = "com.me.myapp.myaccountpassword" as CFString
dictionary[kSecAttrAccount as String] = "My Name" as CFString
dictionary[kSecValueData as String] = "new_password!!".data(using: .utf8) as! CFData
dictionary[kSecAttrAccessControl as String] = accessControl

Et sauvegardez-le comme vous l'avez fait auparavant:

Rapide

let lastResultCode = SecItemAdd(query as CFDictionary, nil)

Pour accéder aux données stockées, interrogez simplement Keychain pour obtenir une clé. Keychain Services présentera la boîte de dialogue d'authentification à l'utilisateur et retournera des données ou des données nulles selon que l'empreinte digitale appropriée a été fournie ou que le code d'accès a été apparié.

En option, une chaîne d'invite peut être spécifiée:

Rapide

var query = [String: Any]()

query[kSecClass as String] = kSecClassGenericPassword
query[kSecReturnData as String] = kCFBooleanTrue
query[kSecAttrAccount as String] = "My Name" as CFString
query[kSecAttrLabel as String] = "com.me.myapp.myaccountpassword" as CFString
query[kSecUseOperationPrompt as String] = "Please put your fingers on that button" as CFString

var queryResult: AnyObject?
let status = withUnsafeMutablePointer(to: &queryResult) {
    SecItemCopyMatching(query as CFDictionary, UnsafeMutablePointer($0))
}

Faites attention que le status sera err si l'utilisateur a refusé, annulé ou échoué l'autorisation.

Rapide

if status == noErr {
    let password = String(data: queryResult as! Data, encoding: .utf8)!
    print("Password: \(password)")
} else {
    print("Authorization not passed")
}


Modified text is an extract of the original Stack Overflow Documentation
Sous licence CC BY-SA 3.0
Non affilié à Stack Overflow