Usando o Discourse como provedor de contas e problemas com PBKDF2

Olá. Provavelmente é uma pergunta estúpida, mas estamos migrando nosso fórum do XenForo para o Discourse. Temos um servidor de backend para autorização que envolve a conexão ao banco de dados e a verificação de credenciais na tabela de usuários.

O algoritmo bcrypt do XenForo funcionou como esperado e sem nenhum problema. No entanto, quando migramos para o Discourse, o algoritmo PBKDF2 não pareceu corresponder às minhas expectativas. Mesma senha exata, mesmo salt exato, mesmo número exato de iterações e comprimento, mas o hash de saída é diferente.

Tentei várias implementações diferentes do algoritmo PBKDF2, mas todas elas produzem o mesmo hash (diferente do do Discourse). Incluindo minha própria implementação.
Eu preferiria evitar mecanismos como OAuth2 ou SSO devido à sobrecarga adicional e ao trabalho extra que isso nos impõe.

Alguém já usou o Discourse para esses casos de uso e, se sim, como você resolveu esse problema?

Você está usando o plugin migrate password?

Não, pelo menos não que eu saiba. você está falando sobre isso?

1 curtida

Você já tentou a implementação do openssl? É o que usamos (você pode ver em discourse/lib/pbkdf2.rb).

Como exemplo, após definir a senha de um usuário para 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 curtida

Nós usamos principalmente a implementação crypto/bcrypt do Go para Xenforo. Os mesmos hashes de várias implementações de algoritmos pbkdf2 me sugerem que o Go possivelmente armazena strings ou converte strings para bytes de uma maneira um tanto diferente.

Terei que tentar isso amanhã (já é tarde por aqui). Se o OpenSSL me der o resultado desejado, então terei que procurar por bindings do OpenSSL para Go, ou terei que mudar para uma linguagem completamente diferente (que tenha bindings do OpenSSL) para o backend.

Você tem um caso de teste de exemplo curto?

Por exemplo, se você usar as informações acima, o que você obtém?

1 curtida

Desculpe, no momento não estou em posição de te dizer, pois os fusos horários são irritantes. Já é muito tarde por aqui e só poderei fazer isso no dia seguinte.

Eu fiz como você pediu. A senha é swordfish#98765.

Entrada do banco de dados:

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 {
	// error handling...
}

hashBytes, err := hex.DecodeString(hash)
if err != nil {
	// error handling...
}

saltBytes, err := hex.DecodeString(salt)
if err != nil {
	// error handling...
}

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))

Saída do código acima:

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

Esquece. Tive que usar a representação hexadecimal do salt como argumento, e não o salt decodificado como eu estava fazendo na postagem acima. Agora os hashes são iguais.

5 curtidas

Essa também foi minha teoria! Cometi o mesmo erro no início.

1 curtida

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