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ø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) -> Ordbokla 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.
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
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
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 kCFBooleanTrue
. kSecReturnData
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.
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) -> ordboklet 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?
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.
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.
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!