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