Хранение чувствительной информации в незащищённой базе данных
|  | Критичность: НИЗКИЙ | 
| Способ обнаружения: DAST, DATA BASES | 
Описание
Приложение хранит чувствительную информацию в незащищенной базе данных, что может повлечь компрометацию данных.
Несмотря на то, что файл хранится внутри директории приложения, не стоит хранить в нем чувствительную информацию. Получить эту информацию можно разными способами, начиная локальным или облачным бекапом и заканчивая различными уязвимостями чтения файлов и инъекций в Content Provider.
Чтобы понять, какие именно данные необходимо защищать, прежде всего необходимо определить, какие данные обрабатывает и хранит приложение, и какая часть из этой информации считается конфиденциальной. Как правило, в таких случаях полагаются на законодательство и здравый смысл. Нет смысла защищать шифрованием абсолютно всю информацию, которую хранит приложение — это может повлиять на скорость и стабильность работы. Вместо этого следует однозначно определить, какие именно данные являются конфиденциальными для вашего приложения или компании и сосредоточить свое внимание именно на них.
Рекомендации
В случае необходимости хранения чувствительной информации в базе данных нужно дополнительно шифровать итоговую базу данных или данные, которые в ней хранятся. В качестве примера с шифрованием базы данных можно воспользоваться библиотекой sqlcipher.
Пример использования SQLCipher (Java):
package com.demo.sqlcipher;
import java.io.File;
import net.sqlcipher.database.SQLiteDatabase;
import android.app.Activity;
import android.os.Bundle;
public class HelloSQLCipherActivity extends Activity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        InitializeSQLCipher();
    }
    private void InitializeSQLCipher() {
        SQLiteDatabase.loadLibs(this);
        File databaseFile = getDatabasePath("demo.db");
        databaseFile.mkdirs();
        databaseFile.delete();
        SQLiteDatabase database = SQLiteDatabase.openOrCreateDatabase(databaseFile, "test123", null);
        database.execSQL("create table t1(a, b)");
        database.execSQL("insert into t1(a, b) values(?, ?)", new Object[]{"one for the money",
                                                                        "two for the show"});
    }
}
Пример использования SQLCipher (Kotlin):
package com.demo.sqlcipher
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import net.sqlcipher.database.SQLiteDatabase
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        SQLiteDatabase.loadLibs(this)
        val databaseFile = getDatabasePath("demo.db")
        if(databaseFile.exists()) databaseFile.delete()
        databaseFile.mkdirs()
        databaseFile.delete()
        val database = SQLiteDatabase.openOrCreateDatabase(databaseFile, "test123", null)
        database.execSQL("create table t1(a, b)")
        database.execSQL("insert into t1(a, b) values(?, ?)",
            arrayOf<Any>("one for the money", "two for the show")
        )
    }
}
В данном примере использован «захардкоженный» пароль для базы данных «test123». В реальном приложении не стоит использовать настолько ненадежный пароль и хранить его в исходном коде или в открытом виде.
В качестве способа без хранения пароля — вычисление его «на лету» с использованием пароля пользователя при помощи процедуры усиления ключа.
Правила:
- Явно определяйте режим шифрования и дополнения блоков.
- Используйте криптостойкие технологии шифрования, включающие алгоритм, режим блочного шифрования и режим дополнения блоков.
- В процессе генерации ключа на основе пароля используйте «соль» (salt).
- В процессе генерации ключа на основе пароля используйте достаточное количество итераций хеширования.
- Используйте ключ с длиной, которая обеспечит криптостойкость шифрования.import android.os.Build package com.appsec.android.cryptsymmetricpasswordbasedkey; import javax.crypto.SecretKeyFactory import javax.crypto.spec.PBEKeySpec @Deprecated("Use Argon2 instead") internal object Pbkdf2Factory { private const val DEFAULT_ITERATIONS = 10_000 private val systemAlgorithm by lazy { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { "PBKDF2withHmacSHA1" } else { "PBKDF2withHmacSHA256" } } fun createKey( passphraseOrPin: CharArray, salt: ByteArray, algorithm: String = systemAlgorithm, iterations: Int = DEFAULT_ITERATIONS ): Pbkdf2Key { @Suppress("MagicNumber") val keySpec = PBEKeySpec(passphraseOrPin, salt, iterations, 256) val secretKey = SecretKeyFactory.getInstance(algorithm).generateSecret(keySpec) return Pbkdf2Key( secretKey.algorithm, iterations, salt, secretKey.encoded ) } }
В дальнейшем получившееся значение ключа можно использовать в качестве пароля для шифрования базы данных и нет необходимости в его хранении, так как каждый раз при вводе пароля он буде вычисляться на лету и передаваться в функцию открытия БД.
Важно!
При таком подходе при изменении секрета (пароля пользователя) данные необходимо перешифровать с новым секретом, если есть необходимость в их сохранении. Также если пароль пользователя представляет из себя пин-код, то лучше использовать подход KEK(Key Encryption Key) + DEK(Data Encryption Key), при котором создается ключ для шифрования данных и он дополнительно шифруется на пароле пользователя. При таком подходе в случае изменения секрета необходимо будет только перешифровать ключ и не трогать зашифрованные данные пользователя.
Примечание
При подключении библиотеки SQLCipher не забудьте добавить правила в Proguard для коррекной работы приложения.
Правила для proguard:
-keep,includedescriptorclasses class net.sqlcipher.** { *; }
-keep,includedescriptorclasses interface net.sqlcipher.** { *; }