Varje app som sparar användarens data måste ta hand om säkerheten och integriteten för den dataen. Som vi har sett med de senaste dataöverträdelserna kan det vara mycket allvarliga konsekvenser för att inte skydda användarnas lagrade data. I den här handledningen lär du dig några bästa metoder för att skydda dina användares data.
I det föregående inlägget lärde du dig att skydda filer med hjälp av dataskydd API. Filbaserat skydd är en kraftfull funktion för säker lagring av massdata. Men det kan vara overkill för en liten mängd information för att skydda, till exempel en nyckel eller ett lösenord. För dessa typer av artiklar är nyckelring den rekommenderade lösningen.
Nyckeln är ett bra ställe att lagra mindre mängder information som känsliga strängar och ID-skivor som kvarstår även när användaren raderar appen. Ett exempel kan vara en enhet eller sessionstoken att din server återvänder till appen vid registrering. Oavsett om du kallar det en hemlig sträng eller unikt token, refererar nyckelringen till alla dessa saker som lösenord.
Det finns några populära tredjepartsbibliotek för nyckelringstjänster, till exempel Strongbox (Swift) och SSKeychain (Objective-C). Eller, om du vill ha fullständig kontroll över din egen kod, kan du önska att direkt använda Keychain Services API, vilket är ett C API.
Jag ska kort förklara hur nyckelring fungerar. Du kan tänka på nyckelring som en typisk databas där du kör frågor på ett bord. Funktionerna i nyckelringskedjan API kräver alla a CFDictionary
objekt som innehåller attribut för frågan.
Varje post i nyckelringen har ett servicenamn. Tjänstenamnet är en identifierare: a nyckel- för vad som helst värde du vill lagra eller hämta i nyckelringen. För att en nyckelkodspost ska kunna lagras endast för en specifik användare, vill du också ofta ange ett kontonamn.
Eftersom varje nyckelringfunktion tar en liknande ordbok med många av samma parametrar för att göra en fråga kan du undvika dubbla kod genom att göra en hjälpfunktion som returnerar denna frågeordlista.
Import Security // ... class func passwordQuery (service: String, konto: String) -> Ordboklet dictionary = [kSecClass as String: kSecClassGenericPassword, kSecAttrAccount as String: konto, kSecAttrService as String: service, kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlocked // Om det behövs åtkomst i bakgrunden, kanske du vill överväga kSecAttrAccessibleAfterFirstUnlock] som [String: Any] returordlista
Denna kod ställer in frågan Ordbok
med ditt konto- och servicenamn och berättar nyckeln som vi lagrar ett lösenord.
På samma sätt som hur du kan ställa in skyddsnivån för enskilda filer (som vi diskuterade i föregående inlägg), kan du också ställa in skyddsnivåerna för ditt nyckelringar med hjälp av kSecAttrAccessible
nyckel-.
De SecItemAdd ()
funktionen lägger till data i nyckelringen. Den här funktionen tar en Data
objekt, vilket gör den mångsidig för att lagra många föremål. Med hjälp av funktionen för lösenordsfrågan som vi skapade ovan, låt oss lagra en sträng i nyckelringen. För att göra detta måste vi bara konvertera Sträng
till Data
.
@discardableResult class func setPassword (_ lösenord: String, service: String, konto: String) -> Bool var status: OSStatus = -1 if! (service.isEmpty) &&! (account.isEmpty) deletePassword (service: service , konto: konto) // radera lösenord om du skickar en tom sträng. Kan ändras för att passera noll för att radera lösenord, etc om! Password.isEmpty var dictionary = passwordQuery (service: service, konto: konto) låt dataFromString = password.data (using: String.Encoding.utf8, allowLossyConversion: false) dictionary [ kSecValueData as String] = dataFromString status = SecItemAdd (ordbok som CFDictionary, nil) returstatus == errSecSuccess
För att förhindra dubbla insatser raderar koden ovan först den föregående inmatningen om det finns en. Låt oss skriva den funktionen nu. Detta uppnås med hjälp av SecItemDelete ()
fungera.
@discardableResult class func deletePassword (service: String, konto: String) -> Bool var status: OSStatus = -1 om! (service.isEmpty) &&! (account.isEmpty) let dictionary = passwordQuery (service: service, konto : konto) status = SecItemDelete (ordbok som CFDictionary); returstatus == errSecSuccess
För att hämta en post från nyckeln, använd sedan SecItemCopyMatching ()
fungera. Det kommer att returnera en AnyObject
som matchar din fråga.
class func lösenord (service: String, konto: String) -> String // returnera tom sträng om den inte hittades, kan returnera en valfri var status: OSStatus = -1 var resultString = "" if! (service.isEmpty) &&! (account.isEmpty) var passwordData: AnyObject? var dictionary = passwordQuery (service: service, konto: konto) ordbok [kSecReturnData as String] = kCFBooleanTrue ordbok [kSecMatchLimit as String] = kSecMatchLimitOne status = SecItemCopyMatching (ordbok som CFDictionary, och passwordData) om status == errSecSuccess om låt retrievedData = passwordData som? Data resultString = String (data: retrievedData, kodning: String.Encoding.utf8)! returresultatString
I denna kod ställer vi in kSecReturnData
parameter till kCFBooleanTrue
. kSecReturnData
betyder att faktiska uppgifter om objektet kommer att returneras. Ett annat alternativ kan vara att returnera attributen (kSecReturnAttributes
) av föremålet. Nyckeln tar en CFBoolean
typ som håller konstanterna kCFBooleanTrue
eller kCFBooleanFalse
. Vi ställer in kSecMatchLimit
till kSecMatchLimitOne
så att endast det första objektet i nyckelringen kommer att returneras, i motsats till ett obegränsat antal resultat.
Nyckeln är också den rekommenderade platsen för att lagra offentliga och privata nyckelföremål, till exempel om din app arbetar med och behöver lagra EC eller RSA SecKey
objekt.
Den största skillnaden är att istället för att berätta nyckeln för att lagra ett lösenord, kan vi berätta att den lagrar en nyckel. Faktum är att vi kan få specifika genom att ange vilka typer av nycklar som lagras, t.ex. om det är offentligt eller privat. Allt som behöver göras är att anpassa funktionshjälpsfunktionen för att fungera med vilken typ av nyckel du vill ha.
Nycklar identifieras generellt med hjälp av en omvänd domänmärkning, såsom com.mydomain.mykey istället för tjänste- och kontonamn (eftersom offentliga nycklar delas öppet mellan olika företag eller enheter). Vi tar service och konto strängar och konverterar dem till en tagg Data
objekt. Till exempel ovanstående kod anpassad för att lagra en RSA Private SecKey
skulle se ut så här:
class func keyQuery (service: String, konto: String) -> Ordboklet tagString = "com.mydomain." + service + "." + konto låt tag = tagString.data (använder: .utf8)! // Spara det som data, inte som en sträng låt ordbok = [kSecClass som sträng: kSecClassKey, kSecAttrKeyType som sträng: kSecAttrKeyTypeRSA, kSecAttrKeyClass som sträng: kSecAttrKeyClassPrivate, kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlocked, kSecAttrApplicationTag som String: tag] som [String: Any ] returordbok @discardableResultat klass func setKey (_ key: SecKey, service: String, konto: String) -> Bool var status: OSStatus = -1 om! (service.isEmpty) &&! (account.isEmpty) deleteKey (service: service, konto: konto) var dictionary = keyQuery (service: service, konto: konto) ordbok [kSecValueRef as String] = nyckelstatus = SecItemAdd (ordbok som CFDictionary, nil); returstatus == errSecSuccess @discardableResult klass func deleteKey (service: String, konto: String) -> Bool var status: OSStatus = -1 om! (service.isEmpty) &&! (account.isEmpty) let dictionary = keyQuery (service: service, konto: konto) status = SecItemDelete (ordbok som CFDictionary); returstatus == errSecSuccess klass func-nyckel (service: String, konto: String) -> SecKey? var item: CFTypeRef? om (service.isEmpty) &&! (account.isEmpty) var dictionary = keyQuery (service: service, konto: konto) ordbok [kSecReturnRef as String] = kCFBooleanTrue ordbok [kSecMatchLimit as String] = kSecMatchLimitOne SecItemCopyMatching (ordbok som CFDictionary, &Artikel); returnera objektet som! SecKey?
Objekt säkrade med kSecAttrAccessibleWhenUnlocked
flaggan låses upp endast när enheten är låst upp, men det är beroende av att användaren har ett lösenord eller ett rutt-ID som är inrättat i första hand.
De applicationPassword
legitimation tillåter att objekt i nyckelringen säkras med ett extra lösenord. På det här sättet, om användaren inte har ett lösenord eller ett inställnings-ID, är objekten fortfarande säkra och det lägger till ett extra säkerhetslager om de har ett lösenord.
Som ett exempel scenario kan din server, efter att din app autentiseras med din server, återställa lösenordet över HTTPS som krävs för att låsa upp nyckelringens objekt. Detta är det föredragna sättet att tillhandahålla det ytterligare lösenordet. Hardcoding ett lösenord i binär rekommenderas inte.
Ett annat scenario kan vara att hämta det extra lösenordet från ett användarvänligt lösenord i din app. Detta kräver dock mer arbete för att säkra ordentligt (med hjälp av PBKDF2). Vi kommer att se på att säkra användarvänliga lösenord i nästa handledning.
En annan användning av ett lösenord för ansökan är att lagra en känslig nyckel, till exempel en som du inte vill bli utsatt för, bara för att användaren inte hade konfigurerat ett lösenord.
applicationPassword
är endast tillgänglig på iOS 9 och senare, så du behöver en återgång som inte använder applicationPassword
om du riktar in lägre iOS-versioner. För att använda koden måste du lägga till följande i ditt broschyr:
#importera#importera
Följande kod anger ett lösenord för frågan Ordbok
.
om #available (iOS 9.0, *) // Använd det här i stället för kSecAttrAccessible för frågan varfel: Ej hanterad? låt accessControl = SecAccessControlCreateWithFlags (kCFAllocatorDefault, kSecAttrAccessibleWhenUnlocked, SecAccessControlCreateFlags.applicationPassword, & error) om accessControl! = nil dictionary [kSecAttrAccessControl as String] = accessControl låt localAuthenticationContext = LAContext.init () låt theApplicationPassword = "passwordFromServer" .data .Encoding.utf8)! localAuthenticationContext.setCredential (theApplicationPassword, typ: LACredentialType.applicationPassword) ordbok [kSecUseAuthenticationContext as String] = localAuthenticationContext
Observera att vi satte in kSecAttrAccessControl
på Ordbok
. Detta används i stället för kSecAttrAccessible
, som tidigare var inställd i vår passwordQuery
metod. Om du försöker använda båda får du en OSStatus
-50
fel.
Från och med iOS 8 kan du lagra data i nyckeln som bara kan nås efter att användaren har verifierat sig på enheten med berörd ID eller ett lösenord. När det är dags för användaren att autentisera, kommer Touch ID att prioriteras om det är inställt, annars visas lösenordsskärmen. Att spara till nyckelring kommer inte att kräva att användaren ska autentisera, men hämtar datainställningen.
Du kan ställa in ett nyckelring objekt för att kräva användarautentisering genom att tillhandahålla ett åtkomstkontrollobjekt som är inställt på .userPresence
. Om ingen lösenord är inställd, begärs någon nyckelring med .userPresence
kommer misslyckas.
om #available (iOS 8.0, *) let accessControl = SecAccessControlCreateWithFlags (kCFAllocatorDefault, kSecAttrAccessibleWhenUnlockedThisDeviceOnly, .userPresence, nil) om accessControl! = nil dictionary [kSecAttrAccessControl as String] = accessControl
Den här funktionen är bra när du vill se till att din app används av rätt person. Det skulle till exempel vara viktigt att användaren autentiserar innan han kan logga in på en bankapp. Detta skyddar användare som har lämnat sin enhet olåst, så att banken inte kan nås.
Om du inte har en serverkomponent i din app kan du även använda den här funktionen för att utföra autentisering på enhetssidan istället.
För lastfrågan kan du ge en beskrivning av varför användaren behöver verifiera.
ordbok [kSecUseOperationPrompt as String] = "Godkänn att hämta x"
När data hämtas med SecItemCopyMatching ()
, funktionen visar autentiseringsgränssnittet och väntar på att användaren ska använda peka-ID eller ange lösenordet. Eftersom SecItemCopyMatching ()
kommer att blockera tills användaren har slutfört autentisering, du måste ringa funktionen från en bakgrundsgänga för att låta huvudinriktatråden vara upptagen.
DispatchQueue.global (). Async status = SecItemCopyMatching (ordbok som CFDictionary, & passwordData) om status == errSecSuccess om låt retrievedData = passwordData som? Data DispatchQueue.main.async // ... gör resten av arbetet tillbaka på huvudgänget
Återigen ställer vi oss kSecAttrAccessControl
på frågan Ordbok
. Du måste ta bort kSecAttrAccessible
, som tidigare var inställd i vår passwordQuery
metod. Att använda båda på en gång kommer att resultera i en OSStatus
-50 fel.
I den här artikeln har du haft en rundtur i Keychain Services API. Tillsammans med Data Protection API som vi såg i föregående inlägg, är användningen av det här biblioteket en del av de bästa metoderna för att säkra data.
Om användaren inte har ett lösenord eller Touch ID på enheten finns emellertid ingen kryptering för ramarna. Eftersom API-program för nyckelringstjänster och dataskydd ofta används av iOS-appar, riktas de ibland mot angripare, särskilt på jailbroken-enheter. Om din app inte fungerar med mycket känslig information kan detta vara en acceptabel risk. Medan iOS kontinuerligt uppdaterar säkerhetsramarna för ramarna är vi fortfarande till nåd med användaren som uppdaterar operativsystemet, med ett starkt lösenord och inte jailbreaking deras enhet.
Nyckeln är avsedd för mindre bitar av data, och du kan ha en större mängd data för att säkra det som är oberoende av enhetens autentisering. Medan iOS-uppdateringar lägger till några bra nya funktioner, till exempel programlösenordet, kan du fortfarande behöva stödja lägre iOS-versioner och fortfarande ha stark säkerhet. Av några av dessa skäl kan du i stället vilja kryptera data själv.
Den slutliga artikeln i den här serien omfattar kryptering av data själv med AES-kryptering, och medan det är ett mer avancerat tillvägagångssätt, kan du få full kontroll över hur och när dina data krypteras.
Så håll dig stillad. Och under tiden, kolla in några av våra andra inlägg i IOS App Development!