I dette innlegget ser vi på avanserte bruksområder for kryptering for brukerdata i iOS-apper. Vi starter med et høyt nivå på AES-kryptering, og deretter fortsetter vi å se på noen eksempler på hvordan å implementere AES-kryptering i Swift.
I det siste innlegget lærte du hvordan du lagrer data ved hjelp av nøkkelringen, noe som er bra for små biter av informasjon som nøkler, passord og sertifikater.
Hvis du lagrer en stor mengde tilpassede data som du vil være tilgjengelig bare etter at brukeren eller enheten er godkjent, er det bedre å kryptere dataene ved hjelp av et krypteringsramme. For eksempel kan du ha en app som kan arkivere private chatmeldinger lagret av brukeren eller private bilder tatt av brukeren, eller som kan lagre brukerens økonomiske detaljer. I disse tilfellene vil du sannsynligvis ønske å bruke kryptering.
Det er to vanlige strømmer i applikasjoner for kryptering og dekryptering av data fra iOS-apper. Enten blir brukeren presentert med et passordskjermbilde, eller applikasjonen er autentisert med en server som returnerer en nøkkel for å dekryptere dataene.
Det er aldri en god ide å gjenoppfinne hjulet når det gjelder kryptering. Derfor skal vi bruke AES-standarden som leveres av IOS Common Crypto-biblioteket.
AES er en standard som krypterer data gitt en nøkkel. Den samme nøkkelen som brukes til å kryptere data, brukes til å dekryptere dataene. Det er forskjellige nøkkelstørrelser, og AES256 (256 bits) er den foretrukne lengden som skal brukes med sensitive data.
RNCryptor er et populært krypteringspakke for iOS som støtter AES. RNCryptor er et godt valg fordi det får deg til å løpe veldig fort uten å måtte bekymre deg for de underliggende detaljene. Det er også åpen kilde slik at sikkerhetsforskere kan analysere og revidere koden.
På den annen side, hvis appen din omhandler svært sensitiv informasjon, og du tror at søknaden din vil bli målrettet og sprukket, vil du kanskje skrive din egen løsning. Årsaken til dette er at når mange apper bruker samme kode, kan det gjøre hackerens jobb lettere, slik at de kan skrive en cracking-app som finner felles mønstre i koden og bruker oppdateringer til dem.
Vær imidlertid oppmerksom på at det å skrive din egen løsning bare bremser en angriper og forhindrer automatiserte angrep. Beskyttelsen du får fra din egen implementering er at en hacker vil trenge å bruke tid og engasjement når du sprer appen din alene.
Enten du velger en tredjeparts løsning eller velger å rulle din egen, er det viktig å være kunnskapsrik om hvordan krypteringssystemer fungerer. På den måten kan du bestemme om et bestemt rammeverk du vil bruke, er veldig sikkert. Derfor vil resten av denne opplæringen fokusere på å skrive din egen tilpassede løsning. Med kunnskapen du vil lære av denne opplæringen, vil du kunne fortelle om du bruker et bestemt rammeverk sikkert.
Vi starter med å opprette en hemmelig nøkkel som vil bli brukt til å kryptere dataene dine.
En svært vanlig feil i AES-kryptering er å bruke brukerens passord direkte som krypteringsnøkkel. Hva om brukeren bestemmer seg for å bruke et vanlig eller svakt passord? Hvordan tvinger vi brukere til å bruke en nøkkel som er tilfeldig og sterk nok (har nok entropi) til kryptering, og får dem til å huske det?
Løsningen er nøkkelstrekning. Nøkkelen strekker seg ut av en nøkkel fra et passord ved å hashing det mange ganger med et salt. Saltet er bare en sekvens av tilfeldige data, og det er en vanlig feil å utelate dette saltet - saltet gir nøkkelen sin vitalt entropi, og uten saltet vil samme nøkkel bli avledet dersom samme passord ble brukt av noen ellers.
Uten saltet kan en ordliste brukes til å utlede vanlige nøkler, som deretter kan brukes til å angripe brukerdata. Dette kalles en "ordbok angrep". Tabeller med fellesnøkler som svarer til usaltede passord, brukes til dette formålet. De kalles "regnbuebord".
En annen fallgruve når du lager et salt er å bruke en tilfeldig tallgenereringsfunksjon som ikke var laget for sikkerhet. Et eksempel er rand ()
funksjon i C, som kan nås fra Swift. Denne utgangen kan ende med å være veldig forutsigbar!
For å skape et sikkert salt bruker vi funksjonen SecRandomCopyBytes
å skape kryptografisk sikre tilfeldige byte-det vil si tall som er vanskelig å forutsi.
Hvis du vil bruke koden, må du legge til følgende i broen din:#importere
Her er starten på koden som lager et salt. Vi legger til denne koden når vi går:
var salt = Data (telle: 8) salt.withUnsafeMutableBytes (saltBytes: UsikkertMutabelPointer) -> Opphevet i la saltStatus = SecRandomCopyBytes (kSecRandomDefault, salt.count, saltBytes) // ...
Nå er vi klare til å gjøre hovedstrekningen. Heldigvis har vi allerede en funksjon til disposisjon for å gjøre den faktiske strekkingen: Den Password-Based Key Derivation Function (PBKDF2). PBKDF2 utfører en funksjon mange ganger over for å utlede nøkkelen; øker antall iterasjoner utvider tiden det ville ta å operere på et sett med nøkler under et brute force attack. Det anbefales å bruke PBKDF2 til å generere nøkkelen.
var setupSuccess = true var-nøkkel = Data (gjentatt: 0, telle: kCCKeySizeAES256) var salt = Data (telle: 8) salt.withUnsafeMutableBytes (saltBytes: UsikkerMutabelPointer) -> Opphevet i la saltStatus = SecRandomCopyBytes (kSecRandomDefault, salt.count, saltBytes) hvis saltStatus == errSecSuccess la passwordData = password.data (using: String.Encoding.utf8)! key.withUnsafeMutableBytes (keyBytes: UnsafeMutablePointer ) i let derivationStatus = CCKeyDerivationPBKDF (CCPBKDFAlgorithm (kCCPBKDF2), passord, passordData.count, saltBytes, salt.count, CCPseudoRandomAlgorithm (kCCPRFHmacAlgSHA512), 14271, keyBytes, key.count) hvis derivationStatus! = Int32 (kCCSuccess) setupSuccess = false annet setupSuccess = false
Du lurer kanskje nå på tilfellene der du ikke vil kreve at brukerne oppgir passord i appen din. Kanskje er de allerede godkjenning med en enkelt påloggingsplan. I så fall må serveren generere en AES 256-bit (32 byte) nøkkel ved hjelp av en sikker generator. Nøkkelen skal være forskjellig for ulike brukere eller enheter. Ved godkjenning med serveren kan du overføre en enhet eller bruker-ID til en server over en sikker tilkobling, og den kan sende den tilhørende nøkkelen tilbake.
Denne ordningen har en stor forskjell. Hvis nøkkelen kommer fra serveren, har enheten som kontrollerer den serveren kapasitet til å kunne lese de krypterte dataene dersom enheten eller dataene noensinne er oppnådd. Det er også potensial for nøkkelen til å bli lekket eller eksponert på et senere tidspunkt.
På den annen side, hvis nøkkelen er avledet av noe bare brukeren vet - brukerens passord - så kan bare brukeren dekryptere disse dataene. Hvis du beskytter informasjon som private økonomiske data, bør bare brukeren kunne låse opp dataene. Hvis denne informasjonen er kjent for enheten, kan det være akseptabelt at serveren låser opp innholdet via en server-side-nøkkel.
Nå som vi har en nøkkel, la oss kryptere noen data. Det finnes forskjellige krypteringsmoduser, men vi bruker den anbefalte modusen: Chip Block Chaining (CBC). Dette opererer på våre data ett kvartal om gangen.
En felles fallgruve med CBC er det faktum at hver neste ukrypterte blokk med data er XOR'd med den forrige krypterte blokk for å gjøre krypteringen sterkere. Problemet her er at den første blokken aldri er like unik som alle de andre. Hvis en melding som skal krypteres skulle starte med det samme som en annen melding som skal krypteres, vil den begynnende krypterte utgangen være den samme, og det ville gi en angriper et hint om å finne ut hva meldingen kan være.
For å omgå denne potensielle svakheten, starter vi dataene som skal lagres med det som kalles en initialiseringsvektor (IV): en blokk med tilfeldige byte. IV vil være XOR'd med den første blokken med brukerdata, og siden hver blokk avhenger av alle blokkene som er behandlet frem til det punktet, vil det sikre at hele meldingen blir unikt kryptert, selv om den har de samme dataene som en annen budskap. Med andre ord vil identiske meldinger kryptert med samme nøkkel ikke produsere like resultater. Så mens salter og IVs regnes som offentlige, bør de ikke være sekvensielle eller gjenbrukes.
Vi vil bruke samme sikre SecRandomCopyBytes
funksjon for å skape IV.
var iv = Data.init (telle: kCCBlockSizeAES128) iv.withUnsafeMutableBytes (ivBytes: UnsafeMutablePointer) i la ivStatus = SecRandomCopyBytes (kSecRandomDefault, kCCBlockSizeAES128, ivBytes) hvis ivStatus! = errSecSuccess setupSuccess = false
For å fullføre vårt eksempel bruker vi CCCrypt
funksjon med enten kCCEncrypt
eller kCCDecrypt
. Fordi vi bruker en blokk-kryptering, hvis meldingen ikke passer pent inn i et flertall av blokkstørrelsen, må vi fortelle funksjonen å automatisk legge til polstring til slutten.
Som vanlig i kryptering er det best å følge etablerte standarder. I dette tilfellet definerer standard PKCS7 hvordan du fyller dataene. Vi forteller vår krypteringsfunksjon å bruke denne standarden ved å levere KCCOptionPKCS7Padding
alternativ. Setter alt sammen, her er den fulle koden for å kryptere og dekryptere en streng.
class func encryptData (_ clearTextData: Data, med Passord passord: String) -> Ordbokvar setupSuccess = true var outDictionary = Dictionary .init () var nøkkel = Data (gjentatt: 0, telle: kCCKeySizeAES256) var salt = Data (telle: 8) salt.withUnsafeMutableBytes (saltBytes: UsikkerMutabelPointer ) -> Opphevet i la saltStatus = SecRandomCopyBytes (kSecRandomDefault, salt.count, saltBytes) hvis saltStatus == errSecSuccess la passwordData = password.data (using: String.Encoding.utf8)! key.withUnsafeMutableBytes (keyBytes: UnsafeMutablePointer ) i let derivationStatus = CCKeyDerivationPBKDF (CCPBKDFAlgorithm (kCCPBKDF2), passord, passordData.count, saltBytes, salt.count, CCPseudoRandomAlgorithm (kCCPRFHmacAlgSHA512), 14271, keyBytes, key.count) hvis derivationStatus! = Int32 (kCCSuccess) setupSuccess = false else setupSuccess = false var iv = Data.init (telle: kCCBlockSizeAES128) iv.withUnsafeMutableBytes (ivBytes: UnsafeMutablePointer ) i la ivStatus = SecRandomCopyBytes (kSecRandomDefault, kCCBlockSizeAES128, ivBytes) hvis ivStatus! = errSecSuccess setupSuccess = false hvis (setupSuccess) var numberOfBytesEncrypted: size_t = 0 la size = clearTextData.count + kCCBlockSizeAES128 var kryptert = Data.init telle: størrelse) la cryptStatus = iv.withUnsafeBytes ivBytes i encrypted.withUnsafeMutableBytes encryptedBytes i clearTextData.withUnsafeBytes clearTextBytes i key.withUnsafeBytes keyBytes i CCCrypt (CCOperation (kCCEncrypt), CCAlgorithm (kCCAlgorithmAES), CCOptions (kCCOptionPKCS7Padding), keyBytes, key.count, ivBytes, clearTextBytes, clearTextData.count, encryptedBytes, size, og numberOfBytesEncrypted) hvis cryptStatus == Int32 (kCCSuccess) encrypted.count = numberOfBytesEncrypted outDictionary ["EncryptionData"] = kryptert utDictionary ["EncryptionIV"] = iv outDictionary ["EncryptionSalt"] = salt return outDictionary;
Og her er dekrypteringskoden:
class func decryp (fromDictionary ordbok: Dictionary, withPassword password: String) -> Data var setupSuccess = true la kryptert = ordbok ["EncryptionData"] la iv = ordbok ["EncryptionIV"] la salt = ordbok ["EncryptionSalt"] var nøkkel = Data (gjentatt: 0, telle : kCCKeySizeAES256) salt? .withUnsafeBytes (saltBytes: UsikrePointer ) -> Opphev inn passordData = password.data (bruk: String.Encoding.utf8)! key.withUnsafeMutableBytes (keyBytes: UnsafeMutablePointer ) i let derivationStatus = CCKeyDerivationPBKDF (CCPBKDFAlgorithm (kCCPBKDF2), passord, passordData.count, saltBytes, salt !.count, CCPseudoRandomAlgorithm (kCCPRFHmacAlgSHA512), 14271, keyBytes, key.count) hvis derivationStatus! = Int32 (kCCSuccess) setupSuccess = false var decryptSuccess = false let size = (kryptert? .count)! + kCCBlockSizeAES128 var clearTextData = Data.init (telle: størrelse) hvis (setupSuccess) var numberOfBytesDecrypted: size_t = 0 la cryptStatus = iv? .withUnsafeBytes ivBytes i clearTextData.withUnsafeMutableBytes clearTextBytes i kryptert? .withUnsafeBytes encryptedBytes in key.withUnsafeBytes keyBytes i CCCrypt (CCOperation (kCCDecrypt), CCAlgorithm (kCCAlgorithmAES128), CCOptions (kCCOptionPKCS7Padding), keyBytes, key.count, ivBytes, encryptedBytes, (kryptert? .count) !, clearTextBytes, size, og numberOfBytesDecrypted) hvis cryptStatus ! == Int32 (kCCSuccess) clearTextData.count = numberOfBytesDecrypted decryptSuccess = true returnere decryptSuccess? clearTextData: Data.init (telle: 0)
Til slutt, her er en test for å sikre at data dekrypteres riktig etter kryptering:
class func encryptionTest () la clearTextData = "noen klar tekst for å kryptere" .data (using: String.Encoding.utf8)! la ordbok = encryptData (clearTextData, withPassword: "123456") la dekryptert = dekryp (fraDictionary: ordbok, medPassword: "123456") la decryptedString = String (data: dekryptert, koding: String.Encoding.utf8) print ("dekryptert cleartext resultat - ", decryptedString ??" Feil: Kunne ikke konvertere data til streng ")
I vårt eksempel pakker vi all nødvendig informasjon og returnerer den som en Ordbok
slik at alle stykkene senere kan brukes til å dekryptere dataene. Du trenger bare å lagre IV og salt, enten i nøkkelringen eller på serveren din.
Dette fullfører tredelte serien for å sikre data i hvile. Vi har sett hvordan du lagrer passord, sensitive informasjonstyper og store mengder brukerdata. Disse teknikkene er utgangspunktet for å beskytte lagret brukerinformasjon i appen din.
Det er en stor risiko når en brukerens enhet går tapt eller stjålet, særlig med nyere utnyttelser for å få tilgang til en låst enhet. Selv om mange systemproblemer er oppdatert med en programvareoppdatering, er selve enheten bare like sikker som brukerens passord og versjon av iOS. Derfor er det opp til utvikleren av hver app å gi sterk beskyttelse av sensitiv data som blir lagret.
Alle de temaene som er dekket så langt, gjør bruk av Apples rammebetingelser. Jeg vil legge igjen en ide med deg til å tenke på. Hva skjer når Apples krypteringsbibliotek blir angrepet?
Når en vanlig sikkerhetsarkitektur er kompromittert, blir alle appene som stole på det, også kompromittert. Noen av iOS dynamisk koblede biblioteker, spesielt på jailbroken enheter, kan patches og byttes med ondsinnede.
Et statisk bibliotek som er buntet med binæret i appen din, er imidlertid beskyttet mot denne typen angrep, fordi hvis du prøver å lappe det, endrer du endringen av appen binær. Dette vil ødelegge kodesignaturen til appen, slik at den ikke blir lansert. Hvis du importerte og brukte, for eksempel, OpenSSL for kryptering, var appen din ikke sårbar for et utbredt Apple API-angrep. Du kan kompilere OpenSSL selv og knytte det statisk til appen din.
Så det er alltid mer å lære, og fremtiden for app-sikkerhet på iOS er alltid i utvikling. IOS-sikkerhetsarkitekturen støtter nå også kryptografiske enheter og smartkort! Til slutt, nå vet du de beste metodene for å sikre data i ro, så det er opp til deg å følge dem!
I mellomtiden kan du sjekke ut noe av vårt andre innhold om iOS app utvikling og app sikkerhet.