Sistemde kullanılan veriler, ticari değeri yüksek ya da mahrem olabilir. Veri hırsızlıklarını önlemek için güvenliği yüksek sistemler oluşturulmalıdır.
Bu makale OWASP Foundation Top 10 Mobil güvenlik tehdidinden ikincisi olan “Insecure Data Storage” konusunu Android yapıları üzerinden açıklayacaktır.
Dilerseniz bir önceki bölümdeki “Improper Platform Usage” Güvenlik Zafiyetleri adlı makalemi inceleyebilirsiniz.
“Insecure Data Storage” Anlamı Nedir?
Insecure Data Storage konusu, mobil uygulamanızdaki verilerin cihazda tutulduğunda oluşabilecek güvenlik zafiyetlerinden bahsetmektedir.
En iyi güvenlik yöntemi herhangi bir türde hassas veriyi cihazda saklamaktan kaçınmaktır. Hassas veriler, bir kullanıcının kişisel bilgileri, API key vb. dir. Fakat bazı zamanlarda bu verileri cihazda tutmamız gerekebilir. Bu durumda kullanacağımız depolama yapılarını güvenlikli hale getirmeliyiz.
Android uygulamalarda çoğunlukla kullanılan veri depolama yöntemleri için güvenlik tehditlerine çözümler üreteceğim. Makale de bahsedeceğim veri depolama yöntemleri:
- Room veritabanı
- SharedPreferences
- Jetpack’s DataStore
Room Veritabanı
Room, SQLite veritabanı üzerinde bir soyutlama katmanıdır. Geliştiricilerin kolayca tablolar oluşturmasına, sorgular yazmasına ve veritabanları için standart CRUD işlemlerini otomatik olarak yapılmasında kolaylık sağlar.
Bir üretim uygulamasında kullanıcı kimliği, adı ve soyadı gibi kullanıcı verilerini içeren insecure-database adında çok basit bir veritabanı oluşturduğumuzu varsayalım. Komut satırında sqlite3 kullanarak veritabanını inceleyebilir, tabloları listeleyebilir ve içindekileri içeren tablo verilerini gösterebiliriz. Daha detaylı dokümanı sqlite.org bulunmaktadır.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
$ sqlite3 insecure-database sqlite> .tables User android_metadata room_master_table sqlite> .schema User CREATE TABLE `User` (`uid` INTEGER NOT NULL, `first_name` TEXT, `last_name` TEXT, PRIMARY KEY(`uid`)); sqlite> SELECT * FROM User LIMIT 5; 1653992407422|Tuğba|Üstündağ 1653992407614|Kerim|Fırat 1653992407658|Mehmet|Yaşar 1653992407685|Ali|Solak 1653992407686|Nur|Yaşar |
Herhangi bir şifreleme yapısı kullanılmıyorsa veriler veritabanında düz metin olarak içerir. Bu durumda hayli tehlikelidir.
Room Veritabanı Şifreleme
Android veritabanı dosyalarını şifrelemek için SQLCipher adlı şifreleme yapısı bulunmaktadır.
Android Studio Ide ile oluşturduğum projemin app dizinin altındaki build.gradle dosyasını açıyoruz. Dependencies kod bloklarının arasına aşağıdaki kodları yerleştirerek SQLCipher kütüphanesini yüklüyoruz.
1 |
implementation 'net.zetetic:android-database-sqlcipher:4.5.1@aar' |
Room örneğinizi oluştururken SQLCipher kullanmak için, şifreleyeceğimiz veriyi SupportFactory ve SupportSQLiteOpenHelper.Factory sınıflarını birlikte kullanarak uygulayalım.
1 2 3 4 5 6 7 8 9 |
val passPhrase: ByteArray = "password".encodeToByteArray() val sqlCipherSupportFactory: SupportSQLiteOpenHelper.Factory = SupportFactory(passPhrase) val database = Room.databaseBuilder( applicationContext, YourRoomDatabase::class.java, "secure-database") .openHelperFactory(sqlCipherSupportFactory) .build() |
Veritabanı dosyasının AES şifrelemesi/şifresinin çözülmesi için benzersiz bir anahtar oluşturmak üzere sağlanan ‘ passphrase ‘ değişkenini kullanır. Bu durum SQLCipher dokümantasyonunda daha ayrıntılı açıklanmıştır.
Şifreleme işlemenin sonucunu doğrulamak için sqlcipher komutu ile “secure database” açabiliriz. PRAGMA key’i kodunuzda sağlanan aynı parola ile kullanarak, veritabanının şifresini çözmek ve içeriği bir kez daha düz metin olarak okumak mümkündür.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
$ sqlcipher secure-database sqlite> .tables Error: file is not a database sqlite> PRAGMA key = 'password'; ok sqlite> .tables User room_master_table sqlite> SELECT * FROM User LIMIT 5; 1653992407422|Tuğba|Üstündağ 1653992407614|Kerim|Fırat 1653992407658|Mehmet|Yaşar 1653992407685|Ali|Solak 1653992407686|Nur|Yaşar |
Verilerimizi minimum geliştirme çabasıyla güvenli bir şekilde sakladığımızı görmüş olduk.
“Android’de SQLite Veritabanı Şifreleme” adlı makalemde SQLCipher ile SQLite şifrelemenin örneğini inceleyebilirsiniz.
Shared Preferences
SharedPreferences API, Android 1 sürümünden bu yana kullanılan local veri depolama yapılarından biridir. Geliştiricilerin verilerini key-value (KVP) şeklinde bir depolanmaktadır. Örnek kod,
1 2 3 |
val prefs = context?.getSharedPreferences("mySharedPrefFile", Context.MODE_PRIVATE) ?: return prefs.edit().putString("mySecretKey", "mySecretValue").apply() |
Bu kod, cihazın /data/data/{package name}/shared_prefs klasöründe bir XML dosyası oluşturur. Bu XML dosyasında veriler key-value (KVP) şeklinde kayıt olunur. Shell komut satırlarıyla adb kullanarak XML dosyasının içeriğini görüntüleyelim.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
$ adb shell # Paket ismi calıştıralım $ run-as com.smality.securityexam # Uygulamadaki local dosyaları listeliyoruz $ ls cache code_cache shared_prefs # shared_prefs klasorunuzu listeliyoruz $ ls shared_prefs mySharedPrefFile.xml # Xml dosyamızın içeriğini ekrana yansıtıyoruz $ cat shared_prefs/mySharedPrefFile.xml <?xml version='1.0' encoding='utf-8' standalone='yes' ?> <map> <string name="mySecretKey">mySecretValue</string> </map> |
Üstte görüldüğü üzere “mySecretKey” adlı key’in değeri düz metin olarak saklandı.
SharedPreferences’ı Şifrelemek
SharedPreferences kullandığımızda verilerin düz metin olarak depolandığı için ciddi güvenlik zafiyetine sebep olduğunu anlattım. Bunun çözümü Jetpack Security (JetSec) kütüphanesini kullanarak veriyi şifrelemektir. Örnek kod,
1 2 3 4 5 6 7 |
val masterKey = MasterKey.Builder(this) .setKeyScheme(MasterKey.KeyScheme.AES256_GCM) .build() EncryptedSharedPreferences.create(this, "myEncryptedPrefsFile", masterKey, PrefKeyEncryptionScheme.AES256_SIV, PrefValueEncryptionScheme.AES256_GCM).edit { putString("mySecretKey", "mySecretValue") } |
Dilerseniz bu konuyu daha detaylı örnekleyen “Jetpack Security API Kullanımı” adlı makalemi inceleyebilirsiniz.
Jetpack Security ile şifrelerseniz XML’de veriler şu şekilde görünecektir.
1 2 3 4 5 |
<map> <string name="ARTYCGdkOdwAqjLCjWdsepYfbO+lJzJFFrHIta8JSE0=">ASTonpk6n1buL/VN6mB/S95HNcyHzvFp5qbcpkJMjSQbqRkzO3HWe5KKcP6eTwtzIFamU3Ag</string> <string name="__androidx_security_crypto_encrypted_prefs_key_keyset__">12a90155259183605a12481ccf406838afc98126862109cc5e083185fe7259a052ccb4d1f859ac62ee1ab624f4b35df36c53a23c24547ee322aacc4526a654fd99e9997c0e6bf389b3bf2706a2e29b63d99a1e74535d68457fda16f04e706f127ca09c01622e26db72339720e814af1d6533efc8705eb8a073fa4e5afb2dbfb9446eaa27801942e0c8a8462ff6aed86738f500cdb77655294549810c2cfbe011b4c3900e3504eb3947502c7c1a4408e790e0a601123c0a30747970652e676f6f676c65617069732e636f6d2f676f6f676c652e63727970746f2e74696e6b2e4165735369764b6579100118e790e0a6012001</string> <string name="__androidx_security_crypto_encrypted_prefs_value_keyset__">128801700a3e737db7b3f8385be4aea6f74fbb844b86532055fa99032c67df4c5a5543e799dd9a621013ba716749b3decef994896914cea1d8dbf25fc13e6a7c6f19a0488dbd4a339642f4bfc4ad82fe1b27b4d4ca0a79c55e57354389f7c2e115af8c0d6f8fff0093299c2481a6cf25b5444beb614c227a03c64e1262ecc0a137a1273df34ae24d78ddd51a440899bda2a702123c0a30747970652e676f6f676c65617069732e636f6d2f676f6f676c652e63727970746f2e74696e6b2e41657347636d4b657910011899bda2a7022001</string> </map> |
DataStore
DataStore verileri eşzamansız, tutarlı ve işlemsel olarak depolamak için Kotlin coroutines ve Flow yapılarını kullanır. Örnek kod,
1 2 3 4 5 6 7 8 |
val Context.dataStore by preferencesDataStore(name = "insecure-data-store") val pref1 = stringPreferencesKey("example_pref") val pref2 = stringPreferencesKey("example_pref_2") dataStore.edit { settings -> settings[pref1] = "My 1st Pref" settings[pref2] = "My 2nd Pref" } |
adb backup komutu ile DataStore’da bulunan tüm verilere erişmek mümkündür. Backup yöntemini kullandıktan sonra verileri datastore/insecure-data-store.preferences_pb içinde bulabilirsiniz. Bu dosya ilk bakışta biraz tuhaf görünebilir. Ancak Google’ın protobuf kütüphanesini kullanarak dosyanın içeriğini inceleyebilirsiniz.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
$ hexdump -C insecure-data-store.preferences_pb 00000000 0a 1d 0a 0c 65 78 61 6d 70 6c 65 5f 70 72 65 66 |....example_pref| 00000010 12 0d 2a 0b 4d 79 20 31 73 74 20 50 72 65 66 0a |..*.My 1st Pref.| 00000020 1f 0a 0e 65 78 61 6d 70 6c 65 5f 70 72 65 66 5f |...example_pref_| 00000030 32 12 0d 2a 0b 4d 79 20 32 6e 64 20 50 72 65 66 |2..*.My 2nd Pref| 00000040 $ protoc --decode_raw < insecure-data-store.preferences_pb 1 { 1: "example_pref" 2 { 5: "My 1st Pref" } } 1 { 1: "example_pref_2" 2 { 5: "My 2nd Pref" } } |
DataStore Şifreleme
Şuanda DataStore şifreleme için resmi bir destek bulunmamaktadır. Fakat encrypted-datastore adında açık kaynak bir şifreleme kütüphanesi bulunmaktadır. Yalnız bu kütüphane bir şahış tarafından geliştirilmiştir. Güvenilir olmama ihtimalinden kaynaklı, kütüphaneyi kullanırken dikkatli olmanızı öneririm.
Kaynaklar
1- https://www.zetetic.net/sqlcipher/
2- https://proandroiddev.com/unpacking-android-security-part-2-insecure-data-storage-71f35107052a
3- https://www.tugbaustundag.com/jetpack-security-api-kullanimi/