Перейти к содержанию

Хранение чувствительной информации в незащищённой базе данных

Критичность: НИЗКИЙ
Способ обнаружения: 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». В реальном приложении не стоит использовать настолько ненадежный пароль и хранить его в исходном коде или в открытом виде.

В качестве способа без хранения пароля — вычисление его «на лету» с использованием пароля пользователя при помощи процедуры усиления ключа.

Правила:

  1. Явно определяйте режим шифрования и дополнения блоков.
  2. Используйте криптостойкие технологии шифрования, включающие алгоритм, режим блочного шифрования и режим дополнения блоков.
  3. В процессе генерации ключа на основе пароля используйте «соль» (salt).
  4. В процессе генерации ключа на основе пароля используйте достаточное количество итераций хеширования.
  5. Используйте ключ с длиной, которая обеспечит криптостойкость шифрования.
    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.** { *; }

Ссылки

  1. GitHub—sqlcipher/android-database-sqlcipher: Android SQLite API based on SQLCipher
  2. owasp-mstg/0x05d-Testing-Data-Storage.md at master · OWASP/owasp-mstg
  3. CWE—CWE-521: Weak Password Requirements (4.6) What is a Data Encryption Key (DEK)
  4. Definition from Techopedia
К началу