Sök…
Syntax
- kSecClassGenericPassword // En värdnyckel som representerar ett lösenord som inte är internet
- kSecClassInternetPassword // En värdeknapp som representerar ett internetlösenord
- kSecClassCertificate // En värdnyckel som representerar ett certifikat
- kSecClassCertificate // En värdeknapp som representerar en nyckel
- kSecClassIdentity // En värdnyckel som representerar en identitet, som är ett certifikat plus en nyckel
Anmärkningar
iOS lagrar privat information som lösenord, krypteringsnycklar, certifikat och identiteter i ett säkert lagringsområde som kallas Nyckelring. Detta lagringsområde hanteras fullständigt av en co-processor som kallas Secure Enclave, som är inbäddad i applikationsprocessorn. Eftersom Nyckelringen är sandlådad på iOS kan nyckelringsprodukter bara hämtas av applikationen som placerar dem där i första hand.
I vissa fall måste du aktivera delning av nyckelring i Xcode-funktioner för att undvika fel.
För att interagera med nyckelringen använder vi ett ac-ramverk som heter Keychain Services. Mer information finns i Apples programguide för Keychain Services .
Eftersom Keychain Services är under Foundation
nivån är det begränsat till att använda CoreFoundation
typer. Som ett resultat är de flesta objekt internt representerade som CFDictionary
håller CFString
som sina nycklar och olika CoreFoundation
typer som sina värden.
Medan Keychain Services ingår som en del av Security
är import av Foundation
vanligtvis ett bra alternativ eftersom det innehåller vissa hjälpfunktioner i backend.
Om du inte vill hantera Keychain Services direkt, tillhandahåller Apple det generella exempelprojektet för Keychain Swift som tillhandahåller Swift-typer som använder Keychain Services bakom kulisserna.
Lägga till ett lösenord i nyckelringen
Varje nyckelringsprodukt representeras oftast som en CFDictionary
. Du kan dock helt enkelt använda NSDictionary
i NSDictionary
-C och dra nytta av överbryggning, eller i Swift kan du använda Dictionary
och uttryckligen CFDictionary
till CFDictionary
.
Du kan skapa ett lösenord med följande ordlista:
Snabb
var dict = [String : AnyObject]()
Först behöver du ett nyckel- / värdepar som låter nyckelringen veta att det här är ett lösenord. Observera att eftersom vår dict-nyckel är en String
måste vi kasta någon CFString
till en String
uttryckligen i Swift 3. CFString kanske inte kan användas som nyckel till en Swift Dictionary eftersom den inte är Hashable.
Snabb
dict[kSecClass as String] = kSecClassGenericPassword
Därefter kan vårt lösenord ha en serie attribut för att beskriva det och hjälpa oss att hitta det senare. Här är en lista med attribut för generiska lösenord .
Snabb
// 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
Slutligen behöver vi våra faktiska privata uppgifter. Se till att inte hålla det här i minnet för länge. Detta måste vara CFData
.
Snabb
dict[kSecValueData as String] = "my_password!!".data(using: .utf8) as! CFData
Slutligen vill funktionen Lägg till nyckelringstjänster veta hur den ska returnera den nybyggda nyckelringsposten. Eftersom du inte borde hålla fast vid uppgifterna så länge i minnet, så här kan du bara returnera attributen:
Snabb
dict[kSecReturnAttributes as String] = kCFBooleanTrue
Nu har vi konstruerat vårt föremål. Låt oss lägga till det:
Snabb
var result: AnyObject?
let status = withUnsafeMutablePointer(to: &result) {
SecItemAdd(dict as CFDictionary, UnsafeMutablePointer($0))
}
let newAttributes = result as! Dictionary<String, AnyObject>
Detta lägger de nya attributen in i result
. SecItemAdd
tar in den ordlistan vi konstruerade, samt en pekare där vi vill ha vårt resultat. Funktionen returnerar sedan en OSStatus
indikerar framgång eller en felkod. Resultatkoder beskrivs här .
Hitta ett lösenord i nyckelringen
För att konstruera en fråga måste vi representera den som en CFDictionary
. Du kan också använda NSDictionary
i NSDictionary
-C eller Dictionary
i Swift och CFDictionary
till CFDictionary
.
Vi behöver en klassnyckel:
Snabb
var dict = [String : AnyObject]()
dict[kSecClass as String] = kSecClassGenericPassword
Därefter kan vi ange attribut för att begränsa vår sökning:
Snabb
// 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
Vi kan också specificera speciella sökmodifieringsnycklar som beskrivs här .
Slutligen måste vi säga hur vi vill att våra data ska returneras. Nedan kommer vi att begära att bara det privata lösenordet returneras som ett CFData
objekt:
Snabb
dict[kSecReturnData as String] = kCFBooleanTrue
Nu ska vi söka:
Snabb
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)!
Här tar SecItemCopyMatching
in en frågeförordlista och en pekare dit du vill att resultatet ska gå. Det returnerar en OSStatus
med en resultatkod. Här är möjligheterna.
Uppdatera ett lösenord i nyckelringen
Som vanligt behöver vi först en CFDictionary
att representera det objekt vi vill uppdatera. Detta måste innehålla alla gamla värden för objektet, inklusive gamla privata data. Sedan krävs det en CFDictionary
av alla attribut eller själva data som du vill ändra.
Så först ska vi konstruera en klassnyckel och en lista med attribut. Dessa attribut kan begränsa vår sökning men du måste inkludera alla attribut och där gamla värden om du kommer att ändra dem.
Snabb
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
Nu måste vi lägga till gamla data:
Snabb
dict[kSecValueData as String] = "my_password!!".data(using: .utf8) as! CFData
Låt oss nu skapa samma attribut men ett annat lösenord:
Snabb
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
Nu skickar vi det bara till Keychain Services:
Snabb
let status = SecItemUpdate(dict as CFDictionary, newDict as CFDictionary)
SecItemUpdate
returnerar en statuskod. Resultaten beskrivs här .
Ta bort ett lösenord från nyckelringen
Vi behöver bara en sak för att radera ett objekt från nyckelringen: en CFDictionary
med attribut som beskriver objekten som ska tas bort. Alla objekt som matchar frågeformuläret kommer att raderas permanent, så om du bara tänker ta bort ett enda objekt måste du vara specifik med din fråga. Som alltid kan vi använda en NSDictionary
i NSDictionary
-C eller i Swift kan vi använda en Dictionary
och sedan CFDictionary
till CFDictionary
.
En frågeformulär innehåller i detta sammanhang uteslutande en klassnyckel för att beskriva vad objektet är och attribut för att beskriva information om objektet. Inkludering av kSecMatchCaseInsensitive
som kSecMatchCaseInsensitive
är inte tillåtet.
Snabb
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
Och nu kan vi helt enkelt ta bort det:
Snabb
let status = SecItemDelete(dict as CFDictionary)
SecItemDelete
returnerar ett OSStatus
. Resultatkoder beskrivs här .
Nyckelring Lägg till, uppdatera, ta bort och hitta operationer med en fil.
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
Nyckelringskontroll (TouchID med lösenordsfall)
Nyckelring gör det möjligt att spara objekt med speciellt SecAccessControl-attribut som gör det möjligt att hämta objekt från nyckelring endast efter att användaren kommer att verifieras med Touch ID (eller lösenord om sådant fallback är tillåtet). App meddelas endast om autentiseringen var framgångsrik eller inte, hela UI hanteras av iOS.
Först ska SecAccessControl-objekt skapas:
Snabb
let error: Unmanaged<CFError>?
guard let accessControl = SecAccessControlCreateWithFlags(kCFAllocatorDefault, kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly, .userPresence, &error) else {
fatalError("Something went wrong")
}
Lägg sedan till den i ordboken med kSecAttrAccessControl-nyckel (som är ömsesidigt exklusivt med kSecAttrAccessible-nyckel som du har använt i andra exempel):
Snabb
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
Och spara det som du gjort tidigare:
Snabb
let lastResultCode = SecItemAdd(query as CFDictionary, nil)
För att få åtkomst till lagrade data, fråga bara nyckelring för en nyckel. Keychain Services kommer att presentera autentiseringsdialogrutan för användaren och returnera data eller noll beroende på om lämpligt fingeravtryck tillhandahölls eller lösenordet matchades.
Valfritt kan snabbsträng specificeras:
Snabb
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))
}
Var uppmärksam på att status
kommer att bli err
om användaren avböjde, annullerade eller misslyckades behörighet.
Snabb
if status == noErr {
let password = String(data: queryResult as! Data, encoding: .utf8)!
print("Password: \(password)")
} else {
print("Authorization not passed")
}