以 PHC 格式导出密码哈希

Discourse 密码存储系统的当前详细信息可参阅 https://github.com/discourse/discourse/blob/main/docs/SECURITY.md。截至撰写本文时,我们使用 PBKDF2-SHA256 算法,迭代次数为 600,000 次。

在某些情况下,您可能希望将 Discourse 中所有已哈希的密码导出,并导入到另一个系统中。例如,您可能正从 Discourse 内置的身份验证迁移到自定义的 SSO 系统。请记住,无法从数据库中还原原始密码,因此您的目标系统必须能够使用相同的哈希算法及参数进行运算。

密码数据存储在 user_passwords 表中,该表包含 password_hashpassword_saltpassword_algorithm 列。password_algorithm 列存储完整的 PHC 算法前缀(例如 $pbkdf2-sha256$i=600000,l=32$),如果迭代次数随时间增加,该值可能因用户而异。

您可以使用数据浏览器(Data Explorer)以计算机可读格式导出相关信息:

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

这将按 Discourse 原生格式导出数据。其中,salt 为十六进制编码,密码哈希值也为十六进制编码。

某些外部系统支持 PHC 字符串格式,该格式旨在以跨算法的方式表示密码哈希函数的输出。对于 pbkdf2-sha256,该字符串包含算法类型、迭代次数、base64 编码的 salt 以及 base64 编码的哈希值。幸运的是,Postgres 可以通过单个查询为我们处理所有这些内容。

若要为每个 Discourse 用户生成 PHC 字符串,您可以使用如下数据浏览器查询:

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

如果您使用 Auth0,则应使用以下查询:

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 个赞

这里补充一点:我(在友好的 Auth0 团队协助下)最终对示例查询进行了调整,以生成有效的 PHC 字符串,用于将用户密码导入 Auth0。

我还通过将以下代码行

salt,

修改为

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

对盐值进行了 base64 编码。

详见此处(包括如何将 Discourse 用户及其密码批量导入 Auth0 的分步指南):

1 个赞

感谢 @angus——这很有趣,因为我们有客户使用原帖中的查询成功将用户导入到 Auth0。我想知道他们的导入流程是否发生了变化——据我所知,在 11 月时,将 PHC 字符串导入 Auth0 的功能还非常新。:thinking:

3 个赞

是的,我也在琢磨这个,想法和你一样。

我对 PHC 规范中的措辞也不太确定。不确定这是否意味着盐值必须进行 Base64 编码。

盐值由以下字符序列组成:[a-zA-Z0-9/+.-](小写字母、大写字母、数字、/、+、. 和 -)。函数规范必须定义有效的盐值集合以及该字段的最大长度。适用于任意二进制盐值的函数应将该字段定义为二进制值的 Base64 编码,且该二进制值的长度需落在一个定义好的范围或一组范围内。

2 个赞

抱歉跑题了,但有人从 Auth0 导入密码哈希到 Discourse 吗?我正在考虑进行此迁移,任何帮助都将不胜感激。我不是 Auth0 的付费客户,所以想在付费导出密码哈希之前确认一下这是否可行。

谢谢。

Discourse 核心不支持导入密码,但或许可以通过修改此第三方插件来实现:

4 个赞

谢谢!我已在 GitHub 上找到了该仓库,但在此处的 Meta 社区中尚未找到相关话题。

1 个赞

@angus,我们正在这里清理一些内容。

OP 代码块是否应该如下所示:

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

并包含类似以下内容:

有关将 Discourse 密码导入 Auth0 的更多信息,请参阅 https://community.auth0.com/t/bulk-user-import-custom-password-hash-issue/47408。

要将数据从 Auth0 迁移到 Discourse,这可能会有所帮助:https://meta.discourse.org/t/migrated-password-hashes-support/19512。

3 个赞

是的,看起来不错。

你可能需要补充说明一下,每个导入操作都需要特别关注,因为所处理的用户数据会根据使用场景而有所不同,也就是说,不要直接复制/粘贴这些查询语句。

另外,在 PHC 中导出密码并不一定仅适用于 Auth0,因此或许可以将其称为一个“示例”。

我用于导出的完整查询语句如下:

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 个赞