I det här inlägget ser vi på avancerade användningsområden för kryptering för användardata i iOS-appar. Vi börjar med en hög nivå titt på AES-kryptering, och sedan fortsätt titta på några exempel på hur man implementerar AES-kryptering i Swift.
I det senaste inlägget lärde du dig att lagra data med hjälp av nyckelring, vilket är bra för små bitar av information som nycklar, lösenord och certifikat.
Om du lagrar en stor mängd anpassade data som du vill vara tillgänglig först efter att användaren eller enheten autentiserats, är det bättre att kryptera data med en krypteringsram. Du kan till exempel ha en app som kan arkivera privata chattmeddelanden som sparas av användaren eller privata foton som användaren tagit eller som kan lagra användarens finansiella detaljer. I dessa fall skulle du förmodligen vilja använda kryptering.
Det finns två vanliga flöden i applikationer för kryptering och dekryptering av data från iOS-appar. Antingen användaren presenteras med en lösenordsskärm, eller applikationen är autentiserad med en server som returnerar en nyckel för att dekryptera data.
Det är aldrig en bra idé att uppfinna hjulet när det gäller kryptering. Därför kommer vi att använda AES-standarden som tillhandahålls av IOS Common Crypto-biblioteket.
AES är en standard som krypterar data med en nyckel. Samma nyckel som används för att kryptera data används för att dekryptera data. Det finns olika nyckelstorlekar, och AES256 (256 bitar) är den föredragna längden som ska användas med känslig data.
RNCryptor är ett populärt krypteringspaket för iOS som stöder AES. RNCryptor är ett bra val eftersom det får dig att gå igång mycket snabbt utan att behöva oroa dig för de underliggande detaljerna. Det är också öppen källkod så att säkerhetsforskare kan analysera och granska koden.
Å andra sidan, om din app behandlar mycket känslig information och du tror att din ansökan kommer att riktas och knäckas, kanske du vill skriva din egen lösning. Anledningen till detta är att när många appar använder samma kod, kan det göra hackarens jobb enklare, så att de kan skriva en sprickapp som hittar vanliga mönster i koden och applicerar korrigeringsfiler till dem.
Tänk på att skrivandet av din egen lösning bara saktar ner en angripare och förhindrar automatiska attacker. Det skydd du får från din egen implementering är att en hackare kommer att behöva spendera tid och engagemang på att spricka din app ensam.
Oavsett om du väljer en tredjepartslösning eller väljer att rulla din egen, är det viktigt att vara kunnig om hur krypteringssystem fungerar. På så sätt kan du bestämma om en viss ram du vill använda är riktigt säker. Därför kommer resten av denna handledning att fokusera på att skriva din egen anpassade lösning. Med den kunskap du kommer att lära av den här handledningen kan du berätta om du använder ett visst ramverk säkert.
Vi börjar med att skapa en hemlig nyckel som används för att kryptera dina data.
Ett mycket vanligt fel i AES-kryptering är att använda användarens lösenord direkt som krypteringsnyckeln. Vad händer om användaren väljer att använda ett vanligt eller svagt lösenord? Hur tvingar vi användare att använda en nyckel som är slumpmässig och stark nog (har tillräckligt med entropi) för kryptering och sedan få dem att komma ihåg det?
Lösningen är nyckelsträckning. Nyckelsträckning härleder en nyckel från ett lösenord genom att hashing det många gånger med ett salt. Saltet är bara en sekvens av slumpmässiga data, och det är ett vanligt misstag att släppa bort saltet - saltet ger nyckeln sin viktigaste entropi, och utan saltet kommer samma nyckel att härledas om samma lösenord användes av någon annan.
Utan saltet kan en ordbok med ord användas för att härleda vanliga nycklar, som då kan användas för att attackera användardata. Detta kallas en "dictionary attack". Tabeller med vanliga nycklar som motsvarar osaltade lösenord används för detta ändamål. De kallas "regnbordsbord".
En annan fallgrop när man skapar ett salt är att använda en slumpmässig talgenererande funktion som inte var avsedd för säkerhet. Ett exempel är rand()
funktion i C, som kan nås från Swift. Denna produktion kan bli mycket förutsägbar!
För att skapa ett säkert salt använder vi funktionen SecRandomCopyBytes
att skapa kryptografiskt säkra slumpmässiga byte-det vill säga siffror som är svåra att förutsäga.
För att använda koden måste du lägga till följande i ditt broschyr:#importera
Här är början på koden som skapar ett salt. Vi lägger till i den här koden när vi går vidare:
var salt = Data (räkning: 8) salt.medUnsafeMutableBytes (saltBytes: UnsafeMutablePointer) -> Avbryt i låt saltStatus = SecRandomCopyBytes (kSecRandomDefault, salt.count, saltBytes) // ...
Nu är vi redo att göra nyckelsträckning. Lyckligtvis har vi redan en funktion till vårt förfogande för att göra den faktiska sträckningen: Den lösenordsbaserade nyckelavledningsfunktionen (PBKDF2). PBKDF2 utför en funktion många gånger över för att härleda nyckeln; ökar antalet iterationer expanderar tiden det skulle ta för att fungera på en uppsättning nycklar under en brute force attack. Vi rekommenderar att du använder PBKDF2 för att generera din nyckel.
var setupSuccess = true var-nyckel = Data (upprepa: 0, räkna: kCCKeySizeAES256) var salt = Data (räkning: 8) salt.withUnsafeMutableBytes (saltBytes: UnsafeMutablePointer) -> Avbryt i låt saltStatus = SecRandomCopyBytes (kSecRandomDefault, salt.count, saltBytes) om saltStatus == errSecSuccess let passwordData = password.data (using: String.Encoding.utf8)! key.withUnsafeMutableBytes (keyBytes: UnsafeMutablePointer ) i let derivationStatus = CCKeyDerivationPBKDF (CCPBKDFAlgorithm (kCCPBKDF2), lösenord, passwordData.count, saltBytes, salt.count, CCPseudoRandomAlgorithm (kCCPRFHmacAlgSHA512), 14271, keyBytes, key.count) om derivationStatus! = Int32 (kCCSuccess) setupSuccess = false else setupSuccess = false
Du kanske undrar nu om de fall där du inte vill kräva att användarna anger ett lösenord i din app. Kanske är de redan autentiserande med ett enda inloggningssystem. I det här fallet måste din server generera en AES 256-bitars (32 byte) nyckel med en säker generator. Nyckeln ska vara olika för olika användare eller enheter. På autentisering med din server kan du skicka servern en enhet eller användar-ID över en säker anslutning, och den kan skicka motsvarande nyckel tillbaka.
Detta system har en stor skillnad. Om nyckeln kommer från servern har enheten som styr den servern kapacitet att kunna läsa den krypterade data om enheten eller data någonsin erhölls. Det finns också potential för att nyckeln ska läckas eller exponeras vid en senare tidpunkt.
Å andra sidan, om nyckeln härrör från något som bara vet användaren - användarens lösenord - då kan endast användaren dekryptera den data. Om du skyddar information som privat finansiell data, ska endast användaren kunna låsa upp data. Om den informationen i alla fall är känd kan det vara acceptabelt att servern låser upp innehållet via en serverns nyckel.
Nu när vi har en nyckel, låt oss kryptera vissa data. Det finns olika krypteringslägen, men vi använder det rekommenderade läget: cipher block chaining (CBC). Detta fungerar ett block i taget på våra data.
Ett vanligt fall med CBC är det faktum att varje nästa okrypterade datablock är XOR'd med det tidigare krypterade blocket för att göra krypteringen starkare. Problemet här är att det första blocket aldrig är lika unikt som alla andra. Om ett meddelande som ska krypteras skulle börja med detsamma som ett annat meddelande som ska krypteras, skulle den initiala krypterade utsignalen vara densamma, och det skulle ge en angripare en ledtråd för att ta reda på vad meddelandet kan vara.
För att komma runt denna potentiella svaghet börjar vi datan som ska sparas med vad som kallas en initialiseringsvektor (IV): ett block med slumpmässiga byte. IV kommer att bli XOR'd med det första blocket med användardata och eftersom varje block beror på alla block som bearbetas fram till den punkten kommer det att säkerställa att hela meddelandet kommer att vara unikt krypterat, även om det har samma data som en annan meddelande. Med andra ord kommer identiska meddelanden som krypteras med samma nyckel inte att producera identiska resultat. Så medan salter och IVs anses vara offentliga, bör de inte vara sekventiella eller återanvända.
Vi kommer att använda samma säkra SecRandomCopyBytes
funktion för att skapa IV.
var iv = Data.init (räkning: kCCBlockSizeAES128) iv.withUnsafeMutableBytes (ivBytes: UnsafeMutablePointer) i låt ivStatus = SecRandomCopyBytes (kSecRandomDefault, kCCBlockSizeAES128, ivBytes) om ivStatus! = errSecSuccess setupSuccess = false
För att slutföra vårt exempel använder vi CCCrypt
funktion med antingen kCCEncrypt
eller kCCDecrypt
. Eftersom vi använder en blockchiffring, om meddelandet inte passar snyggt i en multipel av blockstorleken, måste vi berätta för funktionen att automatiskt lägga till vaddering till slutet.
Som vanligt vid kryptering är det bäst att följa fastställda standarder. I det här fallet definierar standarden PKCS7 hur du matar in data. Vi berättar för vår krypteringsfunktion att använda denna standard genom att leverera KCCOptionPKCS7Padding
alternativ. Om du sätter allt ihop, här är den fullständiga koden för att kryptera och dekryptera en sträng.
class func encryptData (_ clearTextData: Data, medPassword lösenord: String) -> Dictionaryvar setupSuccess = true var outDictionary = Dictionary .init () var nyckel = Data (upprepa: 0, räkna: kCCKeySizeAES256) var salt = Data (räkning: 8) salt.withUnsafeMutableBytes (saltBytes: UnsafeMutablePointer ) -> Avbryt i låt saltStatus = SecRandomCopyBytes (kSecRandomDefault, salt.count, saltBytes) om saltStatus == errSecSuccess let passwordData = password.data (using: String.Encoding.utf8)! key.withUnsafeMutableBytes (keyBytes: UnsafeMutablePointer ) i let derivationStatus = CCKeyDerivationPBKDF (CCPBKDFAlgorithm (kCCPBKDF2), lösenord, passwordData.count, saltBytes, salt.count, CCPseudoRandomAlgorithm (kCCPRFHmacAlgSHA512), 14271, keyBytes, key.count) om derivationStatus! = Int32 (kCCSuccess) setupSuccess = false else setupSuccess = false var iv = Data.init (räkning: kCCBlockSizeAES128) iv.withUnsafeMutableBytes (ivBytes: UnsafeMutablePointer ) i låt ivStatus = SecRandomCopyBytes (kSecRandomDefault, kCCBlockSizeAES128, ivBytes) om ivStatus! = errSecSuccess setupSuccess = false om (setupSuccess) var numberOfBytesEncrypted: size_t = 0 låt size = clearTextData.count + kCCBlockSizeAES128 var encrypted = Data.init count: size) låt cryptStatus = iv.withUnsafeBytes ivBytes i krypterad.medUnsafeMutableBytes 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, och numberOfBytesEncrypted) om cryptStatus == Int32 (kCCSuccess) encrypted.count = numberOfBytesEncrypted outDictionary ["EncryptionData"] = krypterat utDictionary ["EncryptionIV"] = iv outDictionary ["EncryptionSalt"] = salt returneraDictionary;
Och här är dekrypteringskoden:
klass func decryp (fromDictionary ordbok: Dictionary, withPassword password: String) -> Data var setupSuccess = true låt krypterad = ordbok ["EncryptionData"] låt iv = ordbok ["EncryptionIV"] låt salt = ordbok ["EncryptionSalt"] var key = Data (upprepa: 0, räkna : kCCKeySizeAES256) salt? .withUnsafeBytes (saltBytes: UnsafePointer ) -> Avbryt i låt passwordData = password.data (using: String.Encoding.utf8)! key.withUnsafeMutableBytes (keyBytes: UnsafeMutablePointer ) i let derivationStatus = CCKeyDerivationPBKDF (CCPBKDFAlgorithm (kCCPBKDF2), lösenord, passwordData.count, saltBytes, salt! .count, CCPseudoRandomAlgorithm (kCCPRFHmacAlgSHA512), 14271, keyBytes, key.count) om derivationStatus! = Int32 (kCCSuccess) setupSuccess = false var decryptSuccess = false let size = (krypterad? .count)! + kCCBlockSizeAES128 var clearTextData = Data.init (count: storlek) om (setupSuccess) var numberOfBytesDecrypted: size_t = 0 låt cryptStatus = iv? .withUnsafeBytes ivByter i clearTextData.withUnsafeMutableBytes clearTextBytes i krypterade? .withUnsafeBytes encryptedBytes in key.withUnsafeBytes keyBytes i CCCrypt (CCOperation (kCCDecrypt), CCAlgorithm (kCCAlgorithmAES128), CCOptions (kCCOptionPKCS7Padding), keyBytes, key.count, ivBytes, encryptedBytes, (encrypted? .count) !, clearTextBytes, size & numberOfBytesDecrypted) om cryptStatus ! == Int32 (kCCSuccess) clearTextData.count = numberOfBytesDecrypted decryptSuccess = true returnera decryptSuccess? clearTextData: Data.init (räkning: 0)
Slutligen är här ett test för att säkerställa att data dekrypteras korrekt efter kryptering:
class func encryptionTest () let clearTextData = "lite klar text för att kryptera" .data (using: String.Encoding.utf8)! låt ordbok = encryptData (clearTextData, medPassword: "123456") låt dekrypterad = dekryp (fromDictionary: dictionary, withPassword: "123456") låt decryptedString = String (data: dekrypterad, kodning: String.Encoding.utf8) print ("dekrypterad cleartext resultat - ", decryptedString ??" Fel: Kunde inte konvertera data till sträng ")
I vårt exempel paketerar vi all nödvändig information och returnerar den som en Ordbok
så att alla bitar senare kan användas för att framgångsrikt dekryptera data. Du behöver bara lagra IV och salt, antingen i nyckelring eller på din server.
Detta kompletterar serien i tre delar för att säkra data i vila. Vi har sett hur man korrekt lagrar lösenord, känsliga uppgifter och stora mängder användardata. Dessa tekniker är grundlinjen för att skydda lagrad användarinformation i din app.
Det är en stor risk när en användares enhet förloras eller stulits, särskilt med senaste exploater för att få tillgång till en låst enhet. Medan många systemproblem är patchade med en mjukvaruuppdatering är själva enheten bara lika säker som användarens lösenord och version av iOS. Därför är det upp till utvecklaren av varje app att ge ett starkt skydd för känsliga data som lagras.
Alla de ämnen som omfattas hittills använder Apples ramverk. Jag kommer lämna en idé med dig att tänka på. Vad händer när Apples krypteringsbibliotek blir attackerat?
När en allmänt använd säkerhetsarkitektur äventyras, kommer alla appar som bygger på det också att äventyras. Någon av iOSs dynamiskt länkade bibliotek, särskilt på jailbroken-enheter, kan patcheras och bytas ut mot skadliga.
Men ett statiskt bibliotek som är buntat med binäret i din app är skyddat från den här typen av attack, eftersom om du försöker patchera det, ändrar du ändringen av binär bin. Detta kommer att bryta kodens signatur, förhindra att det startas. Om du importerade och använde, till exempel, OpenSSL för kryptering, skulle din app inte vara sårbar för en utbredd Apple API-attack. Du kan kompilera OpenSSL själv och länka den statiskt till din app.
Så det finns alltid mer att lära sig, och framtiden för app-säkerhet på iOS utvecklas alltid. IOS-säkerhetsarkitekturen stöder även kryptografiska enheter och smarta kort! Slutligen vet du nu de bästa metoderna för att säkra data i vila, så det är upp till dig att följa dem!
Under tiden, kolla in några av vårt andra innehåll om iOS apputveckling och appsäkerhet.