WildFly: случайно соленые пароли в приложении Java EE

Что такое способ WildFly (8.2) для работы со случайно соленными паролями, хранящимися в базе данных?

Является ли реализация org.jboss.crypto.digest.DigestCallback (в процессе проверки пароля), предназначенной для доступа к солевой части из базы данных?

Или я должен просто использовать хэш и солевые пароли, прежде чем передать их методу login HttpServletRequest?

Ответ 1

Мне кажется, что "способ WildFly" справиться с паролями - это делать то, что делают большинство контейнеров, и поставлять незащищенное решение из коробки. Я не знаю, почему, но каждая стандартная реализация JDBC, которую я видел до сих пор, просто хэширует пароли без соли... Что совершенно небезопасно.

Решение с открытым исходным кодом

EDIT: я нашел решение, которое работает на WildFly. Я закончил тем, что использовал его сам, и он работает хорошо. Я могу порекомендовать его:

m9aertner/PBKDF2

Вот как я его настроил:

Сначала добавьте модуль в WildFly, создав папку ниже modules/, например:

C:\WildFly\v8.2.0\modules\de\rtner\PBKDF2\main

Поместите в него PBKDF2-1.1.0.jar файл вместе с module.xml с этим содержимым:

<?xml version="1.0" encoding="UTF-8"?>
<module xmlns="urn:jboss:module:1.1" name="de.rtner.PBKDF2">
  <resources>
    <resource-root path="PBKDF2-1.1.0.jar"/>
  </resources>
  <dependencies>
    <module name="org.picketbox"/>
    <module name="javax.api"/>
  </dependencies>
</module>`

Затем добавьте конфигурацию царства в standalone.xml:

<subsystem xmlns="urn:jboss:domain:security:1.2">
  <security-domains>
    <!-- .... -->

    <security-domain name="MyRealm">
      <authentication>
        <login-module code="de.rtner.security.auth.spi.SaltedDatabaseServerLoginModule" flag="required" module="de.rtner.PBKDF2">
          <module-option name="dsJndiName" value="java:/jdbc/MyDS"/>
          <module-option name="principalsQuery" value="SELECT password FROM users WHERE username = ?"/>
          <module-option name="rolesQuery" value="SELECT roles.name AS groupid, 'Roles' FROM roles INNER JOIN user_roles ON roles.name = users.username WHERE users.username = ?"/>
          <module-option name="unauthenticatedIdentity" value="guest"/>
          <!-- DEFAULT HASHING OPTIONS:
          <module-option name="hmacAlgorithm" value="HMacSHA1" />
          <module-option name="hashCharset" value="UTF-8" />
          <module-option name="formatter" value="de.rtner.security.auth.spi.PBKDF2HexFormatter" />
          <module-option name="engine" value="de.rtner.security.auth.spi.PBKDF2Engine" />
          <module-option name="engine-parameters" value="de.rtner.security.auth.spi.PBKDF2Parameters" />
          -->
        </login-module>
      </authentication>
    </security-domain>

    <!-- .... -->
  </security-domains>
</subsystem>

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

Пример использования

Вот что я делаю в своем коде для создания нового хэша пароля (включая соль) на основе заданного открытого текста:

public static String hash(String plainText, String storedPassword) {
    if (plainText == null) return null;
    SimplePBKDF2 crypto = new SimplePBKDF2();
    PBKDF2Parameters params = crypto.getParameters();
    params.setHashCharset("UTF-8");
    params.setHashAlgorithm("HmacSHA1");
    params.setIterationCount(1000);
    if (storedPassword != null) {
        new PBKDF2HexFormatter().fromString(params, storedPassword);
    }
    return crypto.deriveKeyFormatted(plainText);
}

При создании нового пароля вы вызываете эту функцию, передавая null как storedPassword:

String password = hash('MySecretPassword', null);

password будет выглядеть примерно так:

"192EAEB3B7AA40B1:1000:4C137AF7AD0F3999D18E2B9E6FB726D5C07DE7D5"

При сравнении паролей вы вызываете одну и ту же функцию, передавая исходный пароль, а затем сравниваете результаты:

String enteredPassword = hash(userInput, password);
if (enteredPassword.equals(password)) {
    // Ok!
}

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

Или сверните свой собственный

Это сообщение в блоге дает некоторое объяснение о том, как свернуть свою собственную реализацию Realm, которая добавляет соль. У него исходный код на GitHub, поэтому, возможно, используйте это.

Это для Glassfish, но я думаю, что это не имеет значения, насколько реализуется код реализации Realm.