Sikring av iOS-data i hvile nøkkelring

Enhver app som lagrer brukerens data, må ta vare på sikkerheten og personvernet til disse dataene. Som vi har sett med nylige brudd på data, kan det være svært alvorlige konsekvenser for ikke å beskytte brukerens lagrede data. I denne opplæringen lærer du noen gode fremgangsmåter for å beskytte brukerens data.

I det forrige innlegget lærte du hvordan du beskytter filer ved hjelp av Data Protection API. Filbasert beskyttelse er en kraftig funksjon for sikker massedataoppbevaring. Men det kan være overkill for en liten mengde informasjon for å beskytte, for eksempel en nøkkel eller et passord. For disse typene er nøkkelringen den anbefalte løsningen.

Nøkkelringstjenester

Nøkkelringet er et flott sted å lagre mindre mengder informasjon som sensitive strenger og IDer som vedvarer selv når brukeren sletter appen. Et eksempel kan være en enhet eller økt token som din server returnerer til appen ved registrering. Enten du kaller det en hemmelig streng eller unikt token, refererer nøkkelringen til alle disse elementene som passord

Det finnes noen få populære biblioteker fra tredjepart for nøkkelringstjenester, for eksempel Strongbox (Swift) og SSKeychain (Objective-C). Eller, hvis du vil ha full kontroll over din egen kode, kan du ønske å bruke nøkkelringstjenesten API, som er en C API. 

Jeg vil kort forklare hvordan nøkkelringen fungerer. Du kan tenke på nøkkelringen som en typisk database der du kjører spørringer på et bord. Funksjonene i nøkkelringen API-en krever alle a CFDictionary objekt som inneholder attributter av spørringen. 

Hver oppføring i nøkkelringen har et servicenavn. Servicenavnet er en identifikator: a nøkkel for hva som helst verdi du vil lagre eller hente i nøkkelringen. For å tillate at et nøkkelringelement bare lagres for en bestemt bruker, vil du også ofte spesifisere et kontonavn. 

Fordi hver nøkkelringfunksjon tar en lignende ordbok med mange av de samme parametrene for å lage en forespørsel, kan du unngå duplikatkode ved å lage en hjelperfunksjon som returnerer denne søkeordlisten.

Import Security // ... class func passwordQuery (service: String, konto: String) -> Ordbok la ordbok = [kSecClass as String: kSecClassGenericPassword, kSecAttrAccount as String: konto, kSecAttrService as String: service, kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlocked // Hvis du trenger tilgang i bakgrunnen, kan du kanskje vurdere kSecAttrAccessibleAfterFirstUnlock] som [String: Any] returordbok 

Denne koden setter opp spørringen Ordbok med konto- og servicenavn og forteller nøkkelringen at vi skal lagre et passord. 

På samme måte som hvordan du kan sette beskyttelsesnivået for individuelle filer (som vi diskuterte i forrige innlegg), kan du også angi beskyttelsesnivåene for nøkkelringet ditt ved hjelp av kSecAttrAccessible nøkkel. 

Legge til et passord

De SecItemAdd () funksjonen legger til data i nøkkelringen. Denne funksjonen tar en Data objekt, noe som gjør den allsidig til å lagre mange typer objekter. Ved hjelp av passordspørsmålfunksjonen vi opprettet ovenfor, la oss lagre en streng i nøkkelringen. For å gjøre dette må vi bare konvertere string til Data.

@discardableResult class func setPassword (_ passord: String, service: String, konto: String) -> Bool var status: OSStatus = -1 hvis! (service.isEmpty) &&! (account.isEmpty) deletePassword , konto: konto) // slette passord hvis passere tom streng. Kan endre for å passere null for å slette passord, etc hvis! Password.isEmpty var dictionary = passwordQuery (service: service, konto: konto) la dataFromString = password.data (bruk: String.Encoding.utf8, allowLossyConversion: false) dictionary [ kSecValueData as String] = dataFromString status = SecItemAdd (ordbok som CFDictionary, null) returstatus == errSecSuccess

Sletter et passord

For å unngå duplikatinnsatser slettes koden ovenfor først forrige oppføring hvis det er en. La oss skrive den funksjonen nå. Dette oppnås ved hjelp av SecItemDelete () funksjon.

@discardableResult class func deletePassword (tjeneste: String, konto: String) -> Bool var status: OSStatus = -1 hvis! (service.isEmpty) &&! (account.isEmpty) let dictionary = passwordQuery (service: service, konto : konto) status = SecItemDelete (ordbok som CFDictionary);  returstatus == errSecSuccess

Henter et passord

For å hente en oppføring fra nøkkelringen, bruk deretter SecItemCopyMatching () funksjon. Det vil returnere en AnyObject som samsvarer med søket ditt.

klassen func passord (tjeneste: String, konto: String) -> String // returner tom streng hvis ikke funnet, kan returnere en valgfri var status: OSStatus = -1 var resultString = "" hvis! (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, og passwordData) hvis status == errSecSuccess hvis la hentesData = passordData som? Data resultString = String (data: hentetData, koding: String.Encoding.utf8)!  returresultatString

I denne koden setter vi inn kSecReturnData parameter til kCFBooleanTruekSecReturnData betyr at de faktiske dataene på elementet vil bli returnert. Et annet alternativ kan være å returnere attributter (kSecReturnAttributes) av elementet. Nøkkelen tar en CFBoolean type som holder konstantene kCFBooleanTrue eller kCFBooleanFalse. Vi setter inn kSecMatchLimit til kSecMatchLimitOne slik at bare det første elementet i nøkkelringen blir returnert, i motsetning til et ubegrenset antall resultater.

Offentlige og private nøkler

Nøkkelringet er også det anbefalte stedet for å lagre offentlige og private nøkkelobjekter, for eksempel hvis appen din samarbeider med og må lagre EC eller RSA SecKey objekter. 

Hovedforskjellen er at i stedet for å fortelle nøkkelringen for å lagre et passord, kan vi fortelle det om å lagre en nøkkel. Faktisk kan vi bli konkrete ved å angi hvilke typer nøkler som er lagret, for eksempel om det er offentlig eller privat. Alt som må gjøres, er å tilpasse spørringshjelpsfunksjonen til å jobbe med hvilken type nøkkel du vil ha. 

Nøkler identifiseres generelt ved hjelp av et omvendt domene-tag som f.eks com.mydomain.mykey i stedet for service- og kontonavn (siden offentlige nøkler deles åpent mellom ulike selskaper eller enheter). Vi tar service- og kontostrenger og konverterer dem til en tagg Data gjenstand. For eksempel er koden ovenfor tilpasset å lagre en RSA Private SecKey ville se slik ut:

class func keyQuery (service: streng, konto: streng) -> ordbok let tagString = "com.mydomain." + tjeneste + "." + konto la tag = tagString.data (bruk: .utf8)! // Lagre det som Data, ikke som en streng la ordbok = [kSecClass som String: kSecClassKey, kSecAttrKeyType som String: kSecAttrKeyTypeRSA, kSecAttrKeyClass som streng: kSecAttrKeyClassPrivate, kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlocked, kSecAttrApplicationTag som streng: tag] som [String: Enhver ] returordbok @discardableResultat klasse func setKey (_ nøkkel: SecKey, tjeneste: String, konto: String) -> Bool var status: OSStatus = -1 hvis! (service.isEmpty) &&! (account.isEmpty) deleteKey (tjeneste: tjeneste, konto: konto) var dictionary = keyQuery (tjeneste: tjeneste, konto: konto) ordbok [kSecValueRef as String] = nøkkelstatus = SecItemAdd (ordbok som CFDictionary, null);  returstatus == errSecSuccess @discardableResult class func deleteKey (tjeneste: String, konto: String) -> Bool var status: OSStatus = -1 hvis! (service.isEmpty) &&! (account.isEmpty) let dictionary = keyQuery (service: service, konto: konto) status = SecItemDelete (ordbok som CFDictionary);  returstatus == errSecSuccess class func-nøkkel (tjeneste: String, konto: String) -> SecKey? var item: CFTypeRef? (koden): KDE40.1, KDE40.1, KDE40.1, KDE40, Kode, Kode, Kode, Kode, Kode, Kode, Kode, Kode, Kode, Kode, Kode, &punkt);  returnere elementet som! SecKey? 

Applikasjonspassord

Varer sikret med kSecAttrAccessibleWhenUnlocked flagg bare opplåses når enheten er låst opp, men det er avhengig av at brukeren har et passord eller berørings ID opprettet i utgangspunktet. 

De applicationPassword legitimasjon tillater at elementer i nøkkelringen blir sikret ved hjelp av et ekstra passord. På denne måten, hvis brukeren ikke har passord eller berørings ID-oppsett, vil elementene fortsatt være sikre, og det legger til et ekstra sikkerhetslag hvis de har et passordsett.  

Som et eksempel scenario, etter at appen din er godkjent med serveren din, kan serveren returnere passordet over HTTPS som kreves for å låse opp nøkkelringelementet. Dette er den foretrukne måten å levere det ekstra passordet på. Hardkoding av et passord i binæret anbefales ikke.

Et annet scenario kan være å hente det ekstra passordet fra et brukerdefinert passord i appen din. Dette krever imidlertid mer arbeid for å sikre seg riktig (ved hjelp av PBKDF2). Vi vil se på å sikre passordene du bruker, i neste veiledning. 

En annen bruk av et programpassord er å lagre en sensitiv nøkkel, for eksempel en som du ikke ønsker å bli utsatt bare fordi brukeren ikke hadde opprettet et passord. 

applicationPassword er bare tilgjengelig på iOS 9 og over, så du trenger en tilbakebetaling som ikke bruker applicationPassword hvis du målretter mot lavere iOS-versjoner. Hvis du vil bruke koden, må du legge til følgende i broen din:

#importere  #importere 

Følgende kode angir et passord for spørringen Ordbok.

hvis #available (iOS 9.0, *) // Bruk dette i stedet for kSecAttrAccessible for spørringen var feil: Unmanaged? la accessControl = SecAccessControlCreateWithFlags (kCFAllocatorDefault, kSecAttrAccessibleWhenUnlocked, SecAccessControlCreateFlags.applicationPassword, og feil) hvis accessControl! = nil ordbok [kSecAttrAccessControl as String] = accessControl la localAuthenticationContext = LAContext.init () la theApplicationPassword = "passwordFromServer" .data .Encoding.utf8)! localAuthenticationContext.setCredential (theApplicationPassword, type: LACredentialType.applicationPassword) ordbok [kSecUseAuthenticationContext as String] = localAuthenticationContext

Legg merke til at vi satt kSecAttrAccessControl på Ordbok. Dette brukes i stedet for kSecAttrAccessible, som tidligere ble satt i vår passwordQuery metode. Hvis du prøver å bruke begge, får du en OSStatus -50 feil.

Bruker autentisering

Fra iOS 8 kan du lagre data i nøkkelringen som bare kan nås etter at brukeren er godkjent på enheten med berørings-ID eller passord. Når det er på tide for brukeren å autentisere, vil berørings ID-en prioriteres hvis den er opprettet, ellers vises passordskjermen. Lagring i nøkkelringet krever ikke at brukeren autentiserer, men henter data vilen. 

Du kan angi et nøkkelringelement for å kreve brukerautentisering ved å gi et tilgangskontrollobjekt satt til .userPresence. Hvis ingen passord er satt opp, vil noen nøkkelring bli bedt om .userPresence vil mislykkes. 

hvis #available (iOS 8.0, *) la accessControl = SecAccessControlCreateWithFlags (kCFAllocatorDefault, kSecAttrAccessibleWhenUnlockedThisDeviceOnly, .userPresence, null) hvis accessControl! = nil ordbok [kSecAttrAccessControl as String] = accessControl 

Denne funksjonen er god når du vil sørge for at appen din blir brukt av den rette personen. For eksempel ville det være viktig for brukeren å autentisere før han kunne logge seg på en banktjeneste. Dette vil beskytte brukere som har forlatt sin enhet ulåst, slik at banken ikke kan nås. 

Hvis du ikke har en server-side-komponent i appen din, kan du også bruke denne funksjonen til å utføre enhetssidesautentisering i stedet.

For lastforespørselen kan du gi en beskrivelse av hvorfor brukeren må godkjenne.

ordbok [kSecUseOperationPrompt as String] = "Godkjen for å hente x" 

Når du henter dataene med SecItemCopyMatching (), funksjonen vil vise autentiseringsgrensesnittet og vente på at brukeren skal bruke berørings-ID eller angi passordet. Siden SecItemCopyMatching () vil blokkere til brukeren har fullført godkjenning, må du ringe funksjonen fra en bakgrunns tråd for å la hovedbrukerens tråd være responsiv.

DispatchQueue.global (). Async status = SecItemCopyMatching (ordbok som CFDictionary, og passwordData) hvis status == errSecSuccess hvis la hentesData = passordData som? Data DispatchQueue.main.async // ... gjør resten av arbeidet tilbake på hovedtråden

Igjen, vi setter inn kSecAttrAccessControl på spørringen Ordbok. Du må fjerne kSecAttrAccessible, som tidligere ble satt i vår passwordQuery metode. Bruke begge på en gang vil resultere i en OSStatus -50 feil.

Konklusjon

I denne artikkelen har du hatt en omvisning i nøkkelringstjenesten API. Sammen med databeskyttelses-API som vi så i forrige innlegg, er bruk av dette biblioteket en del av de beste metodene for å sikre data. 

Men hvis brukeren ikke har passord eller berørings ID på enheten, er det ikke kryptering for begge rammene. Fordi nøkleringstjenestene og databeskyttelses-APIene brukes av iOS-apper, blir de noen ganger målrettet av angripere, spesielt på jailbroken-enheter. Hvis appen ikke fungerer med svært sensitiv informasjon, kan dette være en akseptabel risiko. Selv om iOS kontinuerlig oppdaterer sikkerheten til rammene, er vi fortsatt i godhet for brukeren som oppdaterer operativsystemet, bruker et sterkt passord og ikke jailbreaking enheten deres. 

Nøkkelringet er ment for mindre datamengder, og du kan ha en større mengde data for å sikre at det er uavhengig av enhetens godkjenning. Mens iOS-oppdateringer legger til noen flotte nye funksjoner, for eksempel programpassordet, kan det hende du fortsatt trenger å støtte lavere iOS-versjoner og fortsatt har sterk sikkerhet. Av noen av grunnene kan du i stedet kryptere dataene selv. 

Den endelige artikkelen i denne serien dekker kryptering av dataene selv ved hjelp av AES-kryptering, og mens det er en mer avansert tilnærming, kan du få full kontroll over hvordan og når dataene dine er kryptert.

Så hold deg innstilt. Og i mellomtiden, sjekk ut noen av våre andre innlegg på IOS app utvikling!