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

Неверные параметры для алгоритма генерации ключа

Критичность: СРЕДНИЙ
Способ обнаружения: DAST

Описание

Использование любых данных пользователя в качестве ключа шифрования — это достаточно распространенная, но очень плохая практика. Как правило, таким значениям недостает длины или случайности, поэтому специально для таких случаев предусмотрели процедуру получения ключа шифрования из данных пользователя и называется она «Процедура расширения ключа» или «Функция определения ключа на основе пароля». Рассмотрим мы ее на примере одной из функций под названием PBKDF2 (Password-Based Key Derivation Function):

  • PBKDF2 выполняет функцию усиления в несколько итераций для получения ключа. Актуальные рекомендации OWASP — не менее 600 000 итераций для PBKDF2-HMAC-SHA256 (число подбирается с учётом производительности устройства).
  • Рост количества итераций увеличивает время, необходимое для успешной атаки с использованием полного перебора (brute force).
/**
* Creates a hash in base64 of the user's password using pbkdf2 since we have no nice alternatives like bcrypt.
* @param password user's password
* @return a bas64 representation of the pbkdf2 hash
* @throws GeneralSecurityException
*/
public static String generateUserHash(String password) throws GeneralSecurityException {
    byte[] salt = new byte[HASH_SALT_SIZE];
    (new SecureRandom()).nextBytes(salt);

    byte[] hash = generateUserHash(password, salt);

    byte[] output = new byte[HASH_SALT_SIZE + HASH_OUTPUT_SIZE];

    System.arraycopy(salt, 0, output, 0, salt.length);
    System.arraycopy(hash, 0, output, salt.length, HASH_OUTPUT_SIZE);

    return Base64.encodeToString(output, Base64.DEFAULT);
}

/**
* Creates a hash in bytes of the user's password.
* @param password the user's password
* @param salt the salt to use when creating the hash
* @return the pbkdf2 hash of the user's password in bytes
* @throws GeneralSecurityException
*/
public static byte[] generateUserHash(String password, byte[] salt) throws GeneralSecurityException {
/*
Используем PBKDF2 с HMAC-SHA256: алгоритм PBKDF2withHmacSHA256 поддерживается
платформой начиная с Android 8.0 (API 26) штатным провайдером, без
SpongyCastle/BouncyCastle. Длину вывода (HASH_OUTPUT_SIZE) задавайте 256 бит.
HASH_ITERATIONS — не менее 600 000 (рекомендация OWASP), подбирается с учётом
производительности целевых устройств.
*/
    SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2withHmacSHA256");
    PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, HASH_ITERATIONS, HASH_OUTPUT_SIZE * 8);
    SecretKey hash = factory.generateSecret(keySpec);

    return hash.getEncoded();
}

В итоге мы получим достаточно адекватный ключ шифрования, удовлетворяющий всем необходимым параметрам (длина, случайность). И так как мы используем данные, которые вводит пользователь, можем не хранить этот ключ постоянно, а генерировать его налету каждый раз, когда нам необходимо зашифровать или расшифровать данные.

Чтобы обработать ситуацию, когда мы используем пароль пользователя в процедуре расширения ключа и шифрования данных, а пользователь внезапно решил сменить пароль, возможно применение алгоритма KEK&DEK (Key Encryption Key & Data Encryption Key). При таком подходе мы генерируем ключ шифрования для данных при помощи SecureRandom, шифруем на нем нужные нам данные, а после для безопасного хранения этого ключа используем процедуру расширения на основе пароля пользователя и получившимся ключом шифруем ключ шифрования данных. Схематично это выглядит следующим образом.

Примерная схема с использованием двух ключей

При таком подходе мы сначала создаем ключ для шифрования данных (Data Encryption Key). Затем на нем зашифровываем данные и уже этот ключ шифруем при помощи нового ключа (Key Encryption Key). Как раз этот KEK можно либо сохранить в Keystore, либо генерировать каждый раз (например, на основе пароля пользователя). При таком механизме мы избавляемся от повторной зашифровки данных в случае изменения/компрометации ключа. Нам достаточно повторно зашифровать только DEK и не трогать данные (конечно, это не так, если у нас скомпрометирован DEK, но такой вариант маловероятен). В этом случае, каков бы ни был объем данных, которые нужно сохранить в секрете, время повторного шифрования всегда будет одинаковым, так как сами зашифрованные данные мы не трогаем.

Дополнительные рекомендации

  1. Используйте процедуры расширения ключа для усиления паролей: Для преобразования пользовательских данных в надёжный ключ используйте PBKDF2 или аналогичные функции расширения ключа, такие как scrypt или bcrypt. Эти функции обеспечивают дополнительное усиление путём многократного применения хеширования и добавления случайной соли.

  2. Выбирайте достаточное количество итераций: Чем больше итераций выполняет функция расширения ключа, тем более устойчивым к атакам перебором будет полученный ключ. Для PBKDF2-HMAC-SHA256 OWASP рекомендует не менее 600 000 итераций (для PBKDF2-HMAC-SHA512 — не менее 210 000). Конкретное значение подбирайте по производительности целевых устройств, не опускаясь ниже указанного минимума.

  3. Используйте случайные значения соли (salt): Для каждой операции генерации ключа используйте случайное значение соли. Это добавляет дополнительную случайность и защищает от атак с использованием радужных таблиц.

    Пример генерации соли:

    byte[] salt = new byte[16];
    SecureRandom secureRandom = new SecureRandom();
    secureRandom.nextBytes(salt);
    

  4. Используйте современные алгоритмы расширения ключа: Помимо PBKDF2, рассмотрите использование более современных алгоритмов, таких как scrypt или Argon2, которые предлагают дополнительные уровни защиты, включая защиту от атак с использованием специализированного оборудования.

Ссылки

  1. OWASP Password Storage Cheat Sheet (рекомендации по итерациям PBKDF2)
  2. https://www.baeldung.com/java-encryption-iv
  3. https://proandroiddev.com/secure-data-in-android-initialization-vector-6ca1c659762c
  4. https://medium.com/beautycoder/android-security-and-fingerprint-ef0f6f344888
  5. https://developer.android.com/training/articles/keystore
  6. https://medium.com/@josiassena/using-the-android-keystore-system-to-store-sensitive-information-3a56175a454b
  7. https://habr.com/ru/company/swordfish_security/blog/658433/
  8. https://habr.com/ru/company/swordfish_security/blog/664720/
  9. https://github.com/d0nutptr/Android-Security-Examples/blob/master/Cryptography/app/src/main/java/com/iismathwizard/cryptonote/Crypto.java
  10. https://github.com/flast101/padding-oracle-attack-explained/blob/master/oracle.py
  11. https://habr.com/ru/post/247527/?ysclid=l9wqtx7li4787340116
  12. https://en.wikipedia.org/wiki/Padding_oracle_attack
К началу