Поиск…


Синтаксис

  • kSecClassGenericPassword // Ключ значения, представляющий не-интернет-пароль
  • kSecClassInternetPassword // Ключ значения, представляющий пароль в Интернете
  • kSecClassCertificate // Ключ значения, представляющий сертификат
  • kSecClassCertificate // Ключ значения, представляющий ключ
  • kSecClassIdentity // Ключ значения, представляющий идентификатор, который является сертификатом плюс ключ

замечания

iOS хранит конфиденциальную информацию, такую ​​как пароли, ключи шифрования, сертификаты и удостоверения в защищенной области хранения, называемой Keychain. Эта область хранения полностью управляется сопроцессором, называемым Secure Enclave, который встроен в процессор приложений. Поскольку Keychain изолирован в iOS, элементы keychain могут извлекаться только приложением, которое их там помещает в первую очередь.

В некоторых случаях вы должны включить совместное использование ключей в возможностях Xcode, чтобы избежать ошибок.

Чтобы взаимодействовать с цепочкой ключей, мы используем инфраструктуру ac, называемую Keychain Services. Для получения дополнительной информации см. Руководство по программированию устройств Keychain для Apple .

Поскольку Keychain Services находится ниже уровня Foundation , он ограничен использованием типов CoreFoundation . В результате большинство объектов внутренне представлены как CFDictionary s, где CFString s являются их ключами и различными типами CoreFoundation качестве их значений.

Хотя службы Keychain Services включены как часть структуры Security , импорт Foundation обычно является хорошим вариантом, поскольку он включает некоторые вспомогательные функции в бэкэнд.

Кроме того, если вы не хотите напрямую обращаться к службам Keychain, Apple предоставляет примерный проект Generic Keychain Swift, который предоставляет типы Swift, которые используют функции Keychain Services за кулисами.

Добавление пароля в брелок

Каждый элемент CFDictionary чаще всего представлен как CFDictionary . Вы можете, однако, просто использовать NSDictionary в Objective-C и воспользоваться мостом, или в Swift вы можете использовать Dictionary и явно бросать в CFDictionary .

Вы можете создать пароль со следующим словарем:

стриж

var dict = [String : AnyObject]()

Во-первых, вам нужна пара ключ / значение, которая позволяет Keychain знать, что это пароль. Обратите внимание, что поскольку наша клавиша dict - это String мы должны CFString любой CFString в String в Swift 3. CFString не может использоваться как ключ к Swift Dictionary, потому что это не Hashable.

стриж

dict[kSecClass as String] = kSecClassGenericPassword

Далее, наш пароль может иметь ряд атрибутов, чтобы описать его и помочь нам найти его позже. Вот список атрибутов для общих паролей .

стриж

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

Наконец, нам нужны наши фактические данные. Не задерживайте это слишком долго в памяти. Это должно быть CFData .

стриж

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

Наконец, функция добавления ключей Keychain Services хочет знать, как она должна возвращать вновь созданный элемент keychain. Поскольку вы не должны долго держаться за данные в памяти, вот как вы можете вернуть атрибуты только:

стриж

dict[kSecReturnAttributes as String] = kCFBooleanTrue

Теперь мы построили наш предмет. Добавим:

стриж

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

Это помещает новые атрибуты dict внутри result . SecItemAdd принимает словарь, который мы построили, а также указатель на то, где мы хотим получить наш результат. Затем функция возвращает OSStatus указанием успеха или кода ошибки. Коды результатов описаны здесь .

Поиск пароля в брелках

Чтобы построить запрос, мы должны представить его как CFDictionary . Вы также можете использовать NSDictionary в Objective-C или Dictionary в Swift и отбрасывать в CFDictionary .

Нам нужен ключ класса:

стриж

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
// Service name
dict[kSecAttrService as String] = "MyService" as CFString

Мы также можем указать специальные клавиши-модификаторы поиска, описанные здесь .

Наконец, мы должны сказать, как мы хотим вернуть наши данные. Ниже мы CFData чтобы только частный пароль был возвращен как объект CFData :

стриж

dict[kSecReturnData as String] = kCFBooleanTrue

Теперь давайте посмотрим:

стриж

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 принимает словарь запросов и указатель на то, куда вы хотите, чтобы результат был выполнен. Он возвращает OSStatus с кодами результатов. Вот возможности.

Обновление пароля в цепочке ключей

Как обычно, сначала нам нужен CFDictionary для представления элемента, который мы хотим обновить. Это должно содержать все старые значения для элемента, включая старые личные данные. Затем требуется CFDictionary любых атрибутов или самих данных, которые вы хотели бы изменить.

Итак, сначала создадим ключ класса и список атрибутов. Эти атрибуты могут сузить наш поиск, но вы должны включать в себя любые атрибуты и старые значения, если вы их измените.

стриж

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

Теперь мы должны добавить старые данные:

стриж

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

Теперь давайте создадим те же атрибуты, но другой пароль:

стриж

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

Теперь мы просто передаем его в службы Keychain:

стриж

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

SecItemUpdate возвращает код состояния. Результаты описаны здесь .

Удаление пароля из брелка

Нам нужно только одно, чтобы удалить элемент из Keychain: CFDictionary с атрибутами, описывающими элементы, которые нужно удалить. Любые элементы, соответствующие поисковому словарю, будут удалены навсегда, поэтому, если вы намерены удалить только один элемент, обязательно укажите его в своем запросе. Как всегда, мы можем использовать NSDictionary в Objective-C или в Swift, мы можем использовать Dictionary а затем бросать в CFDictionary .

Словарь запросов в этом контексте включает исключительно ключ класса, чтобы описать, что такое элемент, и атрибуты для описания информации об элементе. Включение ограничений поиска, таких как kSecMatchCaseInsensitive , не допускается.

стриж

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

И теперь мы можем просто удалить его:

стриж

let status = SecItemDelete(dict as CFDictionary)

SecItemDelete возвращает OSStatus . Коды результатов описаны здесь .

Keychain Добавить, обновить, удалить и найти операции с использованием одного файла.

Keychain.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

Keychain.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 (TouchID с возвратом пароля)

Keychain позволяет сохранять элементы со специальным атрибутом SecAccessControl, который позволит получить элемент из Keychain только после того, как пользователь будет аутентифицирован с помощью Touch ID (или кода доступа, если такой резерв разрешен). Приложение уведомляется только о том, была ли аутентификация успешной или нет, весь пользовательский интерфейс управляется iOS.

Сначала необходимо создать объект SecAccessControl:

стриж

let error: Unmanaged<CFError>?

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

Затем добавьте его в словарь с ключом kSecAttrAccessControl (который является взаимоисключающим с ключом kSecAttrAccessible, который вы использовали в других примерах):

стриж

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

И сохраните его, как вы это делали раньше:

стриж

let lastResultCode = SecItemAdd(query as CFDictionary, nil)

Чтобы получить доступ к сохраненным данным, просто запросите Keychain для ключа. Служба Keychain Services представит пользователю диалог аутентификации и вернет данные или нуль в зависимости от того, был ли предоставлен подходящий отпечаток или совпадение кода доступа.

Необязательно, строка подсказки может быть указана:

стриж

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

Обратите внимание, что status будет err если пользователь отклонил, отменил или отказал авторизацию.

стриж

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
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow