Usando Discourse como proveedor de cuentas y problemas con PBKDF2

Hola. Probablemente sea una pregunta tonta, pero hemos estado migrando nuestro foro de XenForo a Discourse. Tenemos un servidor backend para la autorización que implica conectarse a la base de datos y verificar las credenciales contra la tabla de usuarios.

El algoritmo bcrypt de XenForo funcionó como se esperaba y sin ningún problema. Sin embargo, cuando migramos a Discourse, el algoritmo PBKDF2 no pareció cumplir mis expectativas. Misma contraseña exacta, misma sal exacta, mismo número exacto de iteraciones y longitud, pero el hash de salida es diferente.

Probé varias implementaciones diferentes del algoritmo PBKDF2, pero todas producen el mismo hash (diferente al de Discourse). Incluyendo mi propia implementación.
Preferiría evitar mecanismos como OAuth2 o SSO debido a la sobrecarga adicional y al trabajo adicional que nos imponen.

¿Alguien ha utilizado Discourse para tales casos de uso y, si es así, cómo resolvieron este problema?

¿Estás usando el plugin migrate password?

No, al menos no que yo sepa. ¿Te refieres a esto?

1 me gusta

¿Has probado la implementación de openssl? Esa es la que usamos (puedes verla en discourse/lib/pbkdf2.rb).

Como ejemplo, después de establecer la contraseña de un usuario en swordfish#:

discourse_development=# select password_hash, salt, password_algorithm from users where id=2;
-[ RECORD 1 ]------+-----------------------------------------------------------------
password_hash      | 67650523776bdc87ebcd2fc11719553c87b11e6c4da49806d9d5232460d2adc9
salt               | 712ef44dd6fe6d6f0f1b6f702bb78459
password_algorithm | $pbkdf2-sha256$i=600000,l=32$
$ openssl kdf \
  -kdfopt pass:'swordfish#' \
  -kdfopt salt:712ef44dd6fe6d6f0f1b6f702bb78459  \
  -kdfopt digest:SHA2-256 \
  -kdfopt iter:600000 \
  -keylen 32 \
  PBKDF2 \
  | tr -d : | tr '[:upper:]' '[:lower:]'
67650523776bdc87ebcd2fc11719553c87b11e6c4da49806d9d5232460d2adc9
1 me gusta

Principalmente utilizamos la implementación crypto/bcrypt de Go para Xenforo. Los mismos hashes de varias implementaciones de algoritmos pbkdf2 me sugieren que Go posiblemente almacena cadenas o las convierte a bytes de una manera algo diferente.

Tendré que probarlo mañana (es tarde por aquí). Si OpenSSL me da el resultado deseado, entonces tendría que buscar enlaces de OpenSSL para Go, o tendría que cambiar a un idioma completamente diferente (que tenga enlaces de OpenSSL) para el backend.

¿Tienes un caso de prueba de ejemplo corto?

Por ejemplo, si usas la información anterior, ¿qué obtienes?

1 me gusta

Lo siento, actualmente no estoy en posición de decírtelo ya que las zonas horarias son molestas. Es muy tarde por aquí y solo podría hacerlo al día siguiente.

Lo hice como pediste. La contraseña es swordfish#98765.

Entrada de la base de datos:

discourse=> SELECT password_hash, salt, password_algorithm FROM users WHERE id=1;
                          password_hash                           |               salt               |      password_algorithm
------------------------------------------------------------------+----------------------------------+-------------------------------
 db3f0829e66336323e81110a1792a76000b9c60605e1fa6964797ea1b07c33c6 | 0d079078e220158011afaf497794166d | $pbkdf2-sha256$i=600000,l=32$
(1 row)

OpenSSL:

/var/discourse# openssl kdf \
> -kdfopt pass:'swordfish#98765' \
> -kdfopt salt:0d079078e220158011afaf497794166d \
> -kdfopt digest:SHA2-256 \
> -kdfopt iter:600000 \
> -keylen 32 \
> PBKDF2 \
> | tr -d : | tr '[:upper:]' '[:lower:]'
db3f0829e66336323e81110a1792a76000b9c60605e1fa6964797ea1b07c33c6

Código Go:

var userId int
var hash string
var salt string
var active bool

row := s.Database.QueryRow(`
	SELECT u.id, u.password_hash, u.salt, u.active
	FROM users AS u
	INNER JOIN user_emails AS ue ON u.id = ue.user_id
	WHERE ue.email = $1;`,
	email,
)

if err := row.Scan(&userId, &hash, &salt, &active); err != nil {
	// manejo de errores...
}

hashBytes, err := hex.DecodeString(hash)
if err != nil {
	// manejo de errores...
}

saltBytes, err := hex.DecodeString(salt)
if err != nil {
	// manejo de errores...
}

key := pbkdf2.Key([]byte(password), saltBytes, 600000, 32, sha256.New)

fmt.Printf("salt: %v\n", salt)
fmt.Printf("hash: %v\n", hash)
fmt.Printf("hex.EncodeToString(key): %v\n", hex.EncodeToString(key))

Salida del código anterior:

salt: 0d079078e220158011afaf497794166d
hash: db3f0829e66336323e81110a1792a76000b9c60605e1fa6964797ea1b07c33c6
hex.EncodeToString(key): b378c12d96ac62a6099fc674d334f0793e6294f7927da0badc811e794a960802

No importa. Tuve que usar la representación hexadecimal de la sal como argumento, no la sal decodificada como estaba haciendo en la publicación anterior. Ahora los hashes son iguales.

5 Me gusta

¡Esta también fue mi teoría! Cometí el mismo error al principio.

1 me gusta

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.