У меня есть старое приложение на основе Symfony2, и я разрабатываю замену Dropwizard на Java.
Я перенес все пользовательские записи из старой БД в мою новую модель данных.
Я также добавил новые поля для паролей и импортировал старые поля паролей и соли.
Теперь я хочу сделать хорошо известную процедуру. Позвольте пользователю войти в систему, попробуйте ввести новое поле пароля. Если это не удается, попробуйте перенастроенные, если они работают, закодируйте пароль в виде открытого текста с новым алгоритмом и сохраните новый хеш в поле нового пароля. Так что пользователи переносят туда хеши паролей от старой процедуры к новой.
Звучит просто и нормально, это работает как обычно, но этот Symfony и PHP сводят меня с ума.
Я застрял в том же хеше с Java, что и Symfony.
Старое приложение использует MessageDigestPasswordEncoder с «sha512», кодировкой base64 и 5000 итераций, все по умолчанию;)
Важными методами являются:
MessageDigestPasswordEncoder:
public function encodePassword($raw, $salt) {
if ($this->isPasswordTooLong($raw)) {
throw new BadCredentialsException('Invalid password.');
}
if (!in_array($this->algorithm, hash_algos(), true)) {
throw new \LogicException(sprintf('The algorithm "%s" is not supported.', $this->algorithm));
}
$salted = $this->mergePasswordAndSalt($raw, $salt);
$digest = hash($this->algorithm, $salted, true);
// "stretch" hash
for ($i = 1; $i < $this->iterations; ++$i) {
$digest = hash($this->algorithm, $digest.$salted, true);
}
return $this->encodeHashAsBase64 ? base64_encode($digest) : bin2hex($digest);
}
И BasePasswordEncoder:
protected function mergePasswordAndSalt($password, $salt) {
if (empty($salt)) {
return $password;
}
if (false !== strrpos($salt, '{') || false !== strrpos($salt, '}')) {
throw new \InvalidArgumentException('Cannot use { or } in salt.');
}
return $password.'{'.$salt.'}';
}
Это кажется прямым, но я застрял с этим.
Когда я читаю это, это делает:
Итак, вот моя попытка в Java:
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.slf4j.Logger;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
public void legacyEncryption(String salt, String clearPassword) throws UnsupportedEncodingException, NoSuchAlgorithmException {
// Get digester instance for algorithm "SHA-512" using BounceCastle
MessageDigest digester = MessageDigest.getInstance("SHA-512", new BouncyCastleProvider());
// Create salted password string
String mergedPasswordAndSalt = clearPassword + "{" + salt + "}";
// First time hash the input string by using UTF-8 encoded bytes.
byte[] hash = digester.digest(mergedPasswordAndSalt.getBytes("UTF-8"));
// Loop 5k times
for (int i = 0; i < 5000; i++) {
// Concatenate the hash bytes with the clearPassword bytes and rehash
hash = digester.digest(ArrayUtils.addAll(hash, mergedPasswordAndSalt.getBytes("UTF-8")));
}
// Log the resulting hash as base64 String
logger.info("Legace password digest: salt=" + salt + " hash=" + Base64.getEncoder().encodeToString(hash));
}
Кто-нибудь видит проблему? Я думаю, что разница в результате:
PHP: двоичный.
и
JAVA: addAll (byte [], byte [])
заранее спасибо
Реализация на стороне php правильно делает 5 000 итераций, выполняя первый раунд хэширования, а затем цикл 4999 раз.
$digest = hash($this->algorithm, $salted, true);
for ($i = 1; $i < $this->iterations; ++$i) {
$digest = hash($this->algorithm, $digest.$salted, true);
}
В реализации Java цикл for начинается с 0, что приводит к 5k + 1 итерации.
При запуске цикла for в 1 также в java результирующие хеши паролей равны.
byte[] hash = digester.digest(mergedPasswordAndSalt.getBytes("UTF-8"));
for (int i = 0; i < 5000; i++) {
hash = digester.digest(ArrayUtils.addAll(hash, mergedPasswordAndSalt.getBytes("UTF-8")));
}
Других решений пока нет …