Генерация хэша с использованием Java-дайджеста становится медленнее
У меня действительно любопытная ситуация в рабочей среде с использованием wildfly 8.2 и Java 1.7.
Ситуация такова, что когда сервер работает более 2 недель, логин начинает снижать производительность. Я искал подсказки, которые могут указать, где проблема. Затем, проведя некоторое тестирование, я пришел к выводу, что проблема заключается в том, что пароль, вставленный в виде простого текста, зашифрован для сравнения с уже введенным.
Когда выполняется функция, которая шифрует пароль, это занимает почти 2 минуты, но при перезапуске сервера такое же выполнение занимает менее 30 секунд.
Шифрование использует java.security.MessageDigest для генерации хеша. В частности, с использованием SHA-256 с 50000 итерациями. Есть идеи, почему этот процесс может замедлиться со временем? Я использую /dev/urandom для генерации random, так что проблем не должно быть.
Вот код функции:
protected byte[] hash(byte[] bytes, byte[] salt, int hashIterations) throws UnknownAlgorithmException {
MessageDigest digest = getDigest(getAlgorithmName());
if (salt != null) {
digest.reset();
digest.update(salt);
}
byte[] hashed = digest.digest(bytes);
int iterations = hashIterations - 1; //already hashed once above
//iterate remaining number:
for (int i = 0; i < iterations; i++) {
digest.reset();
hashed = digest.digest(hashed);
}
return hashed;
}
3 ответа
После нескольких дней исследований я наконец нашел ответ на свой вопрос. Я хочу поделиться этим здесь в случае, если это будет полезно для кого-то еще.
Проблема была вызвана из-за кеш-памяти кода. Я сосредоточился на куче памяти и не увидел никаких проблем, но когда я проверял не кучу памяти, я обнаружил, что как только процесс входа в систему начал замедляться, в кеше кода упало более половины используемая память
Исследуя эту память, я обнаружил, что, когда в этом пространстве большие капли, может случиться так, что JIT-компилятор перестанет работать. В заключение, это было то, что происходило, и отключение JIT-компилятора приводило к тому, что каждая итерация моего цикла шифрования должна интерпретироваться при каждом выполнении, что логически делало процесс намного медленнее.
Здесь я оставляю некоторые ссылки, которые я считаю полезными в этой теме.
[2] - https://www.atlassian.com/blog/archives/codecache-is-full-compiler-has-been-disabled
Спасибо тем, кто нашел время, чтобы ответить на это в любом случае
Избавьтесь от утверждения: int iterations = hashIterations - 1;
и просто использовать hashIterations
,
В лучшем случае он сокращает итерации с 50000 (в указанном случае) до 49999, а в худшем случае вызывает целочисленное переполнение и увеличивает итерации до максимального значения int
,
На минимальном уровне защиты от вычитания 1
когда hashIterations
это ноль.
Также рассмотрите инструментарий для отладки, записав значение iterations
,
Почему кто-то закроет это??? Возможно, потому что там нет ничего, что могло бы вызвать проблему.
В то время как digest.digest
обычно это трудоемкая часть, это чистые вычисления, и нет ничего, что могло бы их замедлить. Так что остается getAlgorithmName()
а также getDigest(String)
, Первый, вероятно, тривиальный добытчик, но последний, вероятно, использует MessageDigest.getInstance
который находит дайджест. Просто догадываюсь: есть поиск по всем провайдерам безопасности и всему, что они предоставляют, и кто-то может как-то удлинить этот список.
Вы можете сортировать эталонные тесты этого метода библиотеки даже в производственном процессе: просто скопируйте метод в новый исходный файл и добавьте некоторое количество журналов и некоторый код, вызывающий его периодически (или вручную, если хотите). Когда произойдет замедление, у вас будет с чем сравнить, и вы найдете некоторые подробные хронометражи в своих журналах.
Когда все мыслимые причины исчерпаны, попробуйте невообразимые, например, различные iterations
(который вы считаете постоянным) и т. д.