Exportar hashes de contraseñas en formato PHC

Los detalles actuales sobre el sistema de almacenamiento de contraseñas de Discourse se encuentran en discourse/docs/SECURITY.md at main · discourse/discourse · GitHub. Al momento de escribir esto, utilizamos PBKDF2-SHA256 con 600.000 iteraciones.

En algunas situaciones, es posible que desees exportar todas las contraseñas hash de Discourse e importarlas en otro sistema. Por ejemplo, podrías estar migrando desde la autenticación integrada de Discourse hacia un sistema SSO personalizado. Recuerda que es imposible extraer las contraseñas originales de la base de datos, por lo que tu sistema de destino debe ser capaz de ejecutar el mismo algoritmo de hash con los mismos parámetros.

Los datos de las contraseñas se almacenan en la tabla user_passwords, que contiene las columnas password_hash, password_salt y password_algorithm. La columna password_algorithm almacena el prefijo completo del algoritmo PHC (por ejemplo, $pbkdf2-sha256$i=600000,l=32$), el cual puede variar por usuario si el número de iteraciones ha aumentado con el tiempo.

Puedes usar el Explorador de Datos para exportar la información en un formato legible por computadora:

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

Esto exportará los datos en el formato nativo de Discourse. La sal está codificada en hexadecimal y el hash de la contraseña también está codificado en hexadecimal.

Algunos sistemas externos admiten el formato de cadena PHC, que busca ser una forma de representar la salida de una función de hash de contraseñas independiente del algoritmo. Para pbkdf2-sha256, esta cadena contiene el tipo de algoritmo, el número de iteraciones, la sal codificada en base64 y el hash codificado en base64. Afortunadamente, Postgres puede manejar todo esto por nosotros en una sola consulta.

Para generar cadenas PHC para cada usuario de Discourse, puedes usar una consulta del Explorador de Datos como esta:

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

Si utilizas Auth0, entonces necesitas esta consulta en su lugar:

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 Me gusta

Solo una nota aquí: yo (con algo de ayuda del amable equipo de Auth0) terminé ajustando la consulta de ejemplo para generar cadenas PHC válidas para importar contraseñas de usuarios a Auth0.

También codifiqué la sal en base64 cambiando esta línea

salt,

por

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

Consulte más detalles aquí (incluyendo una guía paso a paso sobre cómo importar usuarios de Discourse y sus contraseñas a Auth0).

1 me gusta

Gracias @angus, esto es interesante porque hemos tenido un cliente que utilizó la consulta del mensaje original para importar usuarios a Auth0 con éxito. Me pregunto si algo ha cambiado en su proceso de importación; si no recuerdo mal, la capacidad de importar cadenas PHC a Auth0 era algo muy nuevo en noviembre :thinking:

3 Me gusta

Sí, me preguntaba lo mismo y pensé igual.

Tampoco estaba del todo seguro del lenguaje utilizado en la especificación de PHC. No sé si esto significa que la sal debe estar codificada en B64 o no.

La sal consiste en una secuencia de caracteres en: [a-zA-Z0-9/+.-] (letras minúsculas, letras mayúsculas, dígitos, /, +, . y -). La especificación de la función DEBE definir el conjunto de valores de sal válidos y una longitud máxima para este campo. Las funciones que operan con sal binaria arbitraria DEBERÍAN definir ese campo como la codificación B64 de un valor binario cuya longitud caiga dentro de un rango definido o un conjunto de rangos.

2 Me gusta

Perdón por desviarme del tema, pero ¿alguien ha importado hashes de contraseñas desde Auth0 a Discourse? Estoy pensando en hacer esta migración, así que cualquier ayuda sería muy apreciada. No soy un cliente de pago de Auth0, por lo que solo quería saber si esto es factible antes de pagar por la exportación de hashes de contraseñas.

Gracias.

La importación de contraseñas no es compatible con el núcleo de Discourse, aunque podría ser posible utilizando una versión adaptada de este complemento de terceros:

4 Me gusta

¡Gracias! He encontrado el repositorio en GitHub, pero no el tema aquí en meta.

1 me gusta

Hola @angus, estamos limpiando las cosas aquí.

¿Es cierto que el bloque de código del OP debería decir:

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

y incluir algo como:

Para más información sobre la importación de contraseñas de Discourse a Auth0, consulta Bulk User Import Custom Password Hash Issue - Auth0 Community.

Para mover datos de Auth0 a Discourse, esto podría ayudar: Migrated password hashes support.

3 Me gusta

Sí, eso parece bien.

Quizás quieras añadir algo sobre la necesidad de prestar atención específica a cada importación, ya que los datos de usuario que se manejan variarán según el caso de uso; es decir, no simplemente copies y pegues estas consultas.

Además, exportar contraseñas en PHC no es necesariamente solo para Auth0, así que quizás debería referirse simplemente como un “ejemplo”.

La consulta completa que utilicé para mi exportación fue:

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 Me gusta