Mobil säkerhet har blivit ett hett ämne. För alla appar som kommunicerar på distans är det viktigt att överväga säkerheten för användarinformation som skickas över ett nätverk. I det här inlägget läser du nuvarande bästa praxis för att säkra kommunikationen för din iOS-app i Swift.
När du utvecklar din app, överväga att begränsa nätverksförfrågningar till de som är nödvändiga. För dessa förfrågningar, se till att de är gjorda över HTTPS och inte över HTTP. Det här hjälper till att skydda användarens data från "man i mitten attacker", där en annan dator i nätverket fungerar som ett relä för din anslutning men lyssnar på eller ändrar de data som den passerar tillsammans. Trenden de senaste åren är att ha alla anslutningar gjorda över HTTPS. Lyckligtvis för oss, tillämpar nyare versioner av Xcode redan detta.
För att skapa en enkel HTTPS-förfrågan på iOS är allt vi behöver göra att lägga till "s
" till "http
"av webbadressen. Så länge som värden stöder HTTPS och har giltiga certifikat, kommer vi att få en säker anslutning. Det fungerar för API: er, t.ex. URLSession
, NSURLConnection
, och CFNetwork, liksom populära tredje parts bibliotek som AFNetworking.
Under åren har HTTPS haft flera attacker mot det. Eftersom det är viktigt att ha HTTPS-konfiguration korrekt, har Apple skapat App Transport Security (ATS för kort). ATS säkerställer att appens nätverksanslutningar använder protokoll för industristandard, så att du inte oavsiktligt skickar användardata osäkert. Den goda nyheten är att ATS är aktiverat som standard för appar som är byggda med nuvarande versioner av Xcode.
ATS är tillgänglig från iOS 9 och OS X El Capitan. Aktuella appar i butiken behöver inte plötsligt ATS, men appar som är byggda mot nyare versioner av Xcode och dess SDK-filer kommer att ha det aktiverat som standard. Några av de bästa metoderna som ATS tillämpar omfattar bland annat TLS version 1.2 eller senare, framåtriktad sekretess genom ECDHE-nyckelutbyte, AES-128-kryptering och användningen av minst SHA-2-certifikat.
Det är viktigt att notera att medan ATS är aktiverat automatiskt betyder det inte nödvändigtvis att ATS verkställs i din app. ATS arbetar på grundklassen som URLSession
och NSURLConnection
och strömbaserade CFNetwork-gränssnitt. ATS verkställs inte på nätverksgränssnitt på lägre nivå, t.ex. råuttag, CFNetwork-uttag eller någon tredje parts bibliotek som skulle använda dessa lägre samtal. Så om du använder nätverksnätverk, måste du vara försiktig med att implementera ATS bästa metoder manuellt.
Eftersom ATS tillämpar HTTPS och andra säkra protokoll kanske du undrar om du fortfarande kan göra nätverksanslutningar som inte kan stödja HTTPS, till exempel när du laddar ner bilder från en CDN-cache. Oroa dig inte, du kan styra ATS-inställningar för specifika domäner i projektets plistfil. I Xcode, hitta din Info.plist fil, högerklicka på den och välj Öppna As> Källkod.
Du hittar ett avsnitt som heter NSAppTransportSecurity
. Om det inte finns där kan du själv lägga till koden; formatet är som följer.
NSAppTransportSecurity NSExceptionDomains yourdomain.com NSIncludesSubdomains NSThirdPartyExceptionRequiresForwardSecrecy
Detta låter dig ändra ATS-inställningar för alla nätverksanslutningar. Några av de vanliga inställningarna är följande:
NSAllowsArbitraryLoads
: Inaktiverar ATS. Använd inte detta! Framtida versioner av Xcode tar bort den här nyckeln.NSAllowsArbitraryLoadsForMedia
: Tillåter lastning av media utan ATS-begränsningar för AV-stiftelsens ramverk. Du får endast tillåta osäkra belastningar om ditt medium redan är krypterat med ett annat medel. (Finns på iOS 10 och MacOS 10.12.)NSAllowsArbitraryLoadsInWebContent
: Kan användas för att stänga av ATS-begränsningarna från webbvisningsobjekt i din app. Tänk först innan du stänger av det eftersom det tillåter användare att lägga in godtyckligt osäkert innehåll i din app. (Finns på iOS 10 och MacOS 10.12.)NSAllowsLocalNetworking
: Detta kan användas för att tillåta lokala nätverksresurser att laddas utan ATS-begränsningar. (Finns på iOS 10 och MacOS 10.12.)De NSExceptionDomains
ordbok kan du ställa in inställningar för specifika domäner. Här är en beskrivning av några av de användbara nycklarna som du kan använda för din domän:
NSExceptionAllowsInsecureHTTPLoads
: Tillåter att den specifika domänen använder icke-HTTPS-anslutningar.NSIncludesSubdomains
: Anger om de nuvarande reglerna skickas ner till underdomäner.NSExceptionMinimumTLSVersion
: Används för att ange äldre, mindre säkra TLS-versioner som är tillåtna.Medan krypterad trafik är oläslig, kan den fortfarande bli lagrad. Om den privata nyckeln som används för att kryptera den trafiken kompromissas i framtiden kan nyckeln användas för att läsa all tidigare lagrad trafik.
För att förhindra denna typ av kompromiss genererar Perfekt vidarebefordrad sekretess (PFS) en sessionsnyckelsom är unik för varje kommunikationssession. Om nyckeln för en viss session äventyras kommer den inte att kompromissa med data från andra sessioner. ATS implementerar PFS som standard, och du kan styra den här funktionen med plist-tangenten NSExceptionRequiresForwardSecrecy
. Om du stänger av detta tillåter du TLS-cifre som inte stöder perfekt framåtriktad sekretess.
Certificate Transparency är en kommande standard som är utformad för att kunna kontrollera eller granska de certifikat som presenteras under installationen av en HTTPS-anslutning.
När din värd sätter upp ett HTTPS-certifikat utfärdas det av det som kallas en certifikatmyndighet (CA). Certificate Transparency syftar till att ha nära kontroll i realtid för att ta reda på om ett certifikat utfärdats ondskanligt eller har utfärdats av en kompromissad certifikatmyndighet.
När ett certifikat är utfärdat måste certifikatmyndigheten skicka in intyget till ett antal certifikatloggar, som endast kan bifogas, som senare kan kryssmarkeras av kunden och granskas av domänens ägare. Certifikatet måste finnas i minst två loggar för att certifikatet ska vara giltigt.
Plistnyckeln för den här funktionen är NSRequiresCertificateTransparency
. Om du aktiverar detta, kommer du att genomföra certifikatgenomsynlighet. Detta är tillgängligt på iOS 10 och MacOS 10.12 och senare.
När du köper ett certifikat för att använda HTTPS på din server, sägs det att certifikatet är legitimt eftersom det är signerat med ett certifikat från en mellanliggande certifikatmyndighet. Det certifikat som används av den mellanliggande myndigheten kan i sin tur undertecknas av en annan mellanliggande myndighet, och så vidare, så länge som det sista certifikatet är signerat av en rootcertifikatmyndighet som är betrodd.
När en HTTPS-anslutning är etablerad presenteras dessa certifikat för kunden. Denna förtroendekedja utvärderas för att säkerställa att certifikaten är korrekt signerade av en certifikatmyndighet som redan är betrodd av iOS. (Det finns sätt att kringgå den här kontrollen och att acceptera ditt eget självsignerade certifikat för testning, men gör det inte i en produktionsmiljö.)
Om något av certifikaten i förtroendekedjan inte är giltigt, sägs hela certifikatet vara ogiltigt och dina data skickas inte ut över den otillförlitliga anslutningen. Även om detta är ett bra system är det inte idiotiskt. Det finns olika svagheter som kan göra att IOS lita på en angripares certifikat istället för ett legitimt undertecknat certifikat.
Till exempel kan avlyssningsfullmäktige ha ett mellanliggande intyg som är betrodd. En omvänd ingenjör kan manuellt instruera IOS att acceptera sitt eget certifikat. Dessutom kan ett företags policy ha gjort det möjligt för enheten att acceptera sitt eget certifikat. Allt detta leder till möjligheten att utföra en "man i mitten" attack på din trafik, så att den kan läsas. Men certifikatbeläggning hindrar att anslutningar etableras för alla dessa scenarier.
Certifikatbeläggning kommer till räddning genom att kontrollera serverns certifikat mot en kopia av det förväntade certifikatet.
För att genomföra pinning måste följande delegat genomföras. För URLSession
, Använd följande:
valfri func urlSession (_ session: URLSession, didReceive utmaning: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void)
Eller för NSURLConnection
, du kan använda:
valfri func-anslutning (_-anslutning: NSURLConnection, didReceive challenge: URLAuthenticationChallenge)
Båda metoderna tillåter dig att få en SecTrust
objekt från challenge.protectionSpace.serverTrust
. Eftersom vi överväger autentiseringsdelegaterna måste vi nu uttryckligen ringa den funktion som utför de standard certifikatkedjekontroller som vi just har diskuterat. Gör detta genom att ringa SecTrustEvaluate
fungera. Då kan vi jämföra serverns certifikat med en förväntad.
Här är ett exempel implementering.
Importera Foundation Import Säkerhetsklass URLSessionPinningDelegate: NSObject, URLSessionDelegate func urlSession (_ session: URLSession, didReceive utmaning: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Swift.Void) var framgång: Bool = låt serverTrust = challenge.protectionSpace.serverTrust if (challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust) // Ange policy för att validera domänlåt policy: SecPolicy = SecPolicyCreateSSL (true, "yourdomain.com" som CFString) låt policies = NSArray. init (objekt: policy) SecTrustSetPolicies (serverTrust, policy) låt certifikatCount: CFIndex = SecTrustGetCertificateCount (serverTrust) om certifikatCount> 0 om låt certifikat = SecTrustGetCertificateAtIndex (serverTrust, 0) låt serverCertificateData = SecCertificateCopyData (certifikat) som NSData // för loop över array som kan innehålla utgått + kommande certifikat låt certFilenames: [S tring] = ["CertificateRenewed", "Certificate"] för filenameString: String i certFilenames let filePath = Bundle.main.path (forResource: filenameString, ofType: "cer") om låt filen = filePath if let localCertData = NSData contentOfFile: file) // Ställ ankarcertifikat på din egen server om låt localCert: SecCertificate = SecCertificateCreateWithData (nil, localCertData) låt certArray = [localCert] som CFArray SecTrustSetAnchorCertificates (serverTrust, certArray) // validerar ett certifikat genom att verifiera signatur plus certifikatets signaturer i certifikatkedjan, upp till ankarcertifikatet var result = SecTrustResultType.invalid SecTrustEvaluate (serverTrust, & result); låt isValid: Bool = (resultat == SecTrustResultType.unspecified || result == SecTrustResultType.proceed) om (isValid) // Validera värdcertifikat mot inlagrat certifikat. om serverCertificateData.isEqual (till: localCertData som data) success = true completionHandler (.useCredential, URLCredential (trust: serverTrust)) break // hitta ett framgångsrikt certifikat, behöver inte fortsätta looping // enda om serverCertificateData.isEqual (till: localCertData som data) // // om (isValid) // slut om låt localCertData = NSData (contentOfFile: file) // slut om låt filen = filePath // sluta för filenameString: String i certFilenames / / avsluta om låt certifikat = SecTrustGetCertificateAtIndex (serverTrust, 0) // avsluta om certifikatCount> 0 // avsluta om (challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust) // slut om låt serverTrust = challenge.protectionSpace.serverTrust if framgång == false) completionHandler (.cancelAuthenticationChallenge, nil)
Om du vill använda den här koden anger du delegaten för URLSession
när du skapar din anslutning.
om låt url = NSURL (sträng: "https://yourdomain.com") let session = URLSession (konfiguration: URLSessionConfiguration.ephemeral, delegera: URLSessionPinningDelegate (), delegateQueue: nil) låt dataTask = session.dataTask (med: url som URL, completionHandler: (data, svar, fel) -> Avbryt i // ...) dataTask.resume ()
Var noga med att inkludera certifikatet i appbunten. Om ditt certifikat är en .pem-fil måste du konvertera det till en .cer-fil i macOS-terminalen:
openssl x509 -inform PEM -in mycert.pem -outform DER -out certificate.cer
Om certifikatet ändras av en angripare, kommer din app att upptäcka den och vägra att ansluta.
Observera att vissa tredjepartsbibliotek, t.ex. AFNetworking, stöds redan.
Med alla skydd så långt måste dina anslutningar vara ganska säkra mot människan i mittattackerna. Trots det är en viktig regel när det gäller nätverkskommunikation aldrig att blinda lita på de uppgifter du får. Faktum är att det är bra programmeringspraxis att design enligt kontrakt. DeIngångar och utgångar från dina metoder har ett kontrakt som definierar specifika gränssnittsförväntningar. om gränssnittet säger att det kommer att returnera en NSNumber
, då borde det göra det. Om din server förväntar sig en sträng på 24 tecken eller färre, se till att gränssnittet bara returnerar upp till 24 tecken.
Detta hjälper till att förhindra oskyldiga fel, men viktigare, det kan också minska sannolikheten för olika injektions- och minneskorruptionsattacker. Vanliga parsers som JSONSerialization
klassen konverterar text till svarta datatyper där dessa typer av test kan göras.
om låt ordboken = json som? [String: Any] om låt count = dictionary ["count"] som? Int // ...
Andra parsers kan fungera med Objective-C-ekvivalenta objekt. Här är ett sätt att validera att ett objekt är av den förväntade typen i Swift.
om someObject är NSArray
Innan du skickar en delegat en metod, se till att objektet är av rätt typ så att det kommer att svara på metoden. Annars kommer appen att krascha med ett "okänt väljare" -fel.
om someObject.responds (till: #selector (getter: NSNumber.intValue)
Dessutom kan du se om ett objekt överensstämmer med ett protokoll innan du försöker skicka meddelanden till det:
om someObject.conforms (till: MyProtocol.self)
Eller du kan kontrollera att den matchar en Core Foundation-objekttyp.
om CFGetTypeID (someObject)! = CFNullGetTypeID ()
Det är en bra idé att noggrant välja vilken information från servern användaren kan se. Det är till exempel en dålig idé att visa en felmeddelande som direkt skickar ett meddelande från servern. Felmeddelanden kan avslöja felsökning och säkerhetsrelaterad information. En lösning är att servern ska skicka specifika felkoder som gör att klienten kan visa fördefinierade meddelanden.
Se till att du kodar dina webbadresser så att de bara innehåller giltiga tecken. NSString
's stringByAddingPercentEscapesUsingEncoding
kommer att funka. Det kodar inte några tecken som ampersands och plus tecken, men CFURLCreateStringByAddingPercentEscapes
funktionen möjliggör anpassning av vad som ska kodas.
När du skickar data till en server, var extremt försiktig när någon användarinmatning skickas till kommandon som ska utföras av en SQL-server eller en server som kör kod. Samtidigt som säkra en server mot sådana attacker ligger utanför ramen för den här artikeln, som mobilutvecklare kan vi göra vår del genom att ta bort tecken för språket som servern använder så att ingången inte är mottaglig för injektionsattacker. Exempel kan vara att rita citat, semikolon och snedstreck när de inte behövs för den specifika användarinmatningen.
var mutableString: String = sträng mutableString = mutableString.replacingOccurrences (av: "%" med: "" mutableString = mutableString.replacingOccurrences (av: "\" "med:" ") mutableString = mutableString.replacingOccurrences \ "" med: "") mutableString = mutableString.replacingOccurrences (av: "\ t" med: "") mutableString = mutableString.replacingOccurrences (av: "\ n" med:
Det är bra att begränsa längden på användarinmatningen. Vi kan begränsa antalet tecken som skrivits i ett textfält genom att ställa in UITextField
s delegerade och sedan genomföra sin shouldChangeCharactersInRange
delegera metod.
func textField (_ textField: UITextField, shouldChangeCharactersIn: NSRange, replacementString-sträng: String) -> Bool låt newLength: Int = textField.text! .characters.count + string.characters.count - range.length om newLength> maxSearchLength returnera false else return true
För en UITextView är delegatmetoden för att genomföra detta:
valfri func textField (_ textField: UITextField, shouldChangeCharactersIn intervall: NSRange, replacementString-sträng: String) -> Bool
Användarinmatning kan valideras ytterligare så att ingången är av ett förväntat format. Om en användare exempelvis ska ange en e-postadress kan vi söka efter en giltig adress:
klass func validateEmail (från emailString: String, useStrictValidation isStrict: Bool) -> Bool var filterString: String? = noll om isStrict filterString = "[A-Z0-9a-z ._% + -] + @ [A-Za-z0-9 .-] + \\. [A-Za-z] 2,4 " annat filterString =". + @. + \\. [A-Za-z] 2 [A-Za-z] * "Låt emailPredicate = NSPredicate (format:" SELF MATCHES% @ " filterString!) returnera emailPredicate.evaluate (med: emailString)
Om en användare laddar upp en bild till servern kan vi kontrollera att det är en giltig bild. Till exempel, för en JPEG-fil, är de första två byte och sista två byte alltid FF D8 och FF D9.
klass func validateImageData (_ data: Data) -> Bool låt totalBytes: Int = data.count om totalBytes < 12 return false let bytes = [UInt8](data) let isValid: Bool = (bytes[0] == UInt8(0xff) && bytes[1] == UInt8(0xd8) && bytes[totalBytes - 2] == UInt8(0xff) && bytes[totalBytes - 1] == UInt8(0xd9)) return isValid
Listan fortsätter, men bara du som utvecklare kommer att veta vad den förväntade inmatningen och produktionen ska vara, med tanke på designkraven.
Data som du skickar via nätverket har potential att cachas i minnet och på lagring av enheter. Du kan gå långt för att skydda din nätverkskommunikation, som vi bara har gjort för att få reda på att kommunikationen lagras.
Olika versioner av iOS har haft något oväntat beteende när det gäller cache-inställningarna, och vissa regler för vad som blir cachade i iOS fortsätter att ändras över versionerna. Medan caching hjälper nätverksprestanda genom att minska antalet förfrågningar, stängs av det för att data som du tycker är mycket känslig kan vara en bra idé. Du kan när som helst ta bort den delade cachen (t.ex. vid appstart) genom att ringa:
URLCache.shared.removeAllCachedResponses ()
För att inaktivera caching på global nivå, använd:
låt theURLCache = URLCache (memoryCapacity: 0, diskCapacity: 0, diskPath: nil) URLCache.shared = theURLCache
Och om du använder URLSession
, du kan inaktivera cache för sessionen så här:
låt konfiguration = URLSessionConfiguration.default configuration.requestCachePolicy = .reloadIgnoringLocalCacheData configuration.urlCache = nil låt session = URLSession.init (konfiguration: konfiguration)
Om du använder en NSURLConnection
objekt med en delegat, kan du inaktivera cachen per anslutning med den här delegatemetoden:
func-anslutning (_ anslutning: NSURLConnection, willCacheResponse cachedResponse: CachedURLResponse) -> CachedURLResponse? return nil
Och om du vill skapa en webbadressförfrågan som inte kontrollerar cacheminnet använder du:
var request = NSMutableURLRequest (url: theUrl, cachePolicy: .reloadIgnoringLocalCacheData, timeoutInterval: urlTimeoutTime)
Olika versioner av iOS 8 hade några buggar där några av dessa metoder på egen hand skulle göra ingenting. Det betyder att det är en bra idé att genomföra Allt av ovanstående kod för känsliga anslutningar, när du behöver tillförlitligt förhindra caching av nätverksförfrågningar.
Det är viktigt att förstå gränserna för HTTPS för att skydda nätverkskommunikation.
I de flesta fall stannar HTTPS på servern. Till exempel kan min anslutning till en företags server vara över HTTPS, men när den trafiken träffar servern är den okrypterad. Det innebär att bolaget kommer att kunna se den information som skickades (i de flesta fall behöver den), och det betyder också att företaget då kan få fullmakt eller vidarebefordra informationen igen okrypterad.
Jag kan inte avsluta den här artikeln utan att täcka ytterligare ett koncept som är en ny trend - det kallas "end-to-end-kryptering". Ett bra exempel är en krypterad chattapp där två mobila enheter kommunicerar med varandra via en server. De två enheterna skapar offentliga och privata nycklar - de byter offentliga nycklar, medan de privata nycklarna aldrig lämnar enheten. Data skickas fortfarande över HTTPS via servern, men den krypteras först av den andra partens allmänna nyckel, så att endast enheter som håller de privata nycklarna kan dekryptera varandra meddelanden.
Som en analogi som hjälper dig att förstå end-to-end-kryptering kan du föreställa dig att jag vill att någon ska skicka ett meddelande på ett säkert sätt så att jag bara kan läsa. Så jag ger dem en låda med ett öppet hänglås på den (den offentliga nyckeln) medan jag håller hänglåsnyckeln (privat nyckel). Användaren skriver ett meddelande, lägger det i lådan, låser hänglåset och skickar det tillbaka till mig. Bara jag kan läsa vad meddelandet är för att jag är den enda med nyckeln till att låsa upp hänglåset.
Med end-to-end-kryptering tillhandahåller servern en tjänst för kommunikation, men den kan inte läsa innehållet i kommunikationen - de skickar den låsta rutan, men de har inte nyckeln för att öppna den. Medan implementeringsinformationen ligger utanför ramen för denna artikel är det ett kraftfullt koncept om du vill tillåta säker kommunikation mellan användare av din app.
Om du vill lära dig mer om detta tillvägagångssätt, är en plats att starta GitHub repo för Open Whisper System, ett open source-projekt.
Nästan alla mobilappar idag kommer att kommunicera över ett nätverk, och säkerhet är en kritiskt viktig men ofta försummad aspekt av mobilapputveckling.
I den här artikeln har vi täckt några bästa metoder för säkerhet, inklusive enkla HTTPS, programhärdning av nätverkskommunikation, datasanering och end-to-end-kryptering. Dessa bästa metoder bör fungera som grund för säkerhet när du kodar din mobilapp.
Och medan du är här, kolla in några av våra andra populära iOS-appstudier och kurser!