Экспорт хешей паролей в формате PHC

Текущая информация о системе хранения паролей Discourse доступна по адресу discourse/docs/SECURITY.md at main · discourse/discourse · GitHub. На момент написания мы используем PBKDF2-SHA256 с 600 000 итерациями.

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

Данные паролей хранятся в таблице user_passwords, которая содержит столбцы password_hash, password_salt и password_algorithm. Столбец password_algorithm хранит полный префикс алгоритма PHC (например, $pbkdf2-sha256$i=600000,l=32$), который может различаться для разных пользователей, если количество итераций со временем увеличивалось.

Вы можете использовать Data Explorer для экспорта информации в машиночитаемом формате:

SELECT users.id, users.username, up.password_salt, up.password_hash, up.password_algorithm
FROM users
INNER JOIN user_passwords up ON users.id = up.user_id
WHERE users.id > 0

Это экспортирует данные в нативном формате Discourse. Соль закодирована в шестнадцатеричном виде, хеш пароля также закодирован в шестнадцатеричном виде.

Некоторые внешние системы поддерживают формат строк PHC, который представляет собой универсальный способ представления результата хеширования паролей для различных алгоритмов. Для pbkdf2-sha256 такая строка содержит тип алгоритма, количество итераций, соль в base64 и хеш в base64. К счастью, Postgres может обработать всё это за один запрос.

Чтобы сгенерировать строки PHC для каждого пользователя Discourse, вы можете использовать запрос Data Explorer следующего вида:

SELECT users.id, users.username,
  concat(
    up.password_algorithm,
    replace(encode(up.password_salt::bytea, 'base64'), '=', ''),
    '$',
    replace(encode(decode(up.password_hash, 'hex'), 'base64'), '=', '')
  ) as phc
FROM users
INNER JOIN user_passwords up ON users.id = up.user_id
WHERE users.id > 0

Если вы используете Auth0, используйте следующий запрос:

SELECT
    user_emails.email,
    users.active as email_verified,
    concat(
        up.password_algorithm,
        replace(encode(up.password_salt::bytea, 'base64'), '=', ''),
        '$',
        replace(encode(decode(up.password_hash, 'hex'), 'base64'), '=', '')
    ) as password_hash
FROM users
INNER JOIN user_passwords up ON users.id = up.user_id
INNER JOIN user_emails 
ON users.id = user_emails.user_id 
AND user_emails.primary IS TRUE
AND users.id > 0
13 лайков

Просто заметка: я (при некоторой помощи от дружелюбной команды Auth0) в итоге немного доработал пример запроса для генерации корректных строк PHC при импорте паролей пользователей в Auth0.

Я также закодировал соль в формате base64, изменив эту строку

salt,

на

replace(encode(users.salt::bytea, 'base64'), '=', ''),

Подробнее об этом здесь (включая пошаговую инструкцию по импорту пользователей Discourse и их паролей в Auth0).

1 лайк

Спасибо @angus — это интересно, потому что у нас был клиент, который успешно импортировал пользователей в Auth0, используя запрос из первого сообщения. Интересно, не изменилось ли что-то в их процессе импорта — насколько я помню, возможность импорта строк PHC в Auth0 появилась совсем недавно в ноябре :thinking:

3 лайка

Да, я тоже об этом думал и пришёл к тому же выводу.

Мне тоже было не совсем понятно, как сформулировано в спецификации PHC. Неясно, должно ли соль быть закодировано в Base64 или нет.

Соль состоит из последовательности символов из набора: [a-zA-Z0-9/+.-] (строчные буквы, заглавные буквы, цифры, /, +, . и -). Спецификация функции ДОЛЖНА определять набор допустимых значений соли и максимальную длину этого поля. Функции, работающие с произвольными бинарными солями, ДОЛЖНЫ определять это поле как кодировку Base64 для бинарного значения, длина которого попадает в определённый диапазон или набор диапазонов.

2 лайка

Извините за оффтоп, но кто-нибудь импортировал хеши паролей из Auth0 в Discourse? Я планирую выполнить эту миграцию, поэтому любая помощь будет очень кстати. Я не являюсь платным клиентом Auth0, поэтому хотел узнать, возможно ли это, прежде чем платить за экспорт хешей паролей.

Спасибо.

Импорт паролей не поддерживается ядром Discourse, хотя это может быть возможно с помощью адаптированной версии этого стороннего плагина:

4 лайка

Спасибо! Я нашёл репозиторий на GitHub, но не нашёл тему здесь, на meta.

1 лайк

Привет, @angus, мы тут наводим порядок.

Правда ли, что блок кода от оригинального постера должен выглядеть так:

SELECT id, username,
  concat(
    '$pbkdf2-sha256$i=64000,l=32$',
    replace(encode(users.salt::bytea, 'base64'), '=', ''),
    '$',
    replace(encode(decode(password_hash, 'hex'), 'base64'), '=', '')
  ) as phc
FROM users

и включать что-то вроде:

Для получения дополнительной информации об импорте паролей из Discourse в Auth0 см. Bulk User Import Custom Password Hash Issue - Auth0 Community.

Чтобы перенести данные из Auth0 в Discourse, это может помочь: Migrated password hashes support.

3 лайка

Да, выглядит хорошо.

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

Кроме того, экспорт паролей в PHC не предназначен исключительно для Auth0, поэтому, возможно, его следует назвать просто «примером».

Вот полный запрос, который я использовал для экспорта:

SELECT
    user_emails.email,
    users.active as email_verified,
    concat(
        '$pbkdf2-sha256$i=64000,l=32$',
        replace(encode(users.salt::bytea, 'base64'), '=', ''),
        '$',
        replace(encode(decode(users.password_hash, 'hex'), 'base64'), '=', '')
    ) as password_hash
FROM users
INNER JOIN user_emails 
ON users.id = user_emails.user_id 
AND user_emails.primary IS TRUE
AND users.id > 0
2 лайка