Проследяване на това как да съхранявате жетони в Android

Като пролог към тази статия, искам да отбележа кратко изречение за читателския читател. Този цитат ще бъде важен, когато вървим напред.

Абсолютна сигурност не съществува. Сигурността е набор от мерки, като се събират и комбинират, опитвайки се да забавят неизбежното.

Преди почти три години написах публикация, в която дадох няколко идеи за защита на String маркери от хипотетичен нападател, декомпилиращ нашето приложение за Android. За спомен и за да избегна неизбежната смърт на Интернет, тук възпроизвеждам някои раздели.

Един от най-често срещаните случаи на използване се случва, когато нашето приложение трябва да комуникира с уеб услуга, за да обменя данни. Този обмен на данни може да се колебае от по-малко до по-чувствителен характер и да варира между заявка за вход, петиция за промяна на потребителски данни и т.н.

Абсолютната първа мярка, която трябва да се приложи, е използването на SSL (Secure Sockets Layer) връзка между клиента и сървъра. Отидете отново на първоначалната оферта. Това не гарантира абсолютна поверителност и сигурност, въпреки че прави добра първоначална работа.

Когато използвате SSL връзка (например, когато видите шкафчето в браузъра си), това показва, че връзката между вас и сървъра е криптирана. На теоретично ниво нищо не може да получи достъп до информацията, съдържаща се в тези заявки (*)

(*) Споменах ли, че абсолютната сигурност не съществува? SSL връзките все още могат да бъдат компрометирани. Тази статия няма намерение да предоставя обширен списък на всички възможни атаки, но искам да ви информирам за няколко възможности. Могат да се използват фалшиви SSL сертификати, както и Man-in-the-Middle атаки.

Нека продължим напред Предполагаме, че клиентът ни комуникира чрез шифрован SSL канал с нашия резервен. Те обменят полезни данни, правят своя бизнес, щастливи са. Но искаме да осигурим допълнителен слой за сигурност.

Следваща логическа стъпка, използвана в наши дни, е да се предостави маркер за удостоверяване или API ключ, който да се използва в комуникацията. Той работи по този начин. Нашият бекенд получава петиция. Как да разберем, че петицията идва от един от нашите проверени клиенти, а не от случаен пич, който се опитва да получи достъп до нашия API? Входният текст ще провери дали клиентът предоставя валиден API ключ. Ако предходното изявление се окаже вярно, тогава продължаваме със заявката. В противен случай ние го отричаме и в зависимост от естеството на нашия бизнес предприемаме някои коригиращи мерки (когато това се случва, особено обичам да съхранявам IP и идентификационните номера от клиента, за да видя колко често това се случва. Когато честотата се подува повече от желателно е за хубавия ми вкус, аз смятам забрана или да наблюдавам отблизо какво се опитва да постигне безчувственият интернет пич).

Нека построим замъка си от земята. В нашето приложение вероятно ще добавим променлива наречена API_KEY, която автоматично се инжектира във всяка заявка (ако използвате Android, вероятно във вашия клиент за модернизация).

частен финален статичен низ API_KEY = „67a5af7f89ah3katf7m20fdj202“

Това е чудесно и работи, ако искаме да удостоверим клиента си. Проблемът е, че не осигурява много ефективен слой сам по себе си.

Ако използвате apktool за декомпилиране на приложението и извършване на търсене, търсейки низове, ще намерите в един от получените .smali файлове следното:

const-string v1, „67a5af7f89ah3katf7m20fdj202“

Да, разбира се. Не се казва, че това е маркер за валидиране, така че все още трябва да преминем през щателна проверка, за да решим как да достигнем до този низ и дали той може да се използва за целите на удостоверяването или не. Но знаете къде отивам: това е най-вече въпрос на време и ресурси.

Може ли Proguard да ни помогне да обезопасим този низ, така че да не се налага да се тревожим за него? Не точно. Proguard заявява в своите FAQ, че String криптирането не е напълно възможно.

Какво ще кажете да запазите този низ в един от другите механизми, осигурени от Android, като SharedPreferences? Това едва ли е добра идея. SharedPreferences могат лесно да бъдат достъпни от емулатора или всяко коренно устройство. Преди няколко години човек, наречен Шринивас, доказа как може да бъде променен резултатът във видеоигра. Тук ни липсват опции!

Native Development Kit (NDK)

Тук ще освежа първоначалния модел, който предложих, и как можем също така да повторим чрез него, за да осигурим по-сигурна алтернатива. Да представим две функции, които могат да служат за криптиране и дешифриране на нашите данни:

Тук няма нищо фантазия. Тези две функции ще вземат ключова стойност и низ, който ще бъде кодиран или декодиран. Те ще върнат съответно криптираното или декриптираното означение. Ще наречем следната функция, както следва:

Отгатвате посоката? Точно така. Можехме да шифроваме и дешифрираме маркера си при поискване. Това осигурява допълнителен слой сигурност: когато кодът се обърка, той вече не е толкова прост, колкото извършването на търсене на String и проверка на средата около този String. Но все пак можете ли да откриете проблем, който трябва да бъде решен?

Можеш ли?

Дайте му няколко секунди повече, ако още не сте го измислили.

Да, прав си. Имаме ключ за криптиране, който също се съхранява като String. Това добавя повече слоеве на сигурност от неизвестност, но все още имаме означение за обикновен текст, независимо дали този маркер се използва за криптиране или е токенът сам по себе си.

Нека сега използваме NDK и продължаваме да повтаряме нашия механизъм за сигурност.

NDK ни позволява да имаме достъп до C ++ кодова база от нашия Android код. Като първи подход, нека отделим малко време, за да помислим какво да правим. Можем да имаме собствена функция C ++, която съхранява API ключ или каквито и да са чувствителни данни, които се опитваме да съхраняваме. По-късно тази функция може да бъде извикана от кода и нито един низ няма да се съхранява във всеки Java файл. Това би осигурило автоматична защита срещу техники на декомпилиране.

Нашата C ++ функция ще изглежда така:

Той ще бъде извикан лесно във вашия Java код:

И функцията за криптиране / дешифриране ще бъде извикана както в следващия фрагмент:

Ако знаем, че генерираме APK, обсеменяваме го, декомпилираме го и се опитваме да получим достъп до низа, съдържащ се в нативната функция getSecretKey (), няма да можем да го намерим! Победата?

Не точно. Кодът на NDK всъщност може да бъде разглобен и инспектиран. Това става все по-трудно и започвате да изисквате по-модерни инструменти и техники. Отървахте се от 95% от децата на скрипт, но екип с достатъчно ресурси и мотивация все пак ще има достъп до маркера. Спомняте ли си това изречение?

Абсолютна сигурност не съществува. Сигурността е набор от мерки, като се събират и комбинират, опитвайки се да забавят неизбежното.
Все още можете да получите достъп в разглобен код String литерали!

Hex Rays, например, прави много добра работа при декомпилирането на родните файлове. Сигурен съм, че има и куп инструменти, които биха могли да деконструират всеки местен код, генериран с Android (не съм свързан с Hex Rays, нито получавам никакъв вид парична компенсация от тях).

Така че кое решение бихме могли да използваме, за да комуникираме между бекенд и клиент, без да бъдем маркирани?

Генерирайте ключа в реално време на устройството.

Устройството ви няма нужда да съхранява какъвто и да е ключ и да се справя с всички проблеми с защитата на String literal! Това е много стара техника, използвана от услуги като отдалечена проверка на ключовете.

  1. Клиентът знае функция (), която връща ключ.
  2. Бекендът знае функцията (), реализирана в клиента
  3. Клиентът генерира ключ чрез функцията () и това се доставя на сървъра.
  4. Сървърът го валидира и пристъпва към заявката.

Свързвате ли точките? Вместо да имате собствена функция, която ви връща низ (лесно разпознаваем), защо да нямате функция, която ви връща сумата от три произволни прости числа между 1 и 100? Или функция, която приема текущия ден, изразена в unxtime, и добавя 1 към всяка различна цифра? Какво ще кажете да вземете някаква контекстуална информация от устройството, като например използваната памет, за да осигурите по-висока степен на ентропия?

Последният параграф включва поредица от идеи, но нашият хипотетичен читател се надява да вземе основната точка.

резюме

  1. Абсолютна сигурност не съществува.
  2. Комбинирането на набор от мерки за защита е ключът към постигането на висока степен на сигурност.
  3. Не съхранявайте String литерали във вашия код.
  4. Използвайте NDK, за да създадете самостоятелно генериран ключ.

Спомняте ли си първото изречение?

Абсолютна сигурност не съществува. Сигурността е набор от мерки, като се събират и комбинират, опитвайки се да забавят неизбежното.

Искам да отбележа още веднъж, че целта ви е да защитите колкото е възможно повече вашия код, без да губите перспектива, че 100% от сигурността е недостижима. Но ако сте в състояние да защитите кода си по начин, който изисква огромно количество ресурси, за да декриптирате всяка разумна информация, която имате, ще можете да спите добре и спокойно.

Малка отказ от отговорност

Знам. До тук стигнахте, мислейки в цялата статия „как този човек не споменава Dexguard и преминава през цялата караница?“. Ти си прав. Dexguard всъщност може да затъмни струните и те вършат много добра работа в това. Ценообразуването на Dexguard обаче може да бъде прекомерно. Използвал съм Dexguard в предишни компании с критични системи за сигурност, но това може да не е опция за всички. И в разработката на софтуер, както и в живота, толкова повече опции имаш по-богатият и по-изобилен свят.

Честито кодиране!

Пиша своите мисли за софтуерното инженерство и живота като цяло в акаунта си в Twitter. Ако тази статия ви е харесала или тя ви е помогнала, не се колебайте да я споделите, ♥ и / или оставете коментар. Това е валутата, която подхранва любителските писатели.