Android’de Encryption (Veri Şifreleme)

Verilerimizi korumak, güvenlik altına almak her daim önemli bir konu olmuştur. Uygulamanızın güvenilirliği, kullanıcı verilerini nasıl yönettiğinize bağlıdır. Kullanıcı verilerinizi bilgisayar korsanlarından korumak için veri şifrelemeyi bilmek, “güvenli yazılım oluşturma” başlığı altındaki önemli konulardan biridir. 

Bu makalemde ise Android uygulamalarınızın güvenliğini sağlamak için,  sırasıyla uygulama erişimlerini sınırlama, veri şifreleme ve KeyStore API kullanımını nasıl yapabileceğimizi anlatacağım.  

İlk öncelikle makalede açıklayacağım, örnek uygulama projesini indirip, projenin yapısını tanımak için bir dakikanızı ayırın. Nasıl bir proje oluşturacağımızı görmek için, uygulamayı çalıştırmanızı öneririm. KODLARI İNDİR linkinden indirdiğimiz zip dosyası içindeki petmed-starter projesini çalıştırıp, anlatacağım konuları uygulayabilirsiniz.

Uygulamayı açtığınızda, basit bir kayıt ekranı göreceksiniz. Kaydol sayfasında, bir şifre belirledikten sonra, sonraki uygulama açılışlarında bu şifre girmeniz istenecektir. Bu adımdan sonra, evcil hayvanların bir listelendiğini göreceksiniz. Evcil hayvanın tıbbi bilgilerini göstermek için, listedeki bir hayvanı seçmelisiniz.  

Güvenliğin Temel Adımları

Uygulamalarınızı şifrelemeye ve önemli verileri güvenceye almak için, önce dünyanın geri kalanına veri sızmasını önlemeniz gerekir. Android’e gelince, bu genellikle kullanıcı tabanlı verilerinizin başka herhangi bir uygulama tarafından okunmasını önleme ve uygulamaların yüklendiği konumu sınırlama anlamına gelir. Bunu önce yapalım, böylece özel bilgileri şifrelemeye başlayabilirsiniz.

İzinleri Kullanma

Uygulamanızı oluşturmaya ilk başladığınızda, gerçekte ne kadar kullanıcı verisi tutmanız gerektiğini düşünmek önemlidir.

Android 6.0’dan beri, kaydettiğiniz dosyalar ve SharedPreferences MODE_PRIVATE sabiti ile ayarlanır. Bu, yalnızca uygulamanızın, kendi içindeki verilerine erişebileceği anlamına gelir.İlk öncelikle, projenin güvenli bir şekilde kurulduğundan emin olmamız gerekir.

MainActivity.kt dosyasını açın. MODE_WORLD_READABLE ve MODE_WORLD_WRITABLE için iki itiraz uyarısı olduğunu fark edeceksiniz. Bunlar, önceki Android sürümlerinde dosyalarınıza herkese açık erişim sağlar. MODE_WORLD_WRITABLE’ı ayarlayan satırı bulun ve aşağıdakiler ile değiştirin:

val preferences = getSharedPreferences("MyPrefs", Context.MODE_PRIVATE)

Ardından, MODE_WORD_READABLE’ı ayarlayan satırı bulun ve bununla değiştirin:

val editor = getSharedPreferences("MyPrefs", Context.MODE_PRIVATE).edit()

Harika, tercihlerinizi biraz daha güvenli hale getirdiniz! Ek olarak, uygulamayı şimdi oluşturup çalıştırırsanız, Android 7+ sürümlerinin güvenlik ihlali nedeniyle önceden karşılaştığınız çöküşü almamalısınız. Artık uygulama yükleme dizininiz için güvenli bir konum belirlemelisiniz.

Yükleme Dizinlerini Sınırlama

Son birkaç yılda Android’in karşılaştığı en büyük sorunlardan biri, birçok uygulamayı yüklemek için yeterli belleğe sahip olmaması. Bu, çoğunlukla cihazların daha düşük depolama kapasitesinden kaynaklanıyordu, ancak teknoloji geliştiğinden ve telefonlar biraz daha ucuza geldiğinden, çoğu cihaz artık birçok uygulama için bol miktarda depolama alanı sağlıyor. Ancak, yetersiz depolama alanını azaltmak için Android, uygulamaları harici depolama birimine yüklemenizi sağlar. Böyle bir yaklaşım sergiledikleri için yıllar içinde, birçok güvenlik kaygısı gündeme geldi. Harici SD kartlara uygulama yüklemek, depolama alanını korumanın harika bir yoludur, ancak aynı zamanda bir güvenlik açığıdır. Çünkü SD karta erişimi olan herhangi biri de uygulama verilerine erişebilir. Bu nedenle uygulamanızı dahili depolama alanı ile sınırlandırmanızı tavsiye etmekteyim

Bunu yapmak için, AndroidManifest.xml dosyasını açın.  installLocation = “auto” yazan satırı bulun ve aşağıdaki kod ile değiştirin:

android:installLocation="internalOnly"

Şimdi, yükleme konumu cihazla sınırlıdır. Ancak uygulamanızı ve verilerinizi yine de yedekleyebilirsiniz. Bu, kullanıcıların adb yedeğini kullanarak uygulamanın özel veri klasörünün içeriğine erişebilecekleri anlamına gelir. Yedeklemelere izin vermemek için, AndroidManifest.xml dosyasında  allowBackup = “true” kodunu bulun ve değerini “false” yapın.

Bu işlemleri yaparak uygulamınızı belli bir seviyede güvenlikli hale getirdiniz. Daha güvenilir bir uygulama için, potansiyel saldırganların bulamayacağı bir bilgi ile verileri şifrelemek gerekir.

Kullanıcı Verilerinin Parola ile Güvenliğinin Sağlanması

Verileri, Gelişmiş Şifreleme Standardı (AES) ile şifrelememiz gerekir. AES, verilerinizi bir anahtarla şifrelemek için substitution–permutation ağı kullanır. Bu yaklaşımı kullanarak, bir tablodaki baytları diğerinden gelen baytlarla değiştirir. Böylece veri izinleri oluşturur. AES’i kullanmaya başlamak için önce şifreleme anahtarını oluşturmanız gerekir.

Şifreleme Anahtarı Oluşturma

AES şifreleme kullanabilmek için bir anahtar kullanır. Aynı anahtar, verilerin şifresini çözmek için de kullanılır. Buna symmetric encryption denir. Anahtar farklı uzunluklarda olabilir, ancak 256 bit standarttır. Şifreleme için kullanıcının şifresini doğrudan kullanmak tehlikelidir.

Password-Based Key Derivation Function (PBKDF2) fonksiyonunu kullanırsanız, bu anlamda büyük yardımı olur. PBKDF2 fonksiyonu, bir parola alır ve birçok defa rastgele verilerle karıştırarak, bir anahtar oluşturur. Rastgele verilere “salt” denir.Bir başkası aynı şifreyi kullansa bile, güçlü ve benzersiz bir anahtar oluşturduğunda güvenlik bir şifreleme olur.

Her anahtar benzersiz olduğundan, bir saldırgan anahtarı çevrimiçi çaldığında ve yayınladığında, aynı şifreyi kullanan tüm kullanıcıları göstermez.

Salt üreterek başlayalım. Encryption.kt dosyasını açın ve aşağıdaki kodu encrypt metoduna ekleyin.

val random = SecureRandom()
val salt = ByteArray(256)
random.nextBytes(salt)

SecureRandom sınıfı, çıktının tahmin edilmesinin zor hale getirmesini sağlamak için güçlü bir şifreli bir sayı oluşturur.

Şimdi, kullanıcının şifresi ve salt ile bir anahtar oluşturacaksınız. Yeni eklediğiniz kodun altına aşağıdaki kodu ekleyin.

val pbKeySpec = PBEKeySpec(password, salt, 1324, 256)
val secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1") 
val keyBytes = secretKeyFactory.generateSecret(pbKeySpec).encoded 
val keySpec = SecretKeySpec(keyBytes, "AES") 

Initialization Vector Ekleme

Verileri şifrelemek için neredeyse hazırsınız, ancak yapılacak bir şey daha var.

AES farklı modlarda çalışır. Standart moda, cipher block chaining  (CBC) denir. CBC, verilerinizi her seferinde bir yığın şeklinde şifreler.

CBC güvenlidir. Çünkü her blok, bir öncekine bağlı olarak zincirleme mantığıyla şifreleme yapar. Önceki bloklara olan bu bağımlılık şifrelemeyi güçlü kılar. Başka bir mesajla aynı şekilde başlayan bir mesajı şifrelerseniz ilk şifreli blok aynı olacaktır! Bu bir saldırgan için bir ipucu sağlar.  Bunu düzeltmek için bir Initialization Vector (IV) kullanmalısınız.

Şimdi eklediğiniz kodun hemen arkasına aşağıdaki kodu ekleyerek bir IV oluşturun.

val ivRandom = SecureRandom() 
val iv = ByteArray(16)
ivRandom.nextBytes(iv)
val ivSpec = IvParameterSpec(iv)

Verileri Şifreleme

Artık gerekli tüm parçalara sahip olduğunuza göre, şifrelemeyi gerçekleştirmek için aşağıdaki kodu ekleyiniz.

val cipher = Cipher.getInstance("AES/CBC/PKCS7Padding") // 1
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec)
val encrypted = cipher.doFinal(dataToEncrypt) // 2

doFinal gerçek şifrelemeyi yapar.

  map["salt"] = salt
map["iv"] = iv
map["encrypted"] = encrypted

Şifrelenmiş verileri, salt ve initialization vector’u bir HashMap sınıfının içine yerleştirdik. Bu  şekilde HashMap’ e ekleme sebebim, makale devamında bu şifreyi nasıl çözebileceğimizi göstermek istiyorum.

Verilerin Şifresini Çözmek

Elimizdeki şifreyi çözmek için, init metodundaki Cipher modunu ENCRYPT_MODE’den DECRYPT_MODE’ye değiştirmelisiniz. Aşağıdaki kodları Encryption.kt dosyasındaki decrypt metoduna ekleyin.

// 1
val salt = map["salt"]
val iv = map["iv"]
val encrypted = map["encrypted"]
// 2
//regenerate key from password
val pbKeySpec = PBEKeySpec(password, salt, 1324, 256)
val secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1")
val keyBytes = secretKeyFactory.generateSecret(pbKeySpec).encoded
val keySpec = SecretKeySpec(keyBytes, "AES")
// 3
//Decrypt
val cipher = Cipher.getInstance("AES/CBC/PKCS7Padding")
val ivSpec = IvParameterSpec(iv)
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec)
decrypted = cipher.doFinal(encrypted)
  1. Şifresini çözmek için gerekli şifrelenmiş verileri, salt ve IV‘ü içeren HashMap’i kullandık.
  2. Bu bilgilerin ve kullanıcının şifresinin verildiği anahtarı yeniden üretti.
  3. Verilerin şifresini çözdü ve ByteArray olarak döndürdü.

Önemli not: Normalde key bilgisi hiç bir şekilde saklanmamalıdır. Ben burada sadece şifreyi nasıl çözümleyeceğinize anlık göstermek için HashMap’ e key’i ekledim.  Proje bitiminde key bilgilerini herhangi bir yerde saklamamalısınız.

Şifreli Verileri Kaydetme

Şifreleme işlemi tamamlandıktan sonra, bu verileri kaydetmeniz gerekir. Uygulama zaten depoya veri okuyor ve yazıyor. Şifreli verilerle çalışmasını sağlamak için bu yöntemleri güncelleyeceksiniz.

MainActivity.kt dosyasında, createDataSource metodunun içindeki her şeyi şununla değiştirin:

val inputStream = applicationContext.assets.open(filename)
val bytes = inputStream.readBytes()
inputStream.close()
val password = CharArray(login_password.length())
login_password.text.getChars(0, login_password.length(), password, 0)
val map = Encryption().encrypt(bytes, password)
ObjectOutputStream(FileOutputStream(outFile)).use {
it -> it.writeObject(map)
}

Güncellenen kodda, veri dosyasını bir giriş akışı olarak açtınız ve verileri şifreleme yöntemine beslediniz. ObjectMoutputStream sınıfını kullanarak HashMap’i seri hale getirdiniz ve ardından depoya kaydettiniz.

Uygulamayı oluşturun ve çalıştırın. Evcil hayvanların şu anda listede eksik olduğuna dikkat edin.

Çünkü kaydedilen veriler şifreli. Şifreli içeriği okuyabilmek için kodu güncellemeniz gerekir. PetViewModel.kt dosyasının loadPets metodunda /* ve */ yorum taglarını kaldırın ve aşağıdaki kodu ekleyin.

decrypted = Encryption().decrypt(
hashMapOf("iv" to iv, "salt" to salt, "encrypted" to encrypted), password)

Şifrelenmiş verileri, IV ve salt kullanarak şifre çözme yöntemini çağırdınız. Şimdi val inputStream = file.inputStream () yazan satırı şununla değiştirin:

val inputStream = ByteArrayInputStream(decrypted)

Uygulamayı şimdi oluşturup çalıştırırsanız, seçtiğiniz hayvanın  bilgilerinin geldiğini göreceksiniz. Artık verileri güvenle hale getirdik.

Güvenlikli SharedPreferences Kullanımı

Hassas bilgilerin SharedPreferences’ta saklanması güvensiz olabilir. Çünkü Context.MODE_PRIVATE bayrağıyla bile bilgiyi uygulamanızın içinden sızdırabilirsiniz. Bunu biraz daha çözmek adına aşağıdaki kodlamayı deneyebilirsiniz.

MainActivity.kt dosyasını açın ve saveLastLoggedInTime metodu aşağıdaki kodla değiştirin.

//Get password
val password = CharArray(login_password.length())
login_password.text.getChars(0, login_password.length(), password, 0)
//Base64 the data
val currentDateTimeString = DateFormat.getDateTimeInstance().format(Date())
// 1
val map =
Encryption().encrypt(currentDateTimeString.toByteArray(Charsets.UTF_8), password)
// 2
val valueBase64String = Base64.encodeToString(map["encrypted"], Base64.NO_WRAP)
val saltBase64String = Base64.encodeToString(map["salt"], Base64.NO_WRAP)
val ivBase64String = Base64.encodeToString(map["iv"], Base64.NO_WRAP)
//Save to shared prefs
val editor = getSharedPreferences("MyPrefs", Context.MODE_PRIVATE).edit()
// 3
editor.putString("l", valueBase64String)
editor.putString("lsalt", saltBase64String)
editor.putString("liv", ivBase64String)
editor.apply()

Kod açıkalaması:

  1. Stirng’i UTF-8 kodlayan ve şifrelenen bir ByteArray biçimine dönüştürdük.
  2. encodeToString metoduyla, ham verileri bir String’e dönüştürdük. SharedPreferences doğrudan bir ByteArray saklayamaz, ancak String ile çalışabilir. Base64, ham verileri bir String’e dönüştüren bir standarttır.
  3. String veriler, SharedPreferences öğesine kaydedildi. İsteğe bağlı olarak hem preference key’i hem de değeri şifreleyebilirsiniz. Bu şekilde, bir saldırgan, anahtara bakarak değerin ne olduğunu çözemez.

Şimdi şifreli baytları geri almak için lastLoggedIn metodunu değiştirin:

//Get password
val password = CharArray(login_password.length())
login_password.text.getChars(0, login_password.length(), password, 0)
//Retrieve shared prefs data
// 1
val preferences = getSharedPreferences("MyPrefs", Context.MODE_PRIVATE)
val base64Encrypted = preferences.getString("l", "")
val base64Salt = preferences.getString("lsalt", "")
val base64Iv = preferences.getString("liv", "")
//Base64 decode
// 2
val encrypted = Base64.decode(base64Encrypted, Base64.NO_WRAP)
val iv = Base64.decode(base64Iv, Base64.NO_WRAP)
val salt = Base64.decode(base64Salt, Base64.NO_WRAP)
//Decrypt
// 3
val decrypted = Encryption().decrypt(
hashMapOf("iv" to iv, "salt" to salt, "encrypted" to encrypted), password)
var lastLoggedIn: String? = null
decrypted?.let {
lastLoggedIn = String(it, Charsets.UTF_8)
}
return lastLoggedIn

Kod açıklama:

  1. Şifrelenmiş veriler, IV ve salt için String gösterimleri alındı.
  2. Onları tekrar ham baytlara dönüştürmek için String üzerinde bir Base64 kodu çözüldü. Yani decode yapıldı.
  3. Bir HashMap’teki bu verileri şifre çözme yöntemine aktardı.

Artık depolama alanınızı güvenli bir şekilde kurduğunuza göre, Settings ▸ Apps ▸ PetMed 2 ▸ Storage ▸ Clear data adımlarını yaparak cache temizleyip,  uygulamınızı yeniden başlatın.

Sunucudan Bir Anahtar Kullanma

Güzel bir uygulamayı tamamladık. Esasında giriş ekranına ek olarak bir şifre ekranı gösterilmesi iyi bir kullanıcı deneyimi olmayabilir. Bunun gibi gereksinimler için bazı seçenekleriniz vardır.

İlki, anahtarı türetmek için bir giriş şifresinden yararlanmak. Sunucunun bunun yerine bu anahtarı üretmesini de sağlayabilirsiniz. Anahtar benzersiz olacaktır ve kullanıcı kimlik bilgilerini doğruladıktan sonra güvenli bir şekilde iletilecektir.

Sunucu yoluna gidiyorsanız, sunucu anahtarı oluşturduğundan, cihazda depolanan verilerin şifresini çözme kapasitesine sahip olduğunu bilmek önemlidir. Çünkü birisinin anahtarı sızdırması potansiyeli var.

KeyStore Kullanımı

Android M, KeyStore API’sini kullanarak bir AES anahtarıyla çalışma özelliğini tanıttı. Bunun bazı ek faydaları vardır. KeyStore, bir anahtar üzerinde gizli içeriğini açıklamadan çalıştırmanıza olanak tanır. Özel alana değil, yalnızca nesneye uygulama alanından erişilebilir.

Yeni Bir Rastgele Anahtar Oluşturma

Encryption.kt dosyasına, rastgele bir anahtar oluşturmak için keystoreTest metoduna aşağıdaki kodu ekleyin.

val keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore") // 1
val keyGenParameterSpec = KeyGenParameterSpec.Builder("MyKeyAlias",
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
//.setUserAuthenticationRequired(true) // 2 requires lock screen, invalidated if lock screen is disabled
//.setUserAuthenticationValidityDurationSeconds(120) // 3 only available x seconds from password authentication. -1 requires finger print - every time
.setRandomizedEncryptionRequired(true) // 4 different ciphertext for same plaintext on each call
.build()
keyGenerator.init(keyGenParameterSpec)
keyGenerator.generateKey()

Kodun açıklaması:

  1. Bir KeyGenerator örneği oluşturdunuz ve “AndroidKeyStore” sağlayıcısına ayarladınız.
  2. İsteğe bağlı olarak, ayarlanacak bir kilit ekranı gerektiren .setUserAuthenticationRequired (true) eklediniz.
  3. İsteğe bağlı olarak .setUserAuthenticationValidityDurationSeconds (120) eklediniz, böylece anahtar cihaz doğrulamasından 120 saniye sonra kullanılabilir.
  4. .SetRandomizedEncryptionRequired (true) yaptınız. Bu, KeyStore’a her seferinde yeni bir IV kullanmasını söyler. Daha önce öğrendiğiniz gibi, bu, aynı verileri ikinci kez şifrelerseniz, şifreli çıktının aynı olmayacağı anlamına gelir. Saldırganların aynı girdilerde beslemeye dayanan şifreli veriler hakkında ipuçları edinmelerini önler.

KeyStore özellikleri hakkında  bilmeniz gereken  birkaç durum daha bulunmaktadır.

  1. .SetUserAuthenticationValidityDurationSeconds () için, her tuşa erişmek istediğinizde parmak izi doğrulaması gerektirmek için -1 değerini geçebilirsiniz.
  2. Ekran kilidi gereksinimlerini etkinleştirmek; kullanıcı kilit ekranı pinini veya şifresini değiştirir değiştirmez tuşları iptal edebilirsiniz.
  3. Bir anahtarı, şifrelenmiş verilerle aynı yerde saklamak, anahtarı paspasın altına koymak gibidir. KeyStore, anahtarı katı izinlerle ve çekirdek düzey koduyla korumaya çalışır. Bazı cihazlarda, anahtarlar donanım desteklidir.
  4. .SetUserAuthenticationValidWhileOnBody (boolean remainValid) kullanabilirsiniz. Bu, cihazın artık kişi üzerinde olmadığını tespit ettiğinde anahtarı kullanılamaz duruma getirir.

Verileri Şifreleme

Şimdi, KeyStore’da depolanan o anahtarı kullanacaksınız. Encryption.kt dosyasına, keystoreEncrypt metodunun içine aşağıdaki  kodu ekleyiniz.

// 1
//Get the key
val keyStore = KeyStore.getInstance("AndroidKeyStore")
keyStore.load(null)
val secretKeyEntry =
keyStore.getEntry("MyKeyAlias", null) as KeyStore.SecretKeyEntry
val secretKey = secretKeyEntry.secretKey
// 2
//Encrypt data
val cipher = Cipher.getInstance("AES/GCM/NoPadding")
cipher.init(Cipher.ENCRYPT_MODE, secretKey)
val ivBytes = cipher.iv
val encryptedBytes = cipher.doFinal(dataToEncrypt)
// 3
map["iv"] = ivBytes
map["encrypted"] = encryptedBytes

Kod açıklama:

  1. Bu kez, anahtarı KeyStore’dan aldık.
  2. Verileri, SecretKey verilen Cipher nesnesini kullanarak şifreledik.
  3. Şifreli verileri içeren ve verilerin şifresini çözmek için gerekli IV değerini HashMap  sınıfına atadık.

Bayt Dizisindeki Şifrenin Çözülmesi

keystoreDecrypt metodunun içine aşağıdaki  kodu ekleyiniz.

// 1
//Get the key
val keyStore = KeyStore.getInstance("AndroidKeyStore")
keyStore.load(null)
val secretKeyEntry =
keyStore.getEntry("MyKeyAlias", null) as KeyStore.SecretKeyEntry
val secretKey = secretKeyEntry.secretKey
// 2
//Extract info from map
val encryptedBytes = map["encrypted"]
val ivBytes = map["iv"]
// 3
//Decrypt data
val cipher = Cipher.getInstance("AES/GCM/NoPadding")
val spec = GCMParameterSpec(128, ivBytes)
cipher.init(Cipher.DECRYPT_MODE, secretKey, spec)
decrypted = cipher.doFinal(encryptedBytes)
Bu kodda siz:
  1. Anahtarı tekrar KeyStore’dan almış oldunuz.
  2. map nesnesindeki encrypted ve iv değerlerini değişkene atadınız.
  3. DECRYPT_MODE sabitini kullanarak Cipher nesnesini başlattı ve verilerin bir ByteArray öğesine şifresini çözdü.

Örneği Test Etme

KeyStore API’sını kullanarak verileri şifrelemek ve şifresini çözmek için yollar yarattığınıza göre, bunları test edelim. KeystoreTest metodunun sonuna aşağıdaki kodları ekleyin:

// 1
val map = keystoreEncrypt("My very sensitive string!".toByteArray(Charsets.UTF_8))
// 2
val decryptedBytes = keystoreDecrypt(map)
decryptedBytes?.let {
val decryptedString = String(it, Charsets.UTF_8)
Log.e("MyApp", "The decrypted string is: $decryptedString")
}

Güncellenen kodda:

  1. Bir test String’i oluşturuldu ve şifreledik.
  2. Her şeyin çalıştığını test etmek için şifreli çıktıdaki şifre çözme metodunu çağırdı.

MainActivity.kt dosyasının onCreate metodunda, // Encryption (). keystoreTest () kodunu yorum taglerini kaldırın. Çalıştığını kontrol etmek için uygulamayı çalıştırın. Artık şifresi çözülmüş String’i görebilirsiniz.

Anlatıklarımın uygulanmış halini  KODLARI İNDİR resmine tıklayarak zip dosyası içindeki petmed-final  projesinden ulaşabilirsiniz.xxx

download

Kaynaklar

  1. https://www.raywenderlich.com/778533-encryption-tutorial-for-android-getting-started
  2. https://developer.android.com/guide/topics/security/cryptography
Kategori Genel
Etiketler