안녕하세요. 아마도 멍청한 질문일 수 있지만, 우리는 포럼을 XenForo에서 Discourse로 마이그레이션하고 있습니다. 인증을 위한 백엔드 서버가 있으며, 이 서버는 데이터베이스에 연결하여 users 테이블과 자격 증명을 검증합니다.
XenForo의 bcrypt 알고리즘은 예상대로 문제 없이 작동했습니다. 그러나 Discourse로 마이그레이션한 후 PBKDF2 알고리즘은 제 기대와 맞지 않았습니다. 동일한 비밀번호, 동일한 소금(salt), 동일한 반복 횟수와 길이를 사용했는데도 출력된 해시가 달랐습니다.
PBKDF2 알고리즘의 여러 다른 구현을 시도해 보았지만, 모두 동일한(하지만 Discourse의 것과 다른) 해시를 출력했습니다. 제 자체 구현도 마찬가지였습니다.
추가 오버헤드와 우리에게 부과되는 추가 작업 때문에 OAuth2나 SSO와 같은 메커니즘은 피하고 싶습니다.
누군가 Discourse를 이러한 용도로 사용해 본 적이 있습니까? 만약 그렇다면 이 문제를 어떻게 해결했는지 알려 주실 수 있을까요?
pfaffman
(Jay Pfaffman)
12월 5, 2023, 10:50오후
2
migrate password 플러그인을 사용 중이신가요?
아니요, 적어도 제가 아는 한 그렇지 않습니다. 이것 에 대해 말씀하시는 건가요?
openssl의 구현 방식을 시도해 보셨나요? 우리가 사용하는 방식입니다(discourse/lib/pbkdf2.rb에서 확인하실 수 있습니다).
예를 들어, 사용자 비밀번호를 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:SHA-256 \
-kdfopt iter:600000 \
-keylen 32 \
PBKDF2 \
| tr -d : | tr '[:upper:]' '[:lower:]'
67650523776bdc87ebcd2fc11719553c87b11e6c4da49806d9d5232460d2adc9
Xenforo에서는 주로 Go의 crypto/bcrypt 구현을 사용했습니다. 다양한 pbkdf2 알고리즘 구현에서 생성된 동일한 해시를 보니, Go가 문자열을 저장하거나 문자열을 바이트로 변환하는 방식이 다소 다른 것 같습니다.
내일 시도해 봐야 할 것 같습니다(여기서는 늦은 시간입니다). OpenSSL이 원하는 결과를 주면, Go용 OpenSSL 바인딩을 찾아야 하거나 백엔드를 완전히 다른 언어(OpenSSL 바인딩이 있는 언어)로 전환해야 할 것입니다.
간단한 예시 테스트 케이스가 있으신가요?
예를 들어, 위 정보를 사용하게 되면 어떤 결과가 나오나요?
죄송합니다. 시간대가 좀 복잡해서 지금은 말씀드리기 어려운 상황입니다. 여기는 매우 늦은 시간이라, 내일 말씀드릴 수 있을 것 같습니다.
말씀하신 대로 수행했습니다. 비밀번호는 swordfish#98765입니다.
데이터베이스 항목:
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
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 {
// 에러 처리...
}
hashBytes, err := hex.DecodeString(hash)
if err != nil {
// 에러 처리...
}
saltBytes, err := hex.DecodeString(salt)
if err != nil {
// 에러 처리...
}
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))
위 코드의 출력 결과:
salt: 0d079078e220158011afaf497794166d
hash: db3f0829e66336323e81110a1792a76000b9c60605e1fa6964797ea1b07c33c6
hex.EncodeToString(key): b378c12d96ac62a6099fc674d334f0793e6294f7927da0badc811e794a960802
아니면 됐어. 위에서 말한 것처럼 디코딩된 솔트를 인수로 사용한 게 아니라, 솔트의 16진수 표현식을 인수로 사용해야 했어. 이제 해시가 동일해.
polyzium:
솔트의 16진수 표현을 인수로 사용
저도 같은 생각이었습니다! 처음에 저도 같은 실수를 했어요.