Android Keystore — је системски механизам Android-а за безбедно чување криптографских кључева у хардверској изолацији. Систем користи Trusted Execution Environment (TEE) на уређајима са ARM TrustZone или посебни Secure Element за заштиту кључева на нивоу чипа. Према Android Open Source Project-u, Keystore подржава алгоритме RSA, EC, AES и HMAC са генерацијом кључева директно у заштићеном окружењу.
Главно
Android Keystore — је криптографски провајдер (provider) имплементиран у Android-у од API 1 (Android 1.0), али пуна хардверска подршка се појавила са Android 4.3 (API 18). Keystore решава проблем безбедног чувања приватних кључева на такав начин да чак и при компромитацији оперативног система, нападач не може да извуче кључеве у отвореном облику.
Архитектура Android Keystore-a састоји се од три нивоа: примјенског API (java.security.KeyStore), системске услуге (keystore daemon) и хардверског нивоа (Keymaster HAL). Апликација приступа кроз стандардни API Java Cryptography Architecture (JCA), а системска услуга усмерава захтеве ка Keymaster-у који ради у TEE.
Све криптографске операције са кључевима (потпис, дешифровање) се извршавају унутар TEE или Secure Element. Кључеви никада не напуштају заштићено окружење — апликација добија само идентификатор (алиас) за позивање на кључ. Ово је фундаментална разлика у односу на софтверске KeyStore-ове, где су кључеви потенцијално доступни у меморији процеса.
Стандардни JKS (Java KeyStore) или BKS (Bouncy Castle) чувају кључеве у датотекама заштићеним лозинком. Android Keystore чува кључеве у хардверској изолацији, где су заштићени чак и од root корисника. JKS је рањив на директан приступ фајл систему, Android Keystore — није.
Друга разлика: у Android Keystore-у кључеви имају строге параметре коришћења (purpose — само sign/verify/encrypt/decrypt) који се постављају при генерацији. Они се не могу касније мењати, што спречава злоупотребу кључа.
При креирању новог кључа, апликација позива KeyPairGenerator или KeyGenerator са KeyGenParameterSpec-ом, који садржи све параметре будућег кључа. Систем прослеђује захтев Keymaster HAL-у, који генерише кључ унутар TEE и враћа идентификатор.
Метода KeyGenParameterSpec.Builder прихвата обавезне параметре: име кључа у Keystore-у, намену (PURPOSE_SIGN, PURPOSE_ENCRYPT), алгоритам (RSA, EC, AES). Додатно: digest (SHA-256), padding (PKCS7), userAuthenticationRequired (биометрија), keyValidityStart/End (временска ограничења).
Након постављања параметара, KeyPairGenerator.generateKeyPair() враћа KeyPair, где је PrivateKey објекат који делегира операције Keymaster-у. Јавни кључ може да се извуче, приватни — не може. Постоји само унутар TEE.
import java.security.KeyPairGenerator
import android.security.keystore.KeyGenParameterSpec
import android.security.keystore.KeyProperties
fun generateKey(alias: String) {
val spec = KeyGenParameterSpec.Builder(alias,
KeyProperties.PURPOSE_SIGN or KeyProperties.PURPOSE_VERIFY
).setDigests(KeyProperties.DIGEST_SHA256)
.setSignaturePaddings(KeyProperties.SIGN_PADDING_RSA_PKCS1)
.setUserAuthenticationRequired(true)
.build()
val kpGen = KeyPairGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_RSA,
"AndroidKeyStore"
)
kpGen.initialize(spec)
kpGen.generateKeyPair()
}
Signature за ECDSA или RSA-PSS се креира кроз стандардни API: Signature.getInstance(algorithm).initSign(privateKey). Операција потписивања се извршава у TEE: апликација шаље податке, Keymaster их хардверски потписује и враћа потпис. Кључ и подаци се не мешају у заједничкој меморији.
За биометријску заштиту потребно је пре потписивања аутентификовати корисника кроз BiometricPrompt. Без успешне аутентификације, Keymaster не извршава операцију, враћајући CryptoAuthenticationException.
import java.security.KeyStore
import java.security.Signature
import androidx.biometric.BiometricPrompt
fun signWithBiometric(alias: String) {
val ks = KeyStore.getInstance("AndroidKeyStore")
ks.load(null)
val entry = ks.getEntry(alias, null) as KeyStore.PrivateKeyEntry
val signature = Signature.getInstance("SHA256withRSA")
signature.initSign(entry.privateKey)
// BiometricPrompt sa CryptoObject(signature) zahteva FaceID/PIN
}
Android подржава два начина чувања кључева: софтверски (на уређајима без TEE) и хардверски (на уређајима са TEE или Secure Element). Начин зависи од могућности SoC-а и верзије Android-а.
На уређајима без Trusted Execution Environment-а (пре Android 4.3 или буџетни SoC) кључеви се чувају у шифрованом облику коришћењем главног кључа изведеног из лозинке заклучаног екрана. Овај начин је мање сигуран — кључеви су доступни у меморији процеса приликом извршавања криптографских операција.
Ниво заштите се заснива на шифровању датотеке KeyStore-а помоћу AES-256-GCM. Кључ за шифровање се генерише на основу лозинке или PIN-кода корисника кроз Scrypt (PBKDF2 са великим бројем итерација).
На савременим уређајима користи се Keymaster 4.x у TEE (ARM TrustZone). Кључеви се генеришу, чувају и користе искључиво унутар TrustZone-а. Чак ни и језгро Linux-а нема приступ приватним кључевима — само Keymaster HAL може да обавља операције.
Secure Element (нпр. eSE у Samsung Knox-у или StrongBox у Google Pixel 3+) — је посебан чип са сопственим процесором и меморијом. Сертификован према Common Criteria EAL 4+ и пружа максимални ниво заштите, укључујући заштиту од физичког отварања.
| Тип | Место чувања | Ниво заштите | Доступно од API |
|---|---|---|---|
| Software | Датотека /data/misc/keystore | Средњи (AES-256) | API 1+ |
| Keymaster 3 | TEE (TrustZone) | Висок | API 23+ |
| Keymaster 4 | TEE + Secure I/O | Врло висок | API 28+ |
| StrongBox | Хардверски Secure Element | Максималан | API 28+, опционално |
Android Keystore је интегрисан у Java Cryptography Architecture (JCA). За приступ провајдеру користи се стандардни KeyStore.getInstance("AndroidKeyStore"). API је доступан од API 18.
Метода KeyStore.load(null) учитава контејнер KeyStore текуће апликације. Лозинка није потребна — Android користи контекст апликације и њен UID за разграничење приступа. Свака апликација види само своје уносе, осим ако се користи заједнички UID.
Методе setEntry и getEntry раде са KeyStore.PrivateKeyEntry, SecretKeyEntry или TrustedCertificateEntry. Параметар ProtectionParameter је увијек null за Android Keystore (заштита је имплементирана на нивоу система).
import java.security.KeyStore
import java.security.cert.Certificate
import android.security.keystore.KeyProtection
fun storeSecretKey(alias: String, key: SecretKey) {
val ks = KeyStore.getInstance("AndroidKeyStore")
ks.load(null)
val prot = KeyProtection.Builder(
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
).setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.setUserAuthenticationRequired(true)
.build()
ks.setEntry(alias, KeyStore.SecretKeyEntry(key), prot)
}
Помоћу KeyCharacteristics-а може се утврдити у којем окружењу је похрањен кључ: софтверском KeyStore-у, TEE или StrongBox-у. Метода getKeyCharacteristics() враћа скуп заставица: FLAG_HARDWARE (keymaster), FLAG_SECURE_ELEMENT (StrongBox), FLAG_TRUSTED_USER_PRESENCE_REQUIRED (биометрија).
Android Keystore подржава широк скуп криптографских алгоритама, подељен у три категорије: асиметрични, симетрични и MAC. Подршка за одређене алгоритме зависи од верзије Keymaster HAL-а.
RSA (1024–4096 бита) — за потпис (PKCS1, PSS) и шифровање (OAEP, PKCS1). EC (P-224, P-256, P-384, P-521) — за ECDSA потпис и ECDH размену. AES (128, 256 бита) — за симетрично шифровање у CBC, CTR, GCM начинима. HMAC (SHA1, SHA256, SHA512) — за аутентификацију порука.
За сваки кључ поставља се setPurposes, који ограничава могуће операције. RSA кључ са PURPOSE_SIGN не може да се користи за шифровање, чак и ако нападач има приступ API-ју. Ово је принуђавање коришћења кључа на хардверском нивоу.
Keymaster укључује бројач неуспешних покушаја биометријске аутентификације. Након одређеног броја неуспешних покушаја (подешиво кроз setInvalidatedByBiometricEnrollment) кључ постаје недоступан и захтева брисање/регенерацију. При брисању свих биометријских шаблона, сви кључеви са userAuthenticationRequired=true аутоматски постају неважећи.
Такође, подржано је Key Attestation (Android 8.1+): на захтев апликације, Keymaster потписује цертификат са информацијама о карактеристикама кључа (хардверски/софтверски, алгоритам, purges). Сервер може да провери овај цертификат да би потврдио да је кључ креиран у поузданом окружењу.
Често постављана питања
Java KeyStore чува кључеве у датотеци заштићеној лозинком (JKS, BKS). Android Keystore користи хардверску изолацију TEE или Secure Element. Java KeyStore је рањив на root приступ, Android Keystore — није, јер приватни кључеви никада не напуштају заштићено окружење.
Да, путем KeyStore.setEntry са KeyProtection. Међутим, увезени кључ неће имати хардверску заштиту — биће складиштен у софтверском Keystore-у, шифрован главним кључем. За максималну безбедност, увијек генеришите кључеве унутар Keystore-а.
Користите KeyChain.isBoundKeyAlgorithm или проверите KeyCharacteristics након генерације кључа. Присуство FLAG_HARDWARE у карактеристикама значи да је кључ креиран у TEE. Такође можете проверити android.security.keystore.isHardwareBacked().
При брисању апликације, Android уклања све њене кључеве из Keystore-а. Подаци се неопозиво губе. При поновној инсталацији, апликација мора да генерише нове кључеве. Бекап кључева путем TEE је немогућ из архитектурних разлога.
На закључаном уређају, Keymaster не обавља никакве операције. Кључеви са userAuthenticationRequired=true захтевају биометријску потврду сваки пут. Чак и са root приступом, нападач не може директно позвати Keymaster — само кроз Android Keystore service.
Резиме
Развићемо мобилну апликацију под кључ
IT Sectr креира iOS и Android апликације за стартапе и предузећа од 2017. године. Саветоваћемо вас и предложити најбоље решење.
Прочитајте такође