Подтвердить что ты не робот

Ухудшение производительности BCrypt

У нас есть три веб-приложения (стандартный Spring MVC-Hibernate), работающий на сервере Jboss 6.1. Все три приложения имеют общий метод проверки подлинности, который скомпилирован как JAR и включен в каждый файл WAR. Наш метод аутентификации использует org.springframework.security.crypto.bcrypt.BCrypt для пользовательских паролей хэша, см. Ниже:

hashedPassword.equals(BCrypt.hashpw(plainTextPassword, salt));

Параметры запуска JBOSS

set "JAVA_OPTS=-Xms2048m -Xmx4096m -XX:PermSize=256m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -verbosegc -XX:+PrintGCTimeStamps -XX:+PrintGCDetails -Xloggc:gc.txt -XX:+UseParallelOldGC

Проблема: Похоже, что когда сервер перезагружается, Bcrypt.hashpw занимает 100 мс для дешифрования пароля. Однако через некоторое время (нет рисунка) внезапно производительность Bcrypt.hashpw возрастает от 100 мс до 10 секунд. Для этого нет очевидной причины.

Дополнительная информация:

  • Hibernate Version: 4.2.4.Final
  • Spring Версия: 4.0.5.RELEASE Spring
  • Версия безопасности: 3.2.4.RELEASE

Кто-нибудь еще видел эту проблему раньше?

4b9b3361

Ответ 1

Проблема. Похоже, что при перезапуске сервера Bcrypt.hashpw принимает 100 мс для дешифрования пароля. Однако через некоторое время (нет рисунка) внезапно производительность Bcrypt.hashpw возрастает от 100 мс до 10 секунд. Для этого нет очевидной причины.

Проблема в том, что /dev/random иногда блокируется, и когда она это делает, она будет казаться случайной:). Чем более запутанной является то, что при попытке проверить, как она работает, вы столкнетесь с эффектом наблюдателя, то есть, пытаясь наблюдать случайное поведение, которое вы генерируете энтропией, и это может привести к путанице, т.е. мои результаты не будут такими же, как ваши и т.д. Это также объясняет, почему это не выглядит.

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

В Linux /dev/random - поток случайных байтов. Когда вы читаете поток вы исчерпаете доступную энтропию. Когда он достигает определенной точки читается из блока /dev/random. Вы можете увидеть доступную энтропию, используя эту команду

cat /proc/sys/kernel/random/entropy_avail

Если вы запустите следующий bash script, а также монитор entropy_avail, вы обратите внимание, что энтропия резко падает, поскольку bash script потребляет ее.

while :
do
  cat /dev/random > /dev/null
done

Это также должно дать вам подсказку о том, как воссоздать эту проблему на ваших серверах, т.е. запустить выше bash script, чтобы уменьшить доступную энтропию, и проблема проявится.

Если вы хотите увидеть, сколько байтов в секунду ваша система создает вас может использовать pv для его измерения i.e.

pv /dev/random

Если вы покидаете pv, это имеет эффект, он потребляет случайный поток байтов, что означает, что другие службы могут начать блокироваться. Обратите внимание, что pv также отображает его вывод, чтобы он мог также увеличивать доступность в системе:).

В системах с небольшой энтропией или без энтропии с использованием pv /dev/random будет казаться медленным. Я также испытал, что у VM иногда возникают серьезные проблемы с генерацией энтропии.

Чтобы воссоздать проблему, используйте следующий класс...

import java.security.SecureRandom;
import org.mindrot.jbcrypt.BCrypt;
public class RandTest {
    public static void main(String[] args) {
        SecureRandom sr = new SecureRandom();
        int out = 0;
        String password = "very-strong-password-1729";
        String hashed;
        for (int i = 0; i < 200000 ; i++) {
            hashed = BCrypt.hashpw(password, BCrypt.gensalt());
            //If we print, we're generating entroy :) System.out.println(hashed);
        }
    }
}

Я загрузил bcrypt в локальный каталог. Я скомпилировал и выполнил его следующим образом

javac -cp ./jBCrypt-0.4/src/   RandTest.java
java  -cp ./jBCrypt-0.4/src/:. RandTest

Если вы затем запустите bash script раньше, а runnng RandTest, вы увидите большие паузы, в которых система блокирует ожидание большей энтропии. Если вы запустите strace, вы увидите следующее...

1067 [pid 22481] open("/dev/random", O_RDONLY|O_LARGEFILE) = 12
11068 [pid 22481] fstat64(12, {st_mode=S_IFCHR|0666, st_rdev=makedev(1, 8), ...}) = 0
11069 [pid 22481] fcntl64(12, F_GETFD)        = 0
11070 [pid 22481] fcntl64(12, F_SETFD, FD_CLOEXEC) = 0
.....
11510 [pid 22481] read(12, "\320\244\317RB\370", 8) = 6

Программа читает от /dev/random. Проблема с энтропией тестирования вы можете генерировать больше его, пытаясь его протестировать, т.е. эффект наблюдателя.

затруднительных

Первое исправление заключается в изменении от /dev/random до /dev/urandom, т.е.

time java  -Djava.security.egd=file:///dev/./urandom -cp ./jBCrypt-0.4/src/:.  RandTest

Альтернативным решением является воссоздание устройства /dev/random в качестве устройства /dev/urandom. Вы можете найти, как это сделать на странице руководства, т.е. Вместо их создания...

mknod -m 644 /dev/random c 1 8
mknod -m 644 /dev/urandom c 1 9
chown root:root /dev/random /dev/urandom

мы удаляем один и подделываем его, т.е.

rm /dev/random
mknod -m 644 /dev/random c 1 9
chown root:root /dev/random

/dev/random теперь фактически /dev/urandom

Ключевая вещь, которую следует помнить, - это тестирование случайных данных, требующих система, которую вы тестируете, затруднена из-за эффекта Observer.

Ответ 2

Одно из возможных объяснений состоит в том, что SeedGenerator of SecureRandom вызывает задержки.

Функция Springs BCrypt использует SecureRandom, которая, в свою очередь, использует SeedGenerator, которая, в свою очередь, может использовать блокировку /dev/random. Здесь - хорошее описание этих классов.

Что bugreport также сообщает о проблемах производительности в BCrypt и прослеживает их обратно к генератору семян, показывая полные стеки. Реализация BCrypt отличается, но stacktrace ниже SecureRandom должна быть идентичной реализации spring. Их решение заключалось в уменьшении частоты повторения BCrypt.