Неверные параметры для алгоритма генерации ключа
Описание
Использование любых данных пользователя в качестве ключа шифрования — это достаточно распространенная, но очень плохая практика. Как правило, таким значениям недостает длины или случайности, поэтому специально для таких случаев предусмотрели процедуру получения ключа шифрования из данных пользователя и называется она «Процедура расширения ключа» или «Функция определения ключа на основе пароля». Рассмотрим мы ее на примере одной из функций под названием PBKDF2 (Password-Based Key Derivation Function):
- PBKDF2 выполняет функцию усиления в несколько итераций для получения ключа. Обычно это около 10 тысяч итераций.
- Рост количества итераций увеличивает время, необходимое для успешной атаки с использованием полного перебора (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 {
/*
https://security.stackexchange.com/a/47188/79148
We use PBKDF2 because android doesn't natively support bcrypt, scrypt, or argon2i.
Additionally, keep in mind that this will only provide 20 bytes of security instead
of 32 bytes because we're using HmacSHA1. Android doesn't support HmacSHA256.
You can use SpongyCastle/BouncyCastle to include support for PBKDF2-HmacSHA256.
*/
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
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, но такой вариант маловероятен). В этом случае, каков бы ни был объем данных, которые нужно сохранить в секрете, время повторного шифрования всегда будет одинаковым, так как сами зашифрованные данные мы не трогаем.
Дополнительные рекомендации
-
Используйте процедуры расширения ключа для усиления паролей: Для преобразования пользовательских данных в надёжный ключ используйте PBKDF2 или аналогичные функции расширения ключа, такие как scrypt или bcrypt. Эти функции обеспечивают дополнительное усиление путём многократного применения хеширования и добавления случайной соли.
-
Выбирайте достаточное количество итераций: Чем больше итераций выполняет функция расширения ключа, тем более устойчивым к атакам перебором будет полученный ключ. Рекомендуется использовать не менее 10 тысяч итераций, однако оптимальное количество зависит от требований безопасности и возможностей устройства.
-
Используйте случайные значения соли (salt): Для каждой операции генерации ключа используйте случайное значение соли. Это добавляет дополнительную случайность и защищает от атак с использованием радужных таблиц.
Пример генерации соли:
-
Используйте современные алгоритмы расширения ключа: Помимо PBKDF2, рассмотрите использование более современных алгоритмов, таких как scrypt или Argon2, которые предлагают дополнительные уровни защиты, включая защиту от атак с использованием специализированного оборудования.
Ссылки
- https://www.baeldung.com/java-encryption-iv
- https://proandroiddev.com/secure-data-in-android-initialization-vector-6ca1c659762c
- https://medium.com/beautycoder/android-security-and-fingerprint-ef0f6f344888
- https://developer.android.com/training/articles/keystore
- https://medium.com/@josiassena/using-the-android-keystore-system-to-store-sensitive-information-3a56175a454b
- https://habr.com/ru/company/swordfish_security/blog/658433/
- https://habr.com/ru/company/swordfish_security/blog/664720/
- https://github.com/d0nutptr/Android-Security-Examples/blob/master/Cryptography/app/src/main/java/com/iismathwizard/cryptonote/Crypto.java
- https://github.com/flast101/padding-oracle-attack-explained/blob/master/oracle.py
- https://habr.com/ru/post/247527/?ysclid=l9wqtx7li4787340116
- https://en.wikipedia.org/wiki/Padding_oracle_attack