Esporta gli hash delle password nel formato PHC

I dettagli attuali sul sistema di archiviazione delle password di Discourse sono disponibili all’indirizzo discourse/docs/SECURITY.md at main · discourse/discourse · GitHub. Al momento della stesura, utilizziamo PBKDF2-SHA256 con 600.000 iterazioni.

In alcune situazioni, potresti voler esportare tutte le password crittografate da Discourse e importarle in un altro sistema. Ad esempio, potresti essere in fase di migrazione dall’autenticazione integrata di Discourse a un sistema SSO personalizzato. Ricorda che è impossibile estrarre le password originali dal database, quindi il sistema di destinazione deve essere in grado di eseguire lo stesso algoritmo di hashing con gli stessi parametri.

I dati delle password sono archiviati nella tabella user_passwords, che contiene le colonne password_hash, password_salt e password_algorithm. La colonna password_algorithm memorizza l’intero prefisso dell’algoritmo PHC (ad esempio $pbkdf2-sha256$i=600000,l=32$), che può variare da utente a utente se il numero di iterazioni è stato aumentato nel tempo.

Puoi utilizzare Data Explorer per esportare le informazioni in un formato leggibile dal computer:

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

Questo esporterà i dati nel formato nativo di Discourse. Il salt è codificato in esadecimale e l’hash della password è anch’esso codificato in esadecimale.

Alcuni sistemi esterni supportano il formato stringa PHC, che mira a rappresentare l’output di una funzione di hashing delle password in modo indipendente dall’algoritmo. Per pbkdf2-sha256, questa stringa contiene il tipo di algoritmo, il numero di iterazioni, il salt codificato in base64 e l’hash codificato in base64. Fortunatamente, Postgres può gestire tutto questo per noi in una singola query.

Per generare stringhe PHC per ogni utente di Discourse, puoi utilizzare una query di Data Explorer come questa:

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

Se utilizzi Auth0, usa invece questa:

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 Mi Piace

Just a note here that I (with some help from the friendly Auth0 team) ended up tweaking the example query to generate valid PHC strings for importing user passwords into Auth0.

I also encoded the salt as base64 by changing this line

salt,

to

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

See further here (including a step by step on how to import Discourse users and their passwords into Auth0).

1 Mi Piace

Thanks @angus - this is interesting because we have had a customer use the query in the OP to successfully import users to Auth0. I wonder if something has changed in their import process - IIRC the ability to import PHC strings to Auth0 was very new back in November :thinking:

3 Mi Piace

Yeah, I was wondering about that, and thought the same.

I also wasn’t quite sure of the language in the PHC specification. Not sure if this means the salt must be B64 encoded or not.

The salt consists in a sequence of characters in: [a-zA-Z0-9/+.-] (lowercase letters, uppercase letters, digits, /, +, . and -). The function specification MUST define the set of valid salt values and a maximum length for this field. Functions that work over arbitrary binary salts SHOULD define that field to be the B64 encoding for a binary value whose length falls in a defined range or set of ranges.

2 Mi Piace

Scusa se mi sono discostato dall’argomento, ma qualcuno ha importato gli hash delle password da Auth0 a Discourse? Sto pensando di effettuare questa migrazione, quindi qualsiasi aiuto sarebbe apprezzato. Non sono un cliente a pagamento di Auth0, quindi volevo solo sapere se è fattibile prima di pagare per l’esportazione degli hash delle password.

Grazie.

L’importazione delle password non è supportata dal nucleo di Discourse, sebbene potrebbe essere possibile utilizzando una versione adattata di questo plugin di terze parti:

4 Mi Piace

Grazie! Ho trovato il repository su GitHub, ma non l’argomento qui su meta.

1 Mi Piace

Ehi @angus, stiamo facendo un po’ di pulizia qui.

È vero che il blocco di codice dell’OP dovrebbe essere:

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

e includere qualcosa come:

Per ulteriori informazioni sull’importazione delle password di Discourse in Auth0, vedi Bulk User Import Custom Password Hash Issue - Auth0 Community.

Per spostare i dati da Auth0 a Discourse, questo potrebbe essere utile: Migrated password hashes support.

3 Mi Piace

Sì, sembra tutto a posto.

Forse vorresti aggiungere una nota sul fatto che ogni importazione richiede un’attenzione specifica, poiché i dati utente gestiti variano a seconda del caso d’uso, ovvero non limitarti a copiare e incollare queste query.

Inoltre, l’esportazione delle password in PHC non è necessariamente destinata solo ad Auth0, quindi forse è meglio riferirsi a ciò come a un “esempio”.

La query completa che ho utilizzato per la mia esportazione era

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 Mi Piace