56 votes

Sauvegarde d'un email/mot de passe dans le trousseau sous iOS

Je suis très novice en matière de développement iOS, alors pardonnez-moi si c'est une question de débutant. J'ai un mécanisme d'authentification simple pour mon application qui prend l'adresse email et le mot de passe de l'utilisateur. J'ai aussi un interrupteur qui dit "Se souvenir de moi". Si l'utilisateur active cet interrupteur, j'aimerais conserver son adresse électronique et son mot de passe afin que ces champs puissent être remplis automatiquement à l'avenir.

J'ai réussi à le faire fonctionner en sauvegardant dans un fichier plist mais je sais que ce n'est pas la meilleure idée puisque le mot de passe n'est pas crypté. J'ai trouvé un exemple de code pour enregistrer dans le trousseau, mais pour être honnête, je suis un peu perdu. Pour la fonction ci-dessous, je ne suis pas sûr de savoir comment l'appeler et comment la modifier pour qu'elle enregistre également l'adresse email.

Je suppose qu'il faudrait l'appeler ainsi : saveString(@"passwordgoeshere");

Merci de votre aide !!!

+ (void)saveString:(NSString *)inputString forKey:(NSString *)account {

    NSAssert(account != nil, @"Invalid account");
    NSAssert(inputString != nil, @"Invalid string");

    NSMutableDictionary *query = [NSMutableDictionary dictionary];

    [query setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass];
    [query setObject:account forKey:(id)kSecAttrAccount];
    [query setObject:(id)kSecAttrAccessibleWhenUnlocked forKey:(id)kSecAttrAccessible];

    OSStatus error = SecItemCopyMatching((CFDictionaryRef)query, NULL);
    if (error == errSecSuccess) {
        // do update
        NSDictionary *attributesToUpdate = [NSDictionary dictionaryWithObject:[inputString dataUsingEncoding:NSUTF8StringEncoding] 
                                                                      forKey:(id)kSecValueData];

        error = SecItemUpdate((CFDictionaryRef)query, (CFDictionaryRef)attributesToUpdate);
        NSAssert1(error == errSecSuccess, @"SecItemUpdate failed: %d", error);
    } else if (error == errSecItemNotFound) {
        // do add
        [query setObject:[inputString dataUsingEncoding:NSUTF8StringEncoding] forKey:(id)kSecValueData];

        error = SecItemAdd((CFDictionaryRef)query, NULL);
        NSAssert1(error == errSecSuccess, @"SecItemAdd failed: %d", error);
    } else {
        NSAssert1(NO, @"SecItemCopyMatching failed: %d", error);
    }
}

5 votes

J'ai corrigé le code de @Anomie pour qu'il fonctionne avec ARC et je l'ai mis sur Github (j'ai fait un lien vers cette réponse et mentionné votre utilisateur dans les deux fichiers, mais si vous voulez d'autres attributions, faites-le moi savoir). J'ai également modifié un peu le formatage et rendu les noms de méthodes un peu plus génériques. github.com/jeremangnr/JNKeychain

101voto

Anomie Points 43759

J'ai écrit un simple wrapper qui permet d'enregistrer n'importe quel objet compatible NSCoding dans le trousseau. Vous pouvez, par exemple, stocker votre email et votre mot de passe dans un NSDictionary et enregistrer le NSDictionary dans le trousseau à l'aide de cette classe.

SimpleKeychain.h

#import <Foundation/Foundation.h>

@class SimpleKeychainUserPass;

@interface SimpleKeychain : NSObject

+ (void)save:(NSString *)service data:(id)data;
+ (id)load:(NSString *)service;
+ (void)delete:(NSString *)service;

@end

SimpleKeychain.m

#import "SimpleKeychain.h"

@implementation SimpleKeychain

+ (NSMutableDictionary *)getKeychainQuery:(NSString *)service {
    return [NSMutableDictionary dictionaryWithObjectsAndKeys:
            (id)kSecClassGenericPassword, (id)kSecClass,
            service, (id)kSecAttrService,
            service, (id)kSecAttrAccount,
            (id)kSecAttrAccessibleAfterFirstUnlock, (id)kSecAttrAccessible,
            nil];
}

+ (void)save:(NSString *)service data:(id)data {
    NSMutableDictionary *keychainQuery = [self getKeychainQuery:service];
    SecItemDelete((CFDictionaryRef)keychainQuery);
    [keychainQuery setObject:[NSKeyedArchiver archivedDataWithRootObject:data] forKey:(id)kSecValueData];
    SecItemAdd((CFDictionaryRef)keychainQuery, NULL);
}

+ (id)load:(NSString *)service {
    id ret = nil;
    NSMutableDictionary *keychainQuery = [self getKeychainQuery:service];
    [keychainQuery setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnData];
    [keychainQuery setObject:(id)kSecMatchLimitOne forKey:(id)kSecMatchLimit];
    CFDataRef keyData = NULL;
    if (SecItemCopyMatching((CFDictionaryRef)keychainQuery, (CFTypeRef *)&keyData) == noErr) {
        @try {
            ret = [NSKeyedUnarchiver unarchiveObjectWithData:(NSData *)keyData];
        }
        @catch (NSException *e) {
            NSLog(@"Unarchive of %@ failed: %@", service, e);
        }
        @finally {}
    }
    if (keyData) CFRelease(keyData);
    return ret;
}

+ (void)delete:(NSString *)service {
    NSMutableDictionary *keychainQuery = [self getKeychainQuery:service];
    SecItemDelete((CFDictionaryRef)keychainQuery);
}

@end

0 votes

@abe : Essayez et vous verrez ? D'après la documentation, toutes les mêmes fonctions sont disponibles, mais c'est possible getKeychainQuery: doit contenir des paramètres supplémentaires dans le dictionnaire.

0 votes

@Anomie J'ai essayé et j'ai trouvé des erreurs sur les valeurs, dont kSecClassGenericPassword, mais j'ai vérifié la documentation, il semble qu'il y ait un support dans foundation framework, j'ai aussi inclus security framework, des idées sur la raison de l'erreur ?

0 votes

Il peut être utile d'utiliser kSecAttrAccessibleWhenUnlockedThisDeviceOnly (par opposition à kSecAttrAccessibleAfterFirstUnlock) dans un exemple susceptible d'être copié par un grand nombre de personnes.

39voto

Vlad Spreys Points 1073

Code ARC ready :

KeychainUserPass.h

#import <Foundation/Foundation.h>

@interface KeychainUserPass : NSObject

+ (void)save:(NSString *)service data:(id)data;
+ (id)load:(NSString *)service;
+ (void)delete:(NSString *)service;

@end

KeychainUserPass.m

#import "KeychainUserPass.h"

@implementation KeychainUserPass

+ (NSMutableDictionary *)getKeychainQuery:(NSString *)service {
    return [NSMutableDictionary dictionaryWithObjectsAndKeys:
            (__bridge id)kSecClassGenericPassword, (__bridge id)kSecClass,
            service, (__bridge id)kSecAttrService,
            service, (__bridge id)kSecAttrAccount,
            (__bridge id)kSecAttrAccessibleAfterFirstUnlock, (__bridge id)kSecAttrAccessible,
            nil];
}

+ (void)save:(NSString *)service data:(id)data {
    NSMutableDictionary *keychainQuery = [self getKeychainQuery:service];
    SecItemDelete((__bridge CFDictionaryRef)keychainQuery);
    [keychainQuery setObject:[NSKeyedArchiver archivedDataWithRootObject:data] forKey:(__bridge id)kSecValueData];
    SecItemAdd((__bridge CFDictionaryRef)keychainQuery, NULL);
}

+ (id)load:(NSString *)service {
    id ret = nil;
    NSMutableDictionary *keychainQuery = [self getKeychainQuery:service];
    [keychainQuery setObject:(id)kCFBooleanTrue forKey:(__bridge id)kSecReturnData];
    [keychainQuery setObject:(__bridge id)kSecMatchLimitOne forKey:(__bridge id)kSecMatchLimit];
    CFDataRef keyData = NULL;
    if (SecItemCopyMatching((__bridge CFDictionaryRef)keychainQuery, (CFTypeRef *)&keyData) == noErr) {
        @try {
            ret = [NSKeyedUnarchiver unarchiveObjectWithData:(__bridge NSData *)keyData];
        }
        @catch (NSException *e) {
            NSLog(@"Unarchive of %@ failed: %@", service, e);
        }
        @finally {}
    }
    if (keyData) CFRelease(keyData);
    return ret;
}

+ (void)delete:(NSString *)service {
    NSMutableDictionary *keychainQuery = [self getKeychainQuery:service];
    SecItemDelete((__bridge CFDictionaryRef)keychainQuery);
}

@end

0voto

Rulle Boer Points 1

Je ne pense pas que la méthode que vous proposez soit une bonne façon d'utiliser la fonction "Se souvenir de moi". Je pense qu'il est préférable de ne pas déconnecter l'utilisateur de son compte sur le serveur. Stocker un cookie côté client avec une valeur hachée, et l'envoyer à chaque appel au serveur. Vous devriez toujours faire cela à chaque appel au serveur de toute façon, par opposition à l'envoi de mots de passe stockés localement. Ne les stockez même pas dans des variables locales.

Si l'utilisateur souhaite stocker le mot de passe dans son trousseau, il s'agit d'une tâche totalement différente et indépendante de la fonction "Se souvenir de moi". Je crains que vous n'ayez confondu ces deux cas d'utilisation.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X