{"id":361334,"date":"2024-05-21T01:11:34","date_gmt":"2024-05-21T01:11:34","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=361334"},"modified":"-0001-11-30T00:00:00","modified_gmt":"2024-05-21T01:11:34","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=361334","title":{"rendered":"<span>\u041e\u0431\u0437\u043e\u0440 \u0430\u043a\u0442\u0443\u0430\u043b\u044c\u043d\u044b\u0445 \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u043e\u0432 \u0448\u0438\u0444\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0432 Android<\/span>"},"content":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w780q1\/getpro\/habr\/upload_files\/35b\/f98\/af2\/35bf98af202340e8f32ede24936a8a01.jpg\" width=\"1024\" height=\"1024\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/35b\/f98\/af2\/35bf98af202340e8f32ede24936a8a01.jpg\" data-blurred=\"true\"\/><\/figure>\n<p>\u041f\u0440\u0438\u0432\u0435\u0442, \u0425\u0430\u0431\u0440! \u041c\u0435\u043d\u044f \u0437\u043e\u0432\u0443\u0442 \u0410\u0440\u0442\u0443\u0440 \u0418\u043b\u044c\u043a\u0430\u0435\u0432, \u044f\u00a0\u0440\u0430\u0431\u043e\u0442\u0430\u044e \u0432 \u0434\u0435\u043f\u0430\u0440\u0442\u0430\u043c\u0435\u043d\u0442\u0435 \u044d\u043a\u043e\u0441\u0438\u0441\u0442\u0435\u043c\u043d\u044b\u0445 \u043f\u0440\u043e\u0434\u0443\u043a\u0442\u043e\u0432, \u043c\u044b \u0440\u0430\u0437\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0435\u043c VK ID SDK \u0438 \u0432\u0441\u0435 \u0447\u0442\u043e \u0441\u0432\u044f\u0437\u0430\u043d\u043e \u0441 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0435\u0439 \u0438 \u0441\u0435\u0441\u0441\u0438\u044f\u043c\u0438, \u0432 \u0447\u0430\u0441\u0442\u043d\u043e\u0441\u0442\u0438\u00a0\u2014 \u043c\u0443\u043b\u044c\u0442\u0438\u0430\u043a\u043a\u0430\u0443\u043d\u0442.<\/p>\n<p>\u0421\u0435\u043a\u0440\u0435\u0442\u043d\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435 \u0442\u0440\u0435\u0431\u0443\u044e\u0442 \u043e\u0441\u043e\u0431\u043e\u0433\u043e \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u044f \u043f\u0440\u0438 \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0438 \u0438 \u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0435. \u0418\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u044b \u0434\u043b\u044f \u0448\u0438\u0444\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u043c\u043e\u0433\u0443\u0442 \u0432\u0430\u0440\u044c\u0438\u0440\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u043f\u043e \u0441\u0442\u0435\u043f\u0435\u043d\u0438 \u0441\u043b\u043e\u0436\u043d\u043e\u0441\u0442\u0438, \u043d\u0430\u0434\u0451\u0436\u043d\u043e\u0441\u0442\u0438 \u0438 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u0438. \u0412 \u044d\u0442\u043e\u043c \u043e\u0431\u0437\u043e\u0440\u0435 \u043c\u044b \u0440\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0438\u043c \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0442\u0430\u043a\u0438\u0445 \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u043e\u0432, \u043e\u043f\u0438\u0448\u0435\u043c \u0438\u0445 \u044d\u0444\u0444\u0435\u043a\u0442\u0438\u0432\u043d\u043e\u0441\u0442\u044c \u0438 \u0440\u0430\u0441\u0441\u043a\u0430\u0436\u0435\u043c \u043e \u0440\u0430\u0441\u043f\u0440\u043e\u0441\u0442\u0440\u0430\u043d\u0451\u043d\u043d\u044b\u0445 \u043e\u0448\u0438\u0431\u043a\u0430\u0445 \u043f\u0440\u0438 \u0438\u0445 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0438. \u0421\u0442\u0430\u0442\u044c\u044f \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u0430 \u043f\u043e \u043c\u043e\u0442\u0438\u0432\u0430\u043c <a href=\"https:\/\/habr.com\/ru\/companies\/vk\/articles\/769636\/\"><u>\u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u043c\u0443\u043b\u044c\u0442\u0438\u0430\u043a\u043a\u0430\u0443\u043d\u0442\u0430<\/u><\/a>, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043f\u043e\u0434\u0432\u0451\u043b \u043d\u0430\u0441 \u043a \u0438\u0441\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u043d\u0438\u044e \u043e\u043f\u0442\u0438\u043c\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u0438 \u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e\u0433\u043e \u0441\u043f\u043e\u0441\u043e\u0431\u0430 \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u0441\u0435\u0441\u0441\u0438\u0439.<\/p>\n<h3>\u041f\u0435\u0440\u0432\u044b\u0439 \u0441\u043f\u043e\u0441\u043e\u0431: \u0441\u0430\u043c\u043e\u0441\u0442\u043e\u044f\u0442\u0435\u043b\u044c\u043d\u043e\u0435 \u0448\u0438\u0444\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u043f\u0440\u0438\u043c\u0438\u0442\u0438\u0432\u0430\u043c\u0438 \u0438\u0437 Android SDK<\/h3>\n<p>\u0415\u0441\u043b\u0438 \u0432\u044b \u0431\u0443\u0434\u0435\u0442\u0435 \u043f\u0438\u0441\u0430\u0442\u044c \u0441\u0430\u043c\u0438, \u0441\u043a\u043e\u0440\u0435\u0435 \u0432\u0441\u0435\u0433\u043e \u0441\u0442\u043e\u043b\u043a\u043d\u0451\u0442\u0435\u0441\u044c \u0441 \u043c\u0430\u0441\u0441\u043e\u0439 \u043f\u0440\u043e\u0431\u043b\u0435\u043c. \u041d\u0430\u0438\u0431\u043e\u043b\u0435\u0435 \u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u0430\u044f \u0441\u0445\u0435\u043c\u0430: \u0445\u0440\u0430\u043d\u0438\u0442\u044c \u043c\u0430\u0441\u0442\u0435\u0440-\u043a\u043b\u044e\u0447 \u0432 Keystore, \u0430 slave-\u043a\u043b\u044e\u0447\u0438 \u0434\u043b\u044f \u0448\u0438\u0444\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0434\u0430\u043d\u043d\u044b\u0445 \u2014 \u0432 \u043b\u044e\u0431\u043e\u043c \u0434\u0440\u0443\u0433\u043e\u043c Persistent Storage, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440 \u0432 SharedPreferences. \u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u044b\u0435 \u043f\u0440\u0438\u043c\u0438\u0442\u0438\u0432\u044b: AES, RSA, Keystore. \u041c\u044b \u0445\u0440\u0430\u043d\u0438\u043b\u0438 slave-\u043a\u043b\u044e\u0447\u0438 \u0432 SharedPreferences. \u041d\u0430\u0434\u0435\u044e\u0441\u044c, \u0432\u0430\u043c \u043d\u0435 \u043f\u0440\u0438\u0434\u0451\u0442\u0441\u044f \u044d\u0442\u0438\u043c \u0437\u0430\u043d\u0438\u043c\u0430\u0442\u044c\u0441\u044f, \u043f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e \u0442\u0430\u043a \u0432\u044b \u0441\u044d\u043a\u043e\u043d\u043e\u043c\u0438\u0442\u0435 \u043c\u043d\u043e\u0433\u043e \u043d\u0435\u0440\u0432\u043e\u0432 \u0438 \u0432\u0440\u0435\u043c\u0435\u043d\u0438.<\/p>\n<p>\u041f\u0440\u0438\u043c\u0435\u0440 \u043a\u043e\u0434\u0430:<\/p>\n<details class=\"spoiler\">\n<summary>AesEncryption.kt<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"kotlin\">\/\/ \u041b\u044e\u0431\u0430\u044f \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f slave-\u043a\u043b\u044e\u0447\u0435\u0439, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440 \u0432 SharedPreferences interface KeyStorage {     operator fun get(name: String): ByteArray?     operator fun set(name: String, key: ByteArray?) }   class AesEncryptionManager(     context: Context,     initExecutor: Executor,     exceptionHandler: (Exception) -> Unit,     private val keyStorage: KeyStorage, ) : EncryptionManager {       private val initLock = ReentrantReadWriteLock()       private val masterKeyValidityStartDate: Date     private val masterKeyValidityEndDate: Date       private var initLatch = CountDownLatch(1)       private lateinit var keyStore: KeyStore     private lateinit var aesCipher: Cipher     private val cipherLock: ReentrantLock = ReentrantLock()       init {         val calendar = Calendar.getInstance()         masterKeyValidityStartDate = calendar.time         calendar.add(Calendar.YEAR, 30)         masterKeyValidityEndDate = calendar.time         initExecutor.execute { init(exceptionHandler, masterKeyCreationCallback) }     }       @WorkerThread     @Throws(EncryptionException::class)     fun init(exceptionHandler: (Exception) -> Unit) = initLock.write {         if (initLatch.count == 0L) {             return         }           try {             keyStore = KeyStore.getInstance(\"AndroidKeyStore\")             keyStore.load(null)             aesCipher = Cipher.getInstance(AES_CIPHER_SUIT)               if (!hasMasterKey()) {                 createMasterKey()                 masterKeyCreationCallback()             }         } catch (e: Exception) {             exceptionHandler.invoke(EncryptionException(\"Failed to run init\", e))         } finally {             initLatch.countDown()         }     }       override fun encrypt(keyAlias: String, data: ByteArray): EncryptionManager.EncryptedData? {         initLock.read {             checkStateOrThrow()         }           val key = getKey(keyAlias) ?: createKey(keyAlias)         return encryptWithKey(key, data)     }       override fun decrypt(keyAlias: String, data: EncryptionManager.EncryptedData): ByteArray? {         initLock.read {             checkStateOrThrow()         }           val key = getKey(keyAlias) ?: throw EncryptionException(\"No key with alias $keyAlias\")         return decryptWithKey(key, data)     }       override fun removeKey(keyAlias: String) {         keyStorage[keyAlias] = null     }       override fun waitForInitialize(maxTimeMs: Long): Boolean {         return initLatch.await(maxTimeMs, TimeUnit.MILLISECONDS)     }       \/\/ -- Master key operations       private fun checkStateOrThrow() {         if (initLatch.count > 0L) {             throw EncryptionException(\"Manager is not initialized\")         }           if (!hasMasterKey()) {             throw EncryptionException(\"Cannot perform operations without master key\")         }     }       private fun createMasterKey() {         try {             KeyPairGenerator                 .getInstance(\"RSA\", \"AndroidKeyStore\")                 .apply {                     initialize(createSpec())                     generateKeyPair()                 }         } catch (e: Exception) {             throw EncryptionException(\"Failed to generate master key\", e)         }     }       private fun hasMasterKey(): Boolean {         return try {             keyStore.getKey(MASTER_KEY_ALIAS, null) != null         } catch (e: Exception) {             L.w(e, \"Failed to retrieve master key\")             false         }     }       private fun encryptWithMasterKey(data: ByteArray): ByteArray {         return try {             val cipher = Cipher.getInstance(RSA_KEY_SUIT)             val key = keyStore.getCertificate(MASTER_KEY_ALIAS).publicKey             cipher.init(Cipher.ENCRYPT_MODE, key)             cipher.doFinal(data)         } catch (e: Exception) {             throw EncryptionException(\"Failed to encrypt with master key\", e)         }     }       private fun decryptWithMasterKey(data: ByteArray): ByteArray {         return try {             val cipher = Cipher.getInstance(RSA_KEY_SUIT)             val key = keyStore.getKey(MASTER_KEY_ALIAS, null)             cipher.init(Cipher.DECRYPT_MODE, key)             cipher.doFinal(data)         } catch (e: Exception) {             throw EncryptionException(\"Failed to decrypt with master key\", e)         }     }   \/\/ -- AES key operations       private fun getKey(name: String): Key? {         val encryptedKey = keyStorage[name]         if (encryptedKey == null) {             L.i(\"No key with alias $name\")             return null         }           return Key(decryptWithMasterKey(encryptedKey))     }       private fun createKey(name: String): Key {         val key = UUID.randomUUID().toString()             .lowercase()             .replace(\"-\", \"\")             .toCharArray()           val salt = UUID.randomUUID().toByteArray()           val spec = PBEKeySpec(key, salt, PBKDF2_ITER_COUNT, AES_KEY_SIZE)           val skf = SecretKeyFactory.getInstance(PBKDF2_ALGORITHM)         val generatedKey = try {             skf.generateSecret(spec).encoded         } catch (e: Exception) {             throw EncryptionException(\"Failed to generate key\", e)         }           val encryptedKey = encryptWithMasterKey(generatedKey)         keyStorage[name] = encryptedKey           return Key(generatedKey)     }       private fun encryptWithKey(key: Key, data: ByteArray): EncryptionManager.EncryptedData {         return try {             val keySpec = SecretKeySpec(key.encodedKey, AES_KEY_SPEC)             cipherLock.withLock {                 aesCipher.init(Cipher.ENCRYPT_MODE, keySpec)                 val encrypted = aesCipher.doFinal(data)                 EncryptionManager.EncryptedData(encrypted, aesCipher.iv)             }         } catch (e: Exception) {             throw EncryptionException(\"Failed to encrypt with raw aes key\", e)         }     }       private fun decryptWithKey(key: Key, data: EncryptionManager.EncryptedData): ByteArray {         return try {             cipherLock.withLock {                 val keySpec = SecretKeySpec(key.encodedKey, AES_KEY_SPEC)                 aesCipher.init(Cipher.DECRYPT_MODE, keySpec, IvParameterSpec(data.initVector))                 aesCipher.doFinal(data.data)             }         } catch (e: Exception) {             throw EncryptionException(\"Failed to decrypt with aes key\", e)         }     }       private fun createSpec(): AlgorithmParameterSpec {         return KeyGenParameterSpec.Builder(MASTER_KEY_ALIAS, KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT)             .setKeySize(KEY_SIZE)             .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1)             .setAlgorithmParameterSpec(RSAKeyGenParameterSpec(KEY_SIZE, RSAKeyGenParameterSpec.F4))             \/\/ key validity start &amp; end explicitly not set as they caused KeyNotYetValidException @ api 27             .setCertificateSubject(X500Principal(\"CN=$MASTER_KEY_ALIAS\"))             .setCertificateSerialNumber(BigInteger.valueOf(abs(MASTER_KEY_ALIAS.hashCode()).toLong()))             .build()     }       companion object {         private const val MASTER_KEY_ALIAS = \"ALIAS_MASTER_KEY\"           private const val KEY_SIZE = 2048 \/\/ bits         private const val AES_KEY_SIZE = 256         private const val PBKDF2_ITER_COUNT = 10000           private const val AES_CIPHER_SUIT = \"AES\/CBC\/PKCS7Padding\"         private const val AES_KEY_SPEC = \"AES\"         private const val RSA_KEY_SUIT = \"RSA\/NONE\/PKCS1Padding\"         private const val PBKDF2_ALGORITHM = \"PBKDF2WithHmacSHA1\"     } }<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<p>\u041e\u0448\u0438\u0431\u043a\u0438:<\/p>\n<ul>\n<li>\n<p>KeyStoreException: Key not found<\/p>\n<\/li>\n<li>\n<p>KeyStoreException: Unknown error at android.security.KeyStore.getKeyStoreException \u043f\u0440\u0438 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0438 \u0434\u0430\u043d\u043d\u044b\u0445<\/p>\n<\/li>\n<li>\n<p>KeyStoreException: Memory allocation failed at android.security.KeyStore \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f<\/p>\n<\/li>\n<li>\n<p>KeyStoreException: Unsupported digest<\/p>\n<\/li>\n<\/ul>\n<p>\u0415\u0436\u0435\u0434\u043d\u0435\u0432\u043d\u043e \u043c\u044b \u0444\u0438\u043a\u0441\u0438\u0440\u043e\u0432\u0430\u043b\u0438 \u0431\u043e\u043b\u044c\u0448\u0435 150 \u0442\u044b\u0441\u044f\u0447 (!) \u043e\u0448\u0438\u0431\u043e\u043a \u043f\u0440\u0438 \u0440\u0430\u0431\u043e\u0442\u0435 \u0441 \u0448\u0438\u0444\u0440\u043e\u0432\u0430\u043d\u0438\u0435\u043c. \u041f\u0440\u0438 \u044d\u0442\u043e\u043c \u0441\u0430\u043c\u0438 \u043e\u0442\u043b\u043e\u0432\u0438\u0442\u044c \u0438\u0445 \u043d\u0435 \u043c\u043e\u0433\u043b\u0438.<\/p>\n<figure class=\"bordered full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/f30\/00a\/f66\/f3000af665ff5ff9f15912f47e933a59.png\" width=\"1586\" height=\"1259\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/f30\/00a\/f66\/f3000af665ff5ff9f15912f47e933a59.png\"\/><\/figure>\n<figure class=\"float bordered full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/ec0\/305\/617\/ec0305617f19d08ec763600596b77a01.png\" width=\"977\" height=\"1407\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/ec0\/305\/617\/ec0305617f19d08ec763600596b77a01.png\"\/><\/figure>\n<p>\u0421\u0440\u0435\u0434\u043d\u0435\u0435 \u0432\u0440\u0435\u043c\u044f \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u043f\u043e \u043d\u0430\u0448\u0438\u043c \u0434\u0430\u043d\u043d\u044b\u043c: 100 \u043c\u0441, \u0441 \u0443\u0447\u0451\u0442\u043e\u043c \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u0441\u0435\u0441\u0441\u0438\u0439 \u0432 \u0411\u0414. \u0411\u043e\u043b\u044c\u0448\u0435 \u0432\u0441\u0435\u0433\u043e \u0432\u0440\u0435\u043c\u0435\u043d\u0438 \u0437\u0430\u043d\u0438\u043c\u0430\u044e\u0442 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0438 <code>KeyStore.load()<\/code> \u0438 <code>cipher.doFinal()<\/code>. <strong>\u041e\u043d\u0438 \u043c\u043e\u0433\u0443\u0442 \u043f\u0440\u0438\u0432\u043e\u0434\u0438\u0442\u044c \u043a ANR \u043d\u0430 \u043d\u0435\u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430\u0445<\/strong>.<\/p>\n<h3>\u0412\u0442\u043e\u0440\u043e\u0439 \u0441\u043f\u043e\u0441\u043e\u0431: EncryptedFile<\/h3>\n<p>\u041e\u0447\u0435\u043d\u044c \u043f\u0440\u043e\u0441\u0442\u043e\u0439 \u0432 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0441\u043f\u043e\u0441\u043e\u0431, \u0440\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0443\u0435\u043c\u044b\u0439 Google:<\/p>\n<details class=\"spoiler\">\n<summary>EncryptedFileWrapper.kt<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"kotlin\">class EncryptedFileWrapper(     context: Context,     fileName: String,     private val sessionStatHelper: SessionStatHelper ) {     private val lock = Any()          private val encryptedFileLink by lazy(mode = LazyThreadSafetyMode.NONE) {         File(context.filesDir, \"encrypted_$fileName\")     }     private val encryptedFile: EncryptedFile by lazy(mode = LazyThreadSafetyMode.NONE) {         try {             EncryptedFile.Builder(                 encryptedFileLink,                 context,                 MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC),                 EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB             ).build()         } catch (th: Throwable) {             L.e(th, \"create_encrypted_file\")             sessionStatHelper.sendError(mapOf(\"action\" to \"create_encrypted_file\", \"stacktrace\" to th.stackTraceToString()))             throw th         }     }      @Throws(GeneralSecurityException::class, IOException::class, KeyStoreException::class)     fun writeEncryptedUnsafe(value: String) = synchronized(lock) {         if (encryptedFileLink.exists()) {             encryptedFileLink.delete()         }          val bytes = value.toByteArray(StandardCharsets.UTF_8)         encryptedFile.openFileOutput().use {             it.write(bytes)             it.flush()         }     }      @Throws(GeneralSecurityException::class, IOException::class, KeyStoreException::class)     fun readEncryptedUnsafe(): String? = synchronized(lock) { \/\/ blocking reading         if (!encryptedFileLink.exists()) {             return null         }          if (encryptedFileLink.length() == 0L) {             encryptedFileLink.delete()             return null         }          val byteArrayOutputStream = ByteArrayOutputStream()         var nextByte: Int         encryptedFile.openFileInput().use { encryptedStream ->             nextByte = encryptedStream.read()             while (nextByte != -1) {                 byteArrayOutputStream.write(nextByte)                 nextByte = encryptedStream.read()             }         }          return byteArrayOutputStream.use { String(it.toByteArray(), StandardCharsets.UTF_8) }     }      @Throws(SecurityException::class)     fun clear(): Boolean = synchronized(lock) {         encryptedFileLink.delete()     } }<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<p>\u041d\u043e \u0432\u043e\u0442 <strong>\u043a\u0430\u043a \u043d\u0430 \u0441\u0430\u043c\u043e\u043c \u0434\u0435\u043b\u0435<\/strong> \u043d\u0443\u0436\u043d\u043e \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440 EncryptedFile:<\/p>\n<details class=\"spoiler\">\n<summary>EncryptedFileCreation.kt<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"kotlin\">private val encryptedFile: EncryptedFile by lazy(mode = LazyThreadSafetyMode.NONE) {     \/\/ Single-creation problem     \/\/ Inspired by https:\/\/github.com\/signalapp\/Signal-Android\/commit\/f1f505d41c0d164c10226290d2af6f01b4461ae5     try {         createEncryptedFileForce(context)     } catch (th: Throwable) {         L.e(th, \"create_encrypted_file_1\")         sessionStatHelper.sendError(mapOf(\"action\" to create_encrypted_file_1\", \"stacktrace\" to th.partStackTrace()))           try {             createEncryptedFileForce(context)         } catch (th: Throwable) {             L.e(th, \"create_encrypted_file_1\")             sessionStatHelper.sendError(mapOf(\"action\" to \"EF_create_encrypted_file_2\", \"stacktrace\" to th.partStackTrace()))             throw th         }     } }<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<p>\u0414\u0430-\u0434\u0430, \u0435\u0441\u043b\u0438 \u0432\u044b \u043d\u0435 \u0441\u043c\u043e\u0433\u043b\u0438 \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u0432 \u043f\u0435\u0440\u0432\u044b\u0439 \u0440\u0430\u0437, \u043f\u043e\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0435\u0449\u0451! \u0422\u0430\u043a\u043e\u0439 \u0441\u043f\u043e\u0441\u043e\u0431 \u043d\u0430\u0448\u043b\u0438 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0438 \u0437\u0430\u0449\u0438\u0449\u0451\u043d\u043d\u043e\u0433\u043e \u043c\u0435\u0441\u0441\u0435\u043d\u0434\u0436\u0435\u0440\u0430 Signal.<\/p>\n<p>\u0414\u0430\u043d\u043d\u044b\u0435:<\/p>\n<ul>\n<li>\n<p>\u0421\u0440\u0435\u0434\u043d\u0435\u0435 \u0432\u0440\u0435\u043c\u044f \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438: 70-90 \u043c\u0441 \u0432 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 \u043e\u0442 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0438 \u0432\u0435\u0440\u0441\u0438\u0438 Android. <strong>\u041d\u0430 \u043d\u0435\u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430\u0445 \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f, \u0448\u0438\u0444\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0438 \u0434\u0435\u0448\u0438\u0444\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u043c\u043e\u0436\u0435\u0442 \u043f\u0440\u0438\u0432\u043e\u0434\u0438\u0442\u044c \u043a ANR.<\/strong><\/p>\n<\/li>\n<li>\n<p>\u0421\u0440\u0435\u0434\u043d\u0435\u0435 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u043e\u0448\u0438\u0431\u043e\u043a \u0432 \u0434\u0435\u043d\u044c: 5000.<\/p>\n<\/li>\n<li>\n<p>\u041e\u0448\u0438\u0431\u043a\u0438:<\/p>\n<ul>\n<li>\n<p>InvalidProtocolBufferException: Protocol message contained an invalid tag (zero)<\/p>\n<\/li>\n<li>\n<p>KeyStoreException: the master key android-keystore:\/\/<em>androidx_security_master_key<\/em> exists but is unusable.<\/p>\n<\/li>\n<li>\n<p>IOException: write failed: ENOSPC (No space left on device).<\/p>\n<\/li>\n<li>\n<p>FileNotFoundException: \/data\/user\/0\/com.vkontakte.android\/files\/encrypted_sessions.json: open failed: ENOSPC (No space left on device).<\/p>\n<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/d0a\/e29\/ed0\/d0ae29ed08be7ad291911e1c16f671a7.png\" alt=\"\u041e\u0448\u0438\u0431\u043a\u0438 EncryptedFile\" title=\"\u041e\u0448\u0438\u0431\u043a\u0438 EncryptedFile\" width=\"1600\" height=\"484\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/d0a\/e29\/ed0\/d0ae29ed08be7ad291911e1c16f671a7.png\"\/><\/p>\n<div><figcaption>\u041e\u0448\u0438\u0431\u043a\u0438 EncryptedFile<\/figcaption><\/div>\n<\/figure>\n<figure class=\"float bordered full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/d5e\/c75\/963\/d5ec7596320da777763ea7bd399a2e29.png\" alt=\"\u0421\u0440\u0435\u0434\u043d\u0435\u0435 \u0432\u0440\u0435\u043c\u044f \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 EncryptedFile\" title=\"\u0421\u0440\u0435\u0434\u043d\u0435\u0435 \u0432\u0440\u0435\u043c\u044f \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 EncryptedFile\" width=\"896\" height=\"812\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/d5e\/c75\/963\/d5ec7596320da777763ea7bd399a2e29.png\"\/><\/p>\n<div><figcaption>\u0421\u0440\u0435\u0434\u043d\u0435\u0435 \u0432\u0440\u0435\u043c\u044f \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 EncryptedFile<\/figcaption><\/div>\n<\/figure>\n<p><strong>\u0414\u043e\u0441\u0442\u043e\u0438\u043d\u0441\u0442\u0432\u0430 EncryptedFile:<\/strong><\/p>\n<ul>\n<li>\n<p><strong>\u041f\u0440\u043e\u0441\u0442\u043e\u0442\u0430.<\/strong> \u0412 \u043e\u0442\u043b\u0438\u0447\u0438\u0435 \u043e\u0442 \u0441\u0430\u043c\u043e\u0441\u0442\u043e\u044f\u0442\u0435\u043b\u044c\u043d\u043e\u0433\u043e \u0448\u0438\u0444\u0440\u043e\u0432\u0430\u043d\u0438\u044f, \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f \u0441 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u043c EncryptedFile \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0434\u043e\u0432\u043e\u043b\u044c\u043d\u043e \u043f\u0440\u044f\u043c\u043e\u043b\u0438\u043d\u0435\u0439\u043d\u043e\u0439.<\/p>\n<\/li>\n<li>\n<p><strong>\u0420\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0430\u0446\u0438\u0438 \u043e\u0442 Google.<\/strong> \u0422\u0430\u043a \u043a\u0430\u043a \u044d\u0442\u043e \u0440\u0435\u0448\u0435\u043d\u0438\u0435 \u0440\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u043e\u0432\u0430\u043d\u043e Google, \u043e\u043d\u043e \u043e\u0431\u044b\u0447\u043d\u043e \u0445\u043e\u0440\u043e\u0448\u043e \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442\u0441\u044f \u0438 \u043e\u0431\u043d\u043e\u0432\u043b\u044f\u0435\u0442\u0441\u044f.<\/p>\n<\/li>\n<\/ul>\n<p><strong>\u041d\u0435\u0434\u043e\u0441\u0442\u0430\u0442\u043a\u0438 EncryptedFile:<\/strong><\/p>\n<ul>\n<li>\n<p><strong>\u041e\u0448\u0438\u0431\u043a\u0438.<\/strong> \u041a\u0430\u043a \u043c\u044b \u0432\u044b\u044f\u0441\u043d\u0438\u043b\u0438, \u043c\u043e\u0433\u0443\u0442 \u0432\u043e\u0437\u043d\u0438\u043a\u043d\u0443\u0442\u044c \u0440\u0430\u0437\u043b\u0438\u0447\u043d\u044b\u0435 \u043e\u0448\u0438\u0431\u043a\u0438, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043d\u0435 \u0432\u0441\u0435\u0433\u0434\u0430 \u043e\u0447\u0435\u0432\u0438\u0434\u043d\u044b \u0438\u0437 \u043e\u0444\u0438\u0446\u0438\u0430\u043b\u044c\u043d\u043e\u0439 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u0438.<\/p>\n<\/li>\n<li>\n<p><strong>\u0412\u0440\u0435\u043c\u044f \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438.<\/strong> \u041d\u0430 \u043d\u0435\u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430\u0445 \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f \u043c\u043e\u0436\u0435\u0442 \u0437\u0430\u043d\u0438\u043c\u0430\u0442\u044c \u0434\u043e\u0432\u043e\u043b\u044c\u043d\u043e \u043c\u043d\u043e\u0433\u043e \u0432\u0440\u0435\u043c\u0435\u043d\u0438.<\/p>\n<\/li>\n<\/ul>\n<h3>\u041f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0439 \u0441\u043f\u043e\u0441\u043e\u0431: EncryptedSharedPreferences<\/h3>\n<p>\u0421\u0430\u043c\u044b\u0439 \u043f\u0440\u0438\u044f\u0442\u043d\u044b\u0439 \u0441\u043f\u043e\u0441\u043e\u0431. \u041d\u043e \u043e\u043d \u043f\u043e\u0434\u0432\u0435\u0440\u0436\u0435\u043d \u0442\u0435\u043c \u0436\u0435 \u0441\u0430\u043c\u044b\u043c \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0430\u043c \u0438 \u043e\u0448\u0438\u0431\u043a\u0430\u043c, \u0447\u0442\u043e \u0435\u0441\u0442\u044c \u0443 EncryptedFile. \u0423 \u043d\u0435\u0433\u043e \u0447\u0443\u0442\u044c \u043b\u0443\u0447\u0448\u0435 \u0432\u0440\u0435\u043c\u044f \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0438 \u0447\u0443\u0442\u044c \u043c\u0435\u043d\u044c\u0448\u0435 \u043e\u0448\u0438\u0431\u043e\u043a, \u043d\u043e \u043e\u043d \u0442\u043e\u0436\u0435 \u043d\u0435 \u0433\u0430\u0440\u0430\u043d\u0442\u0438\u0440\u0443\u0435\u0442 \u043f\u043e\u043b\u043d\u043e\u0439 \u0437\u0430\u0449\u0438\u0449\u0451\u043d\u043d\u043e\u0441\u0442\u0438 \u043e\u0442 \u043e\u0448\u0438\u0431\u043e\u043a \u0448\u0438\u0444\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0434\u0430\u0436\u0435 \u043d\u0430 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0445 \u0432\u0435\u0440\u0441\u0438\u044f\u0445 Android. <strong>\u041d\u0430 \u043d\u0435\u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430\u0445 \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f, \u0448\u0438\u0444\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0438 \u0434\u0435\u0448\u0438\u0444\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u043c\u043e\u0436\u0435\u0442 \u043f\u0440\u0438\u0432\u043e\u0434\u0438\u0442\u044c \u043a ANR.<\/strong><\/p>\n<p>\u0415\u0441\u0442\u044c \u0438 \u0434\u0440\u0443\u0433\u0438\u0435 \u0441\u043f\u043e\u0441\u043e\u0431\u044b, \u043c\u043e\u0436\u043d\u043e \u043d\u0430\u0439\u0442\u0438 <a href=\"https:\/\/github.com\/topics\/android-encryption\"><u>\u043c\u043d\u043e\u0433\u043e \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a \u043d\u0430 GitHub<\/u><\/a>, \u043d\u043e, \u0441\u0443\u0434\u044f \u043f\u043e \u0438\u0445 \u043a\u043e\u0434\u0443, \u043e\u043d\u0438 \u0432\u0441\u0451 \u0440\u0430\u0432\u043d\u043e \u043d\u0435 \u043c\u043e\u0433\u0443\u0442 \u0433\u0430\u0440\u0430\u043d\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0437\u0430\u0449\u0438\u0449\u0451\u043d\u043d\u043e\u0441\u0442\u0438.<\/p>\n<p>\u0421\u0440\u0430\u0432\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u0430\u044f \u0442\u0430\u0431\u043b\u0438\u0446\u0430 \u0432\u0441\u0435\u0445 \u0441\u043f\u043e\u0441\u043e\u0431\u043e\u0432:<\/p>\n<div>\n<div class=\"table\">\n<table>\n<tbody>\n<tr>\n<td>\n<p align=\"left\">\u0421\u043f\u043e\u0441\u043e\u0431<\/p>\n<\/td>\n<td>\n<p align=\"left\"><strong>\u0421\u0440\u0435\u0434\u043d\u0435\u0435 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u043e\u0448\u0438\u0431\u043e\u043a \u0432 \u0434\u0435\u043d\u044c (\u043d\u0430 \u0430\u0443\u0434\u0438\u0442\u043e\u0440\u0438\u0438 VK)<\/strong><\/p>\n<\/td>\n<td>\n<p align=\"left\"><strong>\u0421\u0440\u0435\u0434\u043d\u044f\u044f \u0434\u043b\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438<\/strong><\/p>\n<\/td>\n<td>\n<p align=\"left\"><strong>\u0413\u0430\u0440\u0430\u043d\u0442\u0438\u044f, \u0447\u0442\u043e \u0434\u0430\u043d\u043d\u044b\u0435 \u0431\u0443\u0434\u0443\u0442 \u0437\u0430\u043f\u0438\u0441\u0430\u043d\u044b \u0438 \u043f\u0440\u043e\u0447\u0438\u0442\u0430\u043d\u044b<\/strong><\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\"><strong>\u041f\u0440\u0438\u043c\u0438\u0442\u0438\u0432\u044b \u0448\u0438\u0444\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0438\u0437 Andorid SDK<\/strong><\/p>\n<\/td>\n<td>\n<p align=\"left\">>100 \u0442\u044b\u0441. \u0432 \u0434\u0435\u043d\u044c<\/p>\n<\/td>\n<td>\n<p align=\"left\">100 \u043c\u0441<\/p>\n<\/td>\n<td>\n<p align=\"left\">\u041e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0443\u0435\u0442<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\"><strong>EncryptedFile<\/strong><\/p>\n<\/td>\n<td>\n<p align=\"left\">5000 \u0432 \u0434\u0435\u043d\u044c (\u0443\u043d\u0438\u043a\u0430\u043b\u044c\u043d\u044b\u0435)<\/p>\n<\/td>\n<td>\n<p align=\"left\">70-90 \u043c\u0441<\/p>\n<\/td>\n<td>\n<p align=\"left\">\u041e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0443\u0435\u0442<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\"><strong>EncryptedSharedPreferences<\/strong><\/p>\n<\/td>\n<td>\n<p align=\"left\">3000 \u0432 \u0434\u0435\u043d\u044c (\u0443\u043d\u0438\u043a\u0430\u043b\u044c\u043d\u044b\u0435)<\/p>\n<\/td>\n<td>\n<p align=\"left\">70 \u043c\u0441<\/p>\n<\/td>\n<td>\n<p align=\"left\">\u041e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0443\u0435\u0442<\/p>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<\/div>\n<h3>\u0413\u0434\u0435 \u043c\u043e\u0438 \u0433\u0430\u0440\u0430\u043d\u0442\u0438\u0438?<\/h3>\n<p>\u041f\u043e\u043b\u0443\u0447\u0430\u0435\u0442\u0441\u044f, \u0447\u0442\u043e \u043d\u0435\u0442 \u043d\u0438 \u043e\u0434\u043d\u043e\u0433\u043e \u0433\u0430\u0440\u0430\u043d\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u043e\u0433\u043e \u0441\u043f\u043e\u0441\u043e\u0431\u0430 \u043f\u0440\u043e\u0447\u0438\u0442\u0430\u0442\u044c \u0438 \u0437\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u0434\u0430\u043d\u043d\u044b\u0435 \u0437\u0430\u0449\u0438\u0449\u0451\u043d\u043d\u043e. \u041e\u0442\u0432\u0435\u0442 \u0434\u043e\u0432\u043e\u043b\u044c\u043d\u043e \u043f\u0440\u043e\u0441\u0442: \u0432 \u0441\u043b\u0443\u0447\u0430\u0435 \u043e\u0448\u0438\u0431\u043e\u043a \u043c\u043e\u0436\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043c\u0435\u0442\u043e\u0434 \u0434\u0443\u0431\u043b\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0434\u0430\u043d\u043d\u044b\u0445 \u0432 \u0430\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u043d\u043e\u0435 \u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0435. \u041e\u0448\u0438\u0431\u043a\u0438 \u0431\u0443\u0434\u0443\u0442 \u0432\u043e\u0437\u043d\u0438\u043a\u0430\u0442\u044c \u0443 \u043c\u0435\u043d\u044c\u0448\u0438\u043d\u0441\u0442\u0432\u0430 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439, \u0438 \u0442\u0430\u043a\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c \u043c\u044b \u0441\u043c\u043e\u0436\u0435\u043c \u043e\u0431\u0435\u0441\u043f\u0435\u0447\u0438\u0442\u044c \u0437\u0430\u0449\u0438\u0442\u0443 \u0431\u043e\u043b\u044c\u0448\u0435\u0439 \u0447\u0430\u0441\u0442\u0438 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438. \u0412\u043e\u0442 \u043a\u0430\u043a \u044d\u0442\u043e \u043c\u043e\u0436\u043d\u043e \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u0442\u044c:<\/p>\n<details class=\"spoiler\">\n<summary>SafeEncryptedPreferences.kt<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"kotlin\">class SafeEncryptedPreferences(     context: Context,     fileName: String ) : SharedPreferences {     private val encrypted: SharedPreferences by lazy { EncryptedPreferencesHelper.createEncrypted(context, fileName, plain) }     private val plain: SharedPreferences by lazy {         context.getSharedPreferences(\"plain_$fileName\", MODE_PRIVATE)     }       override fun contains(key: String?): Boolean {         return encrypted.safeContains(key) || plain.contains(key)     }       override fun getBoolean(key: String?, defValue: Boolean): Boolean {         return get(key, defValue, SharedPreferences::getBoolean)     }       override fun getInt(key: String?, defValue: Int): Int {         return get(key, defValue, SharedPreferences::getInt)     }       override fun getLong(key: String?, defValue: Long): Long {         return get(key, defValue, SharedPreferences::getLong)     }       override fun getFloat(key: String?, defValue: Float): Float {         return get(key, defValue, SharedPreferences::getFloat)     }       override fun getString(key: String?, defValue: String?): String? {         return get(key, defValue, SharedPreferences::getString)     }       override fun getStringSet(key: String?, defValues: MutableSet&lt;String>?): MutableSet&lt;String>? {         return get(key, defValues, SharedPreferences::getStringSet)     }       override fun getAll(): MutableMap&lt;String, *> {         val allEncrypted = encrypted.safeAll()         val allPlain = plain.all         val all = HashMap&lt;String, Any?>(allEncrypted.size + allEncrypted.size)         all.putAll(allPlain)         all.putAll(allEncrypted)         return all     }       override fun edit(): SharedPreferences.Editor {         return Editor(encrypted.edit(), plain.edit())     }       override fun registerOnSharedPreferenceChangeListener(         listener: SharedPreferences.OnSharedPreferenceChangeListener?     ) {         encrypted.registerOnSharedPreferenceChangeListener(listener)         plain.registerOnSharedPreferenceChangeListener(listener)     }       override fun unregisterOnSharedPreferenceChangeListener(         listener: SharedPreferences.OnSharedPreferenceChangeListener?     ) {         encrypted.unregisterOnSharedPreferenceChangeListener(listener)         plain.unregisterOnSharedPreferenceChangeListener(listener)     }       private inline fun &lt;T> get(         key: String?,         defValue: T,         getter: SharedPreferences.(key: String?, defValue: T) -> T     ): T {         return if (encrypted.safeContains(key)) {             try {                 encrypted.getter(key, defValue)             } catch (e: Exception) {                 plain.getter(key, defValue)             }         } else {             plain.getter(key, defValue)         }     }       private class Editor(         private val encryptedEditor: SharedPreferences.Editor,         private val plainEditor: SharedPreferences.Editor     ) : SharedPreferences.Editor {           private val clearRequested = AtomicBoolean(false)           override fun remove(key: String?) = apply {             encryptedEditor.safeRemove(key)             plainEditor.remove(key)         }           override fun clear() = apply {             clearRequested.set(true)             encryptedEditor.safeClear()             plainEditor.clear()         }           override fun putLong(key: String?, value: Long) = apply {             put(key, value, SharedPreferences.Editor::putLong)         }           override fun putInt(key: String?, value: Int) = apply {             put(key, value, SharedPreferences.Editor::putInt)         }           override fun putBoolean(key: String?, value: Boolean) = apply {             put(key, value, SharedPreferences.Editor::putBoolean)         }           override fun putStringSet(key: String?, values: MutableSet&lt;String>?) = apply {             put(key, values, SharedPreferences.Editor::putStringSet)         }           override fun putFloat(key: String?, value: Float) = apply {             put(key, value, SharedPreferences.Editor::putFloat)         }           override fun putString(key: String?, value: String?) = apply {             put(key, value, SharedPreferences.Editor::putString)         }           override fun commit(): Boolean {             return encryptedEditor.safeCommit() &amp;&amp; plainEditor.commit()         }           override fun apply() {             if (clearRequested.getAndSet(false)) {                 encryptedEditor.safeCommit()             } else {                 encryptedEditor.safeApply()             }             plainEditor.apply()         }           private inline fun &lt;T> put(             key: String?,             value: T,             putter: SharedPreferences.Editor.(key: String?, value: T) -> Any         ) = apply {             try {                 encryptedEditor.putter(key, value)             } catch (e: Exception) {                 plainEditor.putter(key, value)             }         }     }       companion object {         fun SharedPreferences.safeContains(key: String?): Boolean {             return try {                 contains(key)             } catch (e: Exception) {                 false             }         }           fun SharedPreferences.safeAll(): Map&lt;String, *> {             return try {                 all             } catch (e: Exception) {                 emptyMap&lt;String, Any?>()             }         }           fun SharedPreferences.Editor.safeRemove(key: String?): SharedPreferences.Editor {             return try {                 remove(key)             } catch (e: Exception) {                 this             }         }           fun SharedPreferences.Editor.safeClear(): SharedPreferences.Editor {             return try {                 clear()             } catch (e: Exception) {                 this             }         }           fun SharedPreferences.Editor.safeCommit(): Boolean {             return try {                 commit()             } catch (e: Exception) {                 return false             }         }           fun SharedPreferences.Editor.safeApply() {             return try {                 apply()             } catch (ignored: Exception) {             }         }     } }<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<p>\u0412\u043e\u0442 \u0432 \u0442\u0430\u043a\u043e\u0439 \u043c\u044b \u043f\u043b\u0430\u0447\u0435\u0432\u043d\u043e\u0439 \u0441\u0438\u0442\u0443\u0430\u0446\u0438\u0438. \u041f\u0440\u0438 \u044d\u0442\u043e\u043c \u043c\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u0441\u0430\u043c\u0443\u044e \u0441\u0442\u0430\u0431\u0438\u043b\u044c\u043d\u0443\u044e \u0432\u0435\u0440\u0441\u0438\u044e security-\u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438: 1.0.0-stable.<\/p>\n<h3>\u0411\u043e\u043d\u0443\u0441: \u043a\u0430\u043a \u0442\u0430\u043c \u0434\u0435\u043b\u0430 \u0443 iOS?<\/h3>\n<p>\u0412 iOS \u0434\u043b\u044f \u0448\u0438\u0444\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0441\u0435\u043a\u0440\u0435\u0442\u043e\u0432 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e\u0442 Keychain, \u043e\u043d <a href=\"https:\/\/www.hackingwithswift.com\/read\/28\/3\/writing-somewhere-safe-the-ios-keychain\"><u>\u0438\u043c\u0435\u0435\u0442 \u0433\u0430\u0440\u0430\u043d\u0442\u0438\u0438 \u0438 \u043c\u043e\u0436\u0435\u0442 \u0432\u044b\u0437\u044b\u0432\u0430\u0442\u044c\u0441\u044f \u0432 \u0433\u043b\u0430\u0432\u043d\u043e\u043c \u043f\u043e\u0442\u043e\u043a\u0435<\/u><\/a>.<\/p>\n<h3>\u0420\u0435\u0437\u044e\u043c\u0435<\/h3>\n<p>\u0412\u044b\u0432\u043e\u0434\u044b \u043f\u043e \u0440\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0435\u043d\u043d\u044b\u043c \u0441\u043f\u043e\u0441\u043e\u0431\u0430\u043c \u0448\u0438\u0444\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0434\u0430\u043d\u043d\u044b\u0445:<\/p>\n<div>\n<div class=\"table\">\n<table>\n<tbody>\n<tr>\n<td>\n<p align=\"left\"><strong>\u041c\u0435\u0442\u043e\u0434<\/strong><\/p>\n<\/td>\n<td>\n<p align=\"left\"><strong>\u041f\u0440\u0435\u0438\u043c\u0443\u0449\u0435\u0441\u0442\u0432\u0430<\/strong><\/p>\n<\/td>\n<td>\n<p align=\"left\"><strong>\u041d\u0435\u0434\u043e\u0441\u0442\u0430\u0442\u043a\u0438<\/strong><\/p>\n<\/td>\n<td>\n<p align=\"left\"><strong>\u0421\u0440\u0435\u0434\u043d\u044f\u044f \u0434\u043b\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438<\/strong><\/p>\n<\/td>\n<td>\n<p align=\"left\"><strong>\u0421\u0440\u0435\u0434\u043d\u0435\u0435 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u043e\u0448\u0438\u0431\u043e\u043a \u0432 \u0434\u0435\u043d\u044c<\/strong><\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">\u0421\u0430\u043c\u043e\u0441\u0442\u043e\u044f\u0442\u0435\u043b\u044c\u043d\u043e\u0435 \u0448\u0438\u0444\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0441 Keystore<\/p>\n<\/td>\n<td>\n<p align=\"left\">\u041f\u043e\u043b\u043d\u044b\u0439 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u044c, \u0433\u0438\u0431\u043a\u043e\u0441\u0442\u044c<\/p>\n<\/td>\n<td>\n<p align=\"left\">\u0421\u043b\u043e\u0436\u043d\u043e\u0441\u0442\u044c, \u0432\u044b\u0441\u043e\u043a\u0438\u0439 \u0440\u0438\u0441\u043a \u043e\u0448\u0438\u0431\u043e\u043a<\/p>\n<\/td>\n<td>\n<p align=\"left\">100 \u043c\u0441<\/p>\n<\/td>\n<td>\n<p align=\"left\">150 000<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">EncryptedFile<\/p>\n<\/td>\n<td>\n<p align=\"left\">\u041f\u0440\u043e\u0441\u0442\u043e\u0442\u0430 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438, \u0440\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0430\u0446\u0438\u0438 Google<\/p>\n<\/td>\n<td>\n<p align=\"left\">\u0412\u043e\u0437\u043c\u043e\u0436\u043d\u044b\u0435 \u043e\u0448\u0438\u0431\u043a\u0438 \u0438 \u0438\u0445 \u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0438\u0435 \u0432 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u0438, \u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0438\u0435 \u0433\u0430\u0440\u0430\u043d\u0442\u0438\u0439<\/p>\n<\/td>\n<td>\n<p align=\"left\">70-90 \u043c\u0441<\/p>\n<\/td>\n<td>\n<p align=\"left\">5000<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">EncryptedSharedPreferences<\/p>\n<\/td>\n<td>\n<p align=\"left\">\u041f\u0440\u043e\u0441\u0442\u043e\u0442\u0430 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438, \u0440\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0430\u0446\u0438\u0438 Google<\/p>\n<\/td>\n<td>\n<p align=\"left\">\u0412\u043e\u0437\u043c\u043e\u0436\u043d\u044b\u0435 \u043e\u0448\u0438\u0431\u043a\u0438 \u0438 \u0438\u0445 \u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0438\u0435 \u0432 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u0438, \u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0438\u0435 \u0433\u0430\u0440\u0430\u043d\u0442\u0438\u0439<\/p>\n<\/td>\n<td>\n<p align=\"left\">70 \u043c\u0441<\/p>\n<\/td>\n<td>\n<p align=\"left\"> &#8212;<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">\u0421\u0442\u043e\u0440\u043e\u043d\u043d\u0438\u0435 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438<\/p>\n<\/td>\n<td>\n<p align=\"left\">\u041f\u0440\u043e\u0441\u0442\u043e\u0442\u0430 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f<\/p>\n<\/td>\n<td>\n<p align=\"left\">\u0417\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u044c \u043e\u0442 \u0441\u0442\u043e\u0440\u043e\u043d\u043d\u0438\u0445 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u043e\u0432<\/p>\n<\/td>\n<td>\n<p align=\"left\">&#8212;<\/p>\n<\/td>\n<td>\n<p align=\"left\">&#8212;<\/p>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<\/div>\n<p>\u0412\u0430\u0436\u043d\u043e \u043f\u043e\u043c\u043d\u0438\u0442\u044c: \u043d\u0438 \u043e\u0434\u0438\u043d \u0438\u0437 \u043c\u0435\u0442\u043e\u0434\u043e\u0432 \u043d\u0435 \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u0430\u0431\u0441\u043e\u043b\u044e\u0442\u043d\u044b\u0445 \u0433\u0430\u0440\u0430\u043d\u0442\u0438\u0439 \u0441\u043e\u0445\u0440\u0430\u043d\u043d\u043e\u0441\u0442\u0438 \u0434\u0430\u043d\u043d\u044b\u0445. \u0414\u043b\u044f \u043c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u043e\u0439 \u043d\u0430\u0434\u0451\u0436\u043d\u043e\u0441\u0442\u0438 \u0440\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0443\u0435\u0442\u0441\u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0434\u0432\u043e\u0439\u043d\u0443\u044e \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044e \u0438 \u0437\u0430\u043f\u0430\u0441\u043d\u043e\u0435 \u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0435 (fallback store) \u0434\u043b\u044f \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u043e\u0448\u0438\u0431\u043e\u043a.<\/p>\n<p>\u0410 \u043a\u0430\u043a\u0438\u0435 \u043c\u0435\u0442\u043e\u0434\u044b \u0448\u0438\u0444\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0434\u0430\u043d\u043d\u044b\u0445 \u0432\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0435 \u0432 \u0441\u0432\u043e\u0451\u043c \u043f\u0440\u043e\u0435\u043a\u0442\u0435? \u041a\u0430\u043a \u0432\u044b \u0441\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u0442\u0435\u0441\u044c \u0441 \u043e\u0448\u0438\u0431\u043a\u0430\u043c\u0438 \u0438 \u043d\u0435\u043e\u0436\u0438\u0434\u0430\u043d\u043d\u044b\u043c\u0438 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0430\u043c\u0438?<\/p>\n<\/p>\n<\/div>\n<\/div>\n<\/div>\n<p><!----><!----><\/div>\n<p><!----><!----><br \/> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/776728\/\"> https:\/\/habr.com\/ru\/articles\/776728\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<figure class=\"full-width\"><\/figure>\n<p>\u041f\u0440\u0438\u0432\u0435\u0442, \u0425\u0430\u0431\u0440! \u041c\u0435\u043d\u044f \u0437\u043e\u0432\u0443\u0442 \u0410\u0440\u0442\u0443\u0440 \u0418\u043b\u044c\u043a\u0430\u0435\u0432, \u044f\u00a0\u0440\u0430\u0431\u043e\u0442\u0430\u044e \u0432 \u0434\u0435\u043f\u0430\u0440\u0442\u0430\u043c\u0435\u043d\u0442\u0435 \u044d\u043a\u043e\u0441\u0438\u0441\u0442\u0435\u043c\u043d\u044b\u0445 \u043f\u0440\u043e\u0434\u0443\u043a\u0442\u043e\u0432, \u043c\u044b \u0440\u0430\u0437\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0435\u043c VK ID SDK \u0438 \u0432\u0441\u0435 \u0447\u0442\u043e \u0441\u0432\u044f\u0437\u0430\u043d\u043e \u0441 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0435\u0439 \u0438 \u0441\u0435\u0441\u0441\u0438\u044f\u043c\u0438, \u0432 \u0447\u0430\u0441\u0442\u043d\u043e\u0441\u0442\u0438\u00a0\u2014 \u043c\u0443\u043b\u044c\u0442\u0438\u0430\u043a\u043a\u0430\u0443\u043d\u0442.<\/p>\n<p>\u0421\u0435\u043a\u0440\u0435\u0442\u043d\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435 \u0442\u0440\u0435\u0431\u0443\u044e\u0442 \u043e\u0441\u043e\u0431\u043e\u0433\u043e \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u044f \u043f\u0440\u0438 \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0438 \u0438 \u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0435. \u0418\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u044b \u0434\u043b\u044f \u0448\u0438\u0444\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u043c\u043e\u0433\u0443\u0442 \u0432\u0430\u0440\u044c\u0438\u0440\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u043f\u043e \u0441\u0442\u0435\u043f\u0435\u043d\u0438 \u0441\u043b\u043e\u0436\u043d\u043e\u0441\u0442\u0438, \u043d\u0430\u0434\u0451\u0436\u043d\u043e\u0441\u0442\u0438 \u0438 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u0438. \u0412 \u044d\u0442\u043e\u043c \u043e\u0431\u0437\u043e\u0440\u0435 \u043c\u044b \u0440\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0438\u043c \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0442\u0430\u043a\u0438\u0445 \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u043e\u0432, \u043e\u043f\u0438\u0448\u0435\u043c \u0438\u0445 \u044d\u0444\u0444\u0435\u043a\u0442\u0438\u0432\u043d\u043e\u0441\u0442\u044c \u0438 \u0440\u0430\u0441\u0441\u043a\u0430\u0436\u0435\u043c \u043e \u0440\u0430\u0441\u043f\u0440\u043e\u0441\u0442\u0440\u0430\u043d\u0451\u043d\u043d\u044b\u0445 \u043e\u0448\u0438\u0431\u043a\u0430\u0445 \u043f\u0440\u0438 \u0438\u0445 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0438. \u0421\u0442\u0430\u0442\u044c\u044f \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u0430 \u043f\u043e \u043c\u043e\u0442\u0438\u0432\u0430\u043c <a href=\"https:\/\/habr.com\/ru\/companies\/vk\/articles\/769636\/\"><u>\u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u043c\u0443\u043b\u044c\u0442\u0438\u0430\u043a\u043a\u0430\u0443\u043d\u0442\u0430<\/u><\/a>, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043f\u043e\u0434\u0432\u0451\u043b \u043d\u0430\u0441 \u043a \u0438\u0441\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u043d\u0438\u044e \u043e\u043f\u0442\u0438\u043c\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u0438 \u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e\u0433\u043e \u0441\u043f\u043e\u0441\u043e\u0431\u0430 \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u0441\u0435\u0441\u0441\u0438\u0439.<\/p>\n<h3>\u041f\u0435\u0440\u0432\u044b\u0439 \u0441\u043f\u043e\u0441\u043e\u0431: \u0441\u0430\u043c\u043e\u0441\u0442\u043e\u044f\u0442\u0435\u043b\u044c\u043d\u043e\u0435 \u0448\u0438\u0444\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u043f\u0440\u0438\u043c\u0438\u0442\u0438\u0432\u0430\u043c\u0438 \u0438\u0437 Android SDK<\/h3>\n<p>\u0415\u0441\u043b\u0438 \u0432\u044b \u0431\u0443\u0434\u0435\u0442\u0435 \u043f\u0438\u0441\u0430\u0442\u044c \u0441\u0430\u043c\u0438, \u0441\u043a\u043e\u0440\u0435\u0435 \u0432\u0441\u0435\u0433\u043e \u0441\u0442\u043e\u043b\u043a\u043d\u0451\u0442\u0435\u0441\u044c \u0441 \u043c\u0430\u0441\u0441\u043e\u0439 \u043f\u0440\u043e\u0431\u043b\u0435\u043c. \u041d\u0430\u0438\u0431\u043e\u043b\u0435\u0435 \u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u0430\u044f \u0441\u0445\u0435\u043c\u0430: \u0445\u0440\u0430\u043d\u0438\u0442\u044c \u043c\u0430\u0441\u0442\u0435\u0440-\u043a\u043b\u044e\u0447 \u0432 Keystore, \u0430 slave-\u043a\u043b\u044e\u0447\u0438 \u0434\u043b\u044f \u0448\u0438\u0444\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0434\u0430\u043d\u043d\u044b\u0445 \u2014 \u0432 \u043b\u044e\u0431\u043e\u043c \u0434\u0440\u0443\u0433\u043e\u043c Persistent Storage, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440 \u0432 SharedPreferences. \u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u044b\u0435 \u043f\u0440\u0438\u043c\u0438\u0442\u0438\u0432\u044b: AES, RSA, Keystore. \u041c\u044b \u0445\u0440\u0430\u043d\u0438\u043b\u0438 slave-\u043a\u043b\u044e\u0447\u0438 \u0432 SharedPreferences. \u041d\u0430\u0434\u0435\u044e\u0441\u044c, \u0432\u0430\u043c \u043d\u0435 \u043f\u0440\u0438\u0434\u0451\u0442\u0441\u044f \u044d\u0442\u0438\u043c \u0437\u0430\u043d\u0438\u043c\u0430\u0442\u044c\u0441\u044f, \u043f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e \u0442\u0430\u043a \u0432\u044b \u0441\u044d\u043a\u043e\u043d\u043e\u043c\u0438\u0442\u0435 \u043c\u043d\u043e\u0433\u043e \u043d\u0435\u0440\u0432\u043e\u0432 \u0438 \u0432\u0440\u0435\u043c\u0435\u043d\u0438.<\/p>\n<p>\u041f\u0440\u0438\u043c\u0435\u0440 \u043a\u043e\u0434\u0430:<\/p>\n<details class=\"spoiler\">\n<summary>AesEncryption.kt<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"kotlin\">\/\/ \u041b\u044e\u0431\u0430\u044f \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f slave-\u043a\u043b\u044e\u0447\u0435\u0439, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440 \u0432 SharedPreferences interface KeyStorage {     operator fun get(name: String): ByteArray?     operator fun set(name: String, key: ByteArray?) }   class AesEncryptionManager(     context: Context,     initExecutor: Executor,     exceptionHandler: (Exception) -> Unit,     private val keyStorage: KeyStorage, ) : EncryptionManager {       private val initLock = ReentrantReadWriteLock()       private val masterKeyValidityStartDate: Date     private val masterKeyValidityEndDate: Date       private var initLatch = CountDownLatch(1)       private lateinit var keyStore: KeyStore     private lateinit var aesCipher: Cipher     private val cipherLock: ReentrantLock = ReentrantLock()       init {         val calendar = Calendar.getInstance()         masterKeyValidityStartDate = calendar.time         calendar.add(Calendar.YEAR, 30)         masterKeyValidityEndDate = calendar.time         initExecutor.execute { init(exceptionHandler, masterKeyCreationCallback) }     }       @WorkerThread     @Throws(EncryptionException::class)     fun init(exceptionHandler: (Exception) -> Unit) = initLock.write {         if (initLatch.count == 0L) {             return         }           try {             keyStore = KeyStore.getInstance(\"AndroidKeyStore\")             keyStore.load(null)             aesCipher = Cipher.getInstance(AES_CIPHER_SUIT)               if (!hasMasterKey()) {                 createMasterKey()                 masterKeyCreationCallback()             }         } catch (e: Exception) {             exceptionHandler.invoke(EncryptionException(\"Failed to run init\", e))         } finally {             initLatch.countDown()         }     }       override fun encrypt(keyAlias: String, data: ByteArray): EncryptionManager.EncryptedData? {         initLock.read {             checkStateOrThrow()         }           val key = getKey(keyAlias) ?: createKey(keyAlias)         return encryptWithKey(key, data)     }       override fun decrypt(keyAlias: String, data: EncryptionManager.EncryptedData): ByteArray? {         initLock.read {             checkStateOrThrow()         }           val key = getKey(keyAlias) ?: throw EncryptionException(\"No key with alias $keyAlias\")         return decryptWithKey(key, data)     }       override fun removeKey(keyAlias: String) {         keyStorage[keyAlias] = null     }       override fun waitForInitialize(maxTimeMs: Long): Boolean {         return initLatch.await(maxTimeMs, TimeUnit.MILLISECONDS)     }       \/\/ -- Master key operations       private fun checkStateOrThrow() {         if (initLatch.count > 0L) {             throw EncryptionException(\"Manager is not initialized\")         }           if (!hasMasterKey()) {             throw EncryptionException(\"Cannot perform operations without master key\")         }     }       private fun createMasterKey() {         try {             KeyPairGenerator                 .getInstance(\"RSA\", \"AndroidKeyStore\")                 .apply {                     initialize(createSpec())                     generateKeyPair()                 }         } catch (e: Exception) {             throw EncryptionException(\"Failed to generate master key\", e)         }     }       private fun hasMasterKey(): Boolean {         return try {             keyStore.getKey(MASTER_KEY_ALIAS, null) != null         } catch (e: Exception) {             L.w(e, \"Failed to retrieve master key\")             false         }     }       private fun encryptWithMasterKey(data: ByteArray): ByteArray {         return try {             val cipher = Cipher.getInstance(RSA_KEY_SUIT)             val key = keyStore.getCertificate(MASTER_KEY_ALIAS).publicKey             cipher.init(Cipher.ENCRYPT_MODE, key)             cipher.doFinal(data)         } catch (e: Exception) {             throw EncryptionException(\"Failed to encrypt with master key\", e)         }     }       private fun decryptWithMasterKey(data: ByteArray): ByteArray {         return try {             val cipher = Cipher.getInstance(RSA_KEY_SUIT)             val key = keyStore.getKey(MASTER_KEY_ALIAS, null)             cipher.init(Cipher.DECRYPT_MODE, key)             cipher.doFinal(data)         } catch (e: Exception) {             throw EncryptionException(\"Failed to decrypt with master key\", e)         }     }   \/\/ -- AES key operations       private fun getKey(name: String): Key? {         val encryptedKey = keyStorage[name]         if (encryptedKey == null) {             L.i(\"No key with alias $name\")             return null         }           return Key(decryptWithMasterKey(encryptedKey))     }       private fun createKey(name: String): Key {         val key = UUID.randomUUID().toString()             .lowercase()             .replace(\"-\", \"\")             .toCharArray()           val salt = UUID.randomUUID().toByteArray()           val spec = PBEKeySpec(key, salt, PBKDF2_ITER_COUNT, AES_KEY_SIZE)           val skf = SecretKeyFactory.getInstance(PBKDF2_ALGORITHM)         val generatedKey = try {             skf.generateSecret(spec).encoded         } catch (e: Exception) {             throw EncryptionException(\"Failed to generate key\", e)         }           val encryptedKey = encryptWithMasterKey(generatedKey)         keyStorage[name] = encryptedKey           return Key(generatedKey)     }       private fun encryptWithKey(key: Key, data: ByteArray): EncryptionManager.EncryptedData {         return try {             val keySpec = SecretKeySpec(key.encodedKey, AES_KEY_SPEC)             cipherLock.withLock {                 aesCipher.init(Cipher.ENCRYPT_MODE, keySpec)                 val encrypted = aesCipher.doFinal(data)                 EncryptionManager.EncryptedData(encrypted, aesCipher.iv)             }         } catch (e: Exception) {             throw EncryptionException(\"Failed to encrypt with raw aes key\", e)         }     }       private fun decryptWithKey(key: Key, data: EncryptionManager.EncryptedData): ByteArray {         return try {             cipherLock.withLock {                 val keySpec = SecretKeySpec(key.encodedKey, AES_KEY_SPEC)                 aesCipher.init(Cipher.DECRYPT_MODE, keySpec, IvParameterSpec(data.initVector))                 aesCipher.doFinal(data.data)             }         } catch (e: Exception) {             throw EncryptionException(\"Failed to decrypt with aes key\", e)         }     }       private fun createSpec(): AlgorithmParameterSpec {         return KeyGenParameterSpec.Builder(MASTER_KEY_ALIAS, KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT)             .setKeySize(KEY_SIZE)             .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1)             .setAlgorithmParameterSpec(RSAKeyGenParameterSpec(KEY_SIZE, RSAKeyGenParameterSpec.F4))             \/\/ key validity start &amp; end explicitly not set as they caused KeyNotYetValidException @ api 27             .setCertificateSubject(X500Principal(\"CN=$MASTER_KEY_ALIAS\"))             .setCertificateSerialNumber(BigInteger.valueOf(abs(MASTER_KEY_ALIAS.hashCode()).toLong()))             .build()     }       companion object {         private const val MASTER_KEY_ALIAS = \"ALIAS_MASTER_KEY\"           private const val KEY_SIZE = 2048 \/\/ bits         private const val AES_KEY_SIZE = 256         private const val PBKDF2_ITER_COUNT = 10000           private const val AES_CIPHER_SUIT = \"AES\/CBC\/PKCS7Padding\"         private const val AES_KEY_SPEC = \"AES\"         private const val RSA_KEY_SUIT = \"RSA\/NONE\/PKCS1Padding\"         private const val PBKDF2_ALGORITHM = \"PBKDF2WithHmacSHA1\"     } }<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<p>\u041e\u0448\u0438\u0431\u043a\u0438:<\/p>\n<ul>\n<li>\n<p>KeyStoreException: Key not found<\/p>\n<\/li>\n<li>\n<p>KeyStoreException: Unknown error at android.security.KeyStore.getKeyStoreException \u043f\u0440\u0438 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0438 \u0434\u0430\u043d\u043d\u044b\u0445<\/p>\n<\/li>\n<li>\n<p>KeyStoreException: Memory allocation failed at android.security.KeyStore \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f<\/p>\n<\/li>\n<li>\n<p>KeyStoreException: Unsupported digest<\/p>\n<\/li>\n<\/ul>\n<p>\u0415\u0436\u0435\u0434\u043d\u0435\u0432\u043d\u043e \u043c\u044b \u0444\u0438\u043a\u0441\u0438\u0440\u043e\u0432\u0430\u043b\u0438 \u0431\u043e\u043b\u044c\u0448\u0435 150 \u0442\u044b\u0441\u044f\u0447 (!) \u043e\u0448\u0438\u0431\u043e\u043a \u043f\u0440\u0438 \u0440\u0430\u0431\u043e\u0442\u0435 \u0441 \u0448\u0438\u0444\u0440\u043e\u0432\u0430\u043d\u0438\u0435\u043c. \u041f\u0440\u0438 \u044d\u0442\u043e\u043c \u0441\u0430\u043c\u0438 \u043e\u0442\u043b\u043e\u0432\u0438\u0442\u044c \u0438\u0445 \u043d\u0435 \u043c\u043e\u0433\u043b\u0438.<\/p>\n<figure class=\"bordered full-width\"><\/figure>\n<figure class=\"float bordered full-width\"><\/figure>\n<p>\u0421\u0440\u0435\u0434\u043d\u0435\u0435 \u0432\u0440\u0435\u043c\u044f \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u043f\u043e \u043d\u0430\u0448\u0438\u043c \u0434\u0430\u043d\u043d\u044b\u043c: 100 \u043c\u0441, \u0441 \u0443\u0447\u0451\u0442\u043e\u043c \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u0441\u0435\u0441\u0441\u0438\u0439 \u0432 \u0411\u0414. \u0411\u043e\u043b\u044c\u0448\u0435 \u0432\u0441\u0435\u0433\u043e \u0432\u0440\u0435\u043c\u0435\u043d\u0438 \u0437\u0430\u043d\u0438\u043c\u0430\u044e\u0442 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0438 <code>KeyStore.load()<\/code> \u0438 <code>cipher.doFinal()<\/code>. <strong>\u041e\u043d\u0438 \u043c\u043e\u0433\u0443\u0442 \u043f\u0440\u0438\u0432\u043e\u0434\u0438\u0442\u044c \u043a ANR \u043d\u0430 \u043d\u0435\u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430\u0445<\/strong>.<\/p>\n<h3>\u0412\u0442\u043e\u0440\u043e\u0439 \u0441\u043f\u043e\u0441\u043e\u0431: EncryptedFile<\/h3>\n<p>\u041e\u0447\u0435\u043d\u044c \u043f\u0440\u043e\u0441\u0442\u043e\u0439 \u0432 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u0441\u043f\u043e\u0441\u043e\u0431, \u0440\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0443\u0435\u043c\u044b\u0439 Google:<\/p>\n<details class=\"spoiler\">\n<summary>EncryptedFileWrapper.kt<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"kotlin\">class EncryptedFileWrapper(     context: Context,     fileName: String,     private val sessionStatHelper: SessionStatHelper ) {     private val lock = Any()          private val encryptedFileLink by lazy(mode = LazyThreadSafetyMode.NONE) {         File(context.filesDir, \"encrypted_$fileName\")     }     private val encryptedFile: EncryptedFile by lazy(mode = LazyThreadSafetyMode.NONE) {         try {             EncryptedFile.Builder(                 encryptedFileLink,                 context,                 MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC),      <\/code><\/pre>\n<\/div>\n<\/details>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-361334","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/361334","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=361334"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/361334\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=361334"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=361334"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=361334"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}