En opfølgning på, hvordan man gemmer tokens sikkert i Android

Som en prolog til denne artikel vil jeg bemærke en kort sætning til den opfattede læser. Dette tilbud vil være vigtigt, når vi går videre.

Der findes ikke absolut sikkerhed. Sikkerhed er et sæt af foranstaltninger, der stables og kombineres, der prøver at bremse det uundgåelige.

For næsten tre år siden skrev jeg et indlæg, der gav nogle ideer til at beskytte strengetegn fra en hypotetisk angriber, der dekompilerer vores Android-applikation. For at minde mig og for at afværge den uundgåelige død på Internettet gengiver jeg nogle afsnit her.

Et af de mest almindelige brugssager sker, når vores applikation skal kommunikere med en webtjeneste for at udveksle data. Denne dataudveksling kan svinge fra en mindre til en mere følsom karakter og variere mellem en loginanmodning, andragende om brugerdataændring osv.

Den absolutte første mål, der skal anvendes, er ved hjælp af en SSL-forbindelse (Secure Sockets Layer) mellem klienten og serveren. Gå igen til det indledende tilbud. Dette sikrer ikke et absolut privatliv og sikkerhed, selvom det gør et godt oprindeligt job.

Når du bruger en SSL-forbindelse (som når du ser skabet i din browser), indikerer det, at forbindelsen mellem dig og serveren er krypteret. På et teoretisk niveau kan intet få adgang til oplysningerne indeholdt i denne anmodning (*)

(*) Nævnte jeg, at den absolutte sikkerhed ikke eksisterer? SSL-forbindelser kan stadig blive kompromitteret. Denne artikel har ikke til hensigt at give en omfattende liste over alle mulige angreb, men jeg vil give dig besked om et par muligheder. Falske SSL-certifikater kan bruges såvel som Man-in-the-Middle-angreb.

Lad os komme videre. Vi antager, at vores klient kommunikerer via en krypteret SSL-kanal med vores backend. De udveksler nyttige data, gør deres forretning og er glade. Men vi ønsker at give et ekstra sikkerhedslag.

Et næste logiske trin, der anvendes i dag, er at tilvejebringe en autentificeringstoken eller API-nøgle, der skal bruges i kommunikationen. Det fungerer på denne måde. Vores backend modtager en andragende. Hvordan ved vi, at andragendet kommer fra en af ​​vores verificerede klienter, og ikke en tilfældig fyr, der prøver at få adgang til vores API? Backend vil kontrollere, om klienten leverer en gyldig API-nøgle. Hvis den forrige erklæring tilfældigvis er sand, fortsætter vi med anmodningen. Ellers benægter vi det, og afhængigt af arten af ​​vores forretning, træffer vi nogle korrigerende forholdsregler (når dette sker, vil jeg især gerne gemme IP og ID'er fra klienten for at se, hvor ofte dette sker. Når frekvensen svulmer mere end det er ønskeligt for min fine smag. Jeg overvejer et forbud eller overvåger nøje, hvad den uhøflige internetdude prøver at opnå).

Lad os konstruere vores borg fra jorden. I vores app vil vi sandsynligvis tilføje en variabel kaldet API_KEY, der automatisk injiceres i hver anmodning (hvis du bruger Android, sandsynligvis i din Retrofit-klient).

privat endelig statisk String API_KEY = “67a5af7f89ah3katf7m20fdj202”

Dette er fantastisk, og fungerer, hvis vi vil autentificere vores klient. Problemet er, at det ikke giver et meget effektivt lag i sig selv.

Hvis du bruger apktool til at dekompilere applikationen og udføre en søgning på udkig efter strenge, finder du i en af ​​de resulterende .smali-filer følgende:

const-string v1, “67a5af7f89ah3katf7m20fdj202”

Ja, sikkert. Det siger ikke, at dette er en valideringstoken, så vi er stadig nødt til at gennemgå en omhyggelig verifikation for at beslutte, hvordan vi skal nå denne streng, og om den kan bruges til godkendelsesformål eller ej. Men du ved, hvor jeg skal: dette er hovedsageligt et spørgsmål om tid og ressourcer.

Kunne Proguard hjælpe os med at sikre denne streng, så vi ikke behøver at bekymre dig om det? Ikke rigtig. Proguard siger i sin FAQ, at strengkryptering ikke er fuldstændig mulig.

Hvad med at gemme denne streng i en af ​​de andre mekanismer, der leveres af Android, såsom SharedPreferences? Dette er næppe en god idé. SharedPreferences kan nemt nås fra emulatoren eller en hvilken som helst rodfæstet enhed. For nogle år siden bevisede en fyr, der hedder Srinivas, hvordan scoringen kunne ændres i et videospil. Vi løber tør for mulighederne her!

Native Development Kit (NDK)

Jeg vil her opdatere den oprindelige model, jeg foreslog, og hvordan vi lige så godt kan itereere gennem den for at give et mere sikkert alternativ. Lad os forestille os to funktioner, der kan tjene til at kryptere og dekryptere vores data:

Intet fan her. Disse to funktioner tager en nøgleværdi og en streng, der skal kodes eller dekodes. De returnerer henholdsvis det krypterede eller det dekrypterede token. Vi kalder følgende funktion som følger:

Gæt du retningen? Det er rigtigt. Vi kunne kryptere og dekryptere vores token on demand. Dette giver et ekstra lag af sikkerhed: når koden bliver tilsløret, er den ikke længere lige så ligetil som at udføre en streng-søgning og kontrollere miljøet, der omgiver denne streng. Men kan du stadig finde ud af et problem, der skal løses?

Kan du?

Giv det et par sekunder mere, hvis du ikke har fundet ud af det endnu.

Ja, du har ret. Vi har en krypteringsnøgle, der også gemmes som streng. Dette tilføjer flere lag af sikkerhed ved uklarhed, men vi har stadig et token på almindelig tekst, uanset om dette token bruges til kryptering eller er token per se.

Lad os nu bruge NDK og fortsætte med at gentage vores sikkerhedsmekanisme.

NDK giver os adgang til en C ++ -kodebase fra vores Android-kode. Lad os tage et minut at tænke over, hvad vi skal gøre, som en første tilgang. Vi kunne have en indbygget C ++ -funktion, der gemmer en API-nøgle eller hvilke følsomme data vi prøver at gemme. Denne funktion kan senere kaldes fra koden, og ingen streng gemmes i nogen Java-fil. Dette ville give en automatisk beskyttelse mod dekompileringsteknikker.

Vores C ++ -funktion ser ud som følger:

Det kaldes nemt i din Java-kode:

Og krypterings- / dekrypteringsfunktionen kaldes som i det næste kodestykke:

Hvis vi ved, at der genereres en APK, tilslører den, dekompilerer den og forsøger at få adgang til strengen indeholdt i den oprindelige funktion getSecretKey (), vil vi ikke kunne finde den! Sejr?

Ikke rigtig. NDK-koden kan faktisk adskilles og inspiceres. Dette bliver sværere, og du begynder at kræve mere avancerede værktøjer og teknikker. Du blev af med 95% af script-børnene, men et team med nok ressourcer og motivation vil stadig kunne få adgang til token. Kan du huske denne sætning?

Der findes ikke absolut sikkerhed. Sikkerhed er et sæt af foranstaltninger, der stables og kombineres, der prøver at bremse det uundgåelige.
Du kan stadig få adgang i adskilt kode String letterals!

Hex Rays gør for eksempel et meget godt stykke arbejde med at dekompilere native filer. Jeg er sikker på, at der også er en masse værktøjer, der kan dekonstruere enhver oprindelig kode, der er genereret med Android (jeg er ikke tilknyttet Hex Rays, som hverken modtager nogen form for monetær kompensation fra dem).

Så hvilken løsning kunne vi bruge til at kommunikere mellem en backend og en klient uden at blive markeret?

Generer nøglen i realtid på enheden.

Din enhed behøver ikke opbevare nogen form for nøgle og håndtere al besværet med at beskytte en streng bogstavelig! Dette er en meget gammel teknik, der bruges af tjenester såsom validering af fjerntaster.

  1. Klienten kender en funktion (), der returnerer en nøgle.
  2. Backend kender funktionen () implementeret i klienten
  3. Klienten genererer en nøgle gennem funktionen (), og denne leveres til serveren.
  4. Serveren validerer den og fortsætter med anmodningen.

Tilslutter du prikkerne? I stedet for at have en indbygget funktion, der returnerer en streng (let identificerbar), hvorfor ikke have en funktion, der returnerer summen af ​​tre tilfældige primtal mellem 1 og 100? Eller en funktion, der tager den aktuelle dag udtrykt i unixtime og tilføjer en 1 til hvert andet ciffer? Hvad med at tage nogle kontekstuelle oplysninger fra enheden, såsom den mængde hukommelse, der bruges, til at give en højere grad af entropi?

Det sidste afsnit indeholder en række ideer, men vores hypotetiske læser har forhåbentlig taget hovedpointen.

Resumé

  1. Der findes ikke absolut sikkerhed.
  2. Kombination af et sæt beskyttelsesforanstaltninger er nøglen til at opnå en høj grad af sikkerhed.
  3. Opbevar ikke streng bogstaver i din kode.
  4. Brug NDK til at oprette en selvgenereret nøgle.

Kan du huske den første sætning?

Der findes ikke absolut sikkerhed. Sikkerhed er et sæt af foranstaltninger, der stables og kombineres, der prøver at bremse det uundgåelige.

Jeg vil gerne påpege endnu en gang, at dit mål er at beskytte din kode så meget som muligt uden at miste perspektivet om, at 100% af sikkerheden er uopnåelig. Men hvis du er i stand til at beskytte din kode på en måde, der kræver en enorm mængde ressourcer til at dekryptere alle fornuftige oplysninger, du har, vil du være i stand til at sove godt og roligt.

En lille ansvarsfraskrivelse

Jeg ved. Du kom indtil her, og tænker gennem hele artiklen "hvordan kan denne fyr ikke nævne Dexguard og gå gennem alt besværet?". Du har ret. Dexguard kan faktisk tilsløre strengene, og de gør et meget godt stykke arbejde på det. Dexguard-priser kan imidlertid være uoverkommelige. Jeg har brugt Dexguard i tidligere virksomheder med kritiske sikkerhedssystemer, men dette er muligvis ikke en mulighed for alle. Og inden for softwareudvikling såvel som i livet, jo flere muligheder har du, jo rigere og mere rigeligt får verden.

God kodning!

Jeg skriver mine tanker om Software Engineering og livet generelt på min Twitter-konto. Hvis du har ønsket denne artikel, eller den hjalp dig, er du velkommen til at dele den, ♥ den og / eller efterlade en kommentar. Dette er den valuta, der brænder amatørforfattere.