如何生成 SCRAM-SHA-256 以创建 Postgres 13 用户

How to Generate SCRAM-SHA-256 to Create Postgres 13 User

我想使用 CREATE USER 命令创建一个 Postgres 用户和一个已经散列过的密码摘要。经过大量搜索,我认为只有 MD5 才有可能,直到我找到 this link。我已经验证它是这样工作的:

CREATE USER test_user WITH LOGIN PASSWORD 'SCRAM-SHA-25696:H45+UIZiJUcEXrB9SHlv5Q==$I0mc87UotsrnezRKv9Ijqn/zjWMGPVdy1zHPARAGfVs=:nSjwT9LGDmAsMo+GqbmC2X/9LMgowTQBjUQsl45gZzA=';

然后我可以使用密码登录到该用户,该文章不一定说,但它是“postgres”。现在我知道这是可能的,我如何使用 .NET 5 生成 Postgres 13 将接受的 scram-sha-256 摘要?我看过其他 Postgres 文章使用过时的 MD5 散列,其中用户名在散列之前与密码连接在一起。新的 scram-sha-256 是否也需要发生这种情况?我在任何地方都找不到关于这个主题的太多信息。

我不知道如何使用 .NET 5 生成 scram-sha-256 摘要。 但是,如果您只需要 scram-sha-256 摘要,您可以通过在本地创建虚拟 postgres 用户并使用 createuser 命令回显加密密码来使用变通方法。

例如(通常你 运行 作为 Linux postgres 用户:su postgres):

$ createuser dummyuser -e --pwprompt
Enter password for new role: 
Enter it again: 
SELECT pg_catalog.set_config('search_path', '', false);
CREATE ROLE dummyuser PASSWORD 'SCRAM-SHA-25696:VnimR0aOywxZzY82nzy9Fg==$qF9uMCU6YsKoecvRjP8jSmZZxrXgn5VwzhHwfoWo5Xg=:xGYfBUvGsu9mZFiq1nSFaHi7uN8n47IDwHO32IeK9io=' NOSUPERUSER NOCREATEDB NOCREATEROLE INHERIT LOGIN;

您现在只需将摘要复制到您的查询中即可。当然,只有在您不需要使用 .NET 动态生成摘要时才有效。

不要忘记删除虚拟用户:

$ dropuser dummyuser

此外,如果您的本地 postgres 数据库仍然是 uses/generates md5,您必须使用以下查询将其更改为 scram,运行 作为 postgres 超级用户:

ALTER SYSTEM SET password_encryption = 'scram-sha-256';
SELECT pg_reload_conf();

但是,如果您想要使用 .NET 动态创建摘要,我建议您查看 createuser 命令的源代码...

一天后更新:

下面是将密码加密成scram-sha-256字符串的createuser命令的具体源码:

为了让生活更轻松,这里是上述函数调用的函数(可能不是全部)的链接

您应该能够用 .NET 或任何其他语言重写该代码。希望对您有所帮助!

如果您的目的是在之前生成一个SCRAM-SHA-256密码,那么您有一个可操作的数据库,然后我发现您可以使用此方法生成密码使用 Docker 工具进行散列。

docker run --rm -it --name postgres-dummy -d -e POSTGRES_HOST_AUTH_METHOD=trust postgres:14-alpine
docker exec -it postgres-dummy psql -U postgres
 \password
 (type in your password twice)
 select rolpassword from pg_authid where rolname = 'postgres';
 \q
docker stop postgres-dummy

我知道这不是在 .NET 中执行此操作的方法,但希望它对某些人有用。

有人构建了一个 Go 工具来执行此操作:

https://github.com/supercaracal/scram-sha-256

这是一个基于该 Go 项目的 python 3 端口:

from base64 import standard_b64encode
from hashlib import pbkdf2_hmac, sha256
from os import urandom
import hmac
import sys


salt_size = 16
digest_len = 32
iterations = 4096


def b64enc(b: bytes) -> str:
    return standard_b64encode(b).decode('utf8')


def pg_scram_sha256(passwd: str) -> str:
    salt = urandom(salt_size)
    digest_key = pbkdf2_hmac('sha256', passwd.encode('utf8'), salt, iterations,
                             digest_len)
    client_key = hmac.digest(digest_key, 'Client Key'.encode('utf8'), 'sha256')
    stored_key = sha256(client_key).digest()
    server_key = hmac.digest(digest_key, 'Server Key'.encode('utf8'), 'sha256')
    return (
        f'SCRAM-SHA-256${iterations}:{b64enc(salt)}'
        f'${b64enc(stored_key)}:{b64enc(server_key)}'
    )


def print_usage():
    print("Usage: provide single password argument to encrypt")
    sys.exit(1)


def main():
    args = sys.argv[1:]

    if args and len(args) > 1:
        print_usage()

    if args:
        passwd = args[0]
    else:
        passwd = sys.stdin.read().strip()

    if not passwd:
        print_usage()

    print(pg_scram_sha256(passwd))


if __name__ == "__main__":
    main()

我遇到了同样的问题,最后我深入研究了 Npgsql 代码并将其与 Shane 的答案结合起来得出了这个问题。它不完全是 battle-hardened,但我确实设法用它创建了一个角色,然后用它登录了。我更喜欢 F#,但将其转换回 C# 并不难。请记住,在 F# 中,函数最后一行的值是该函数的 return 值。此外,在 F# 中,^^^ 是 bitwise-xor 运算符。

let password_hash (password: string) =
    let normalized = System.Text.Encoding.UTF8.GetBytes(password.Normalize(System.Text.NormalizationForm.FormKC))
    let salt_len = 16
    let default_iterations = 4096

    let salt = System.Security.Cryptography.RandomNumberGenerator.GetBytes(salt_len)
    let mutable salt1 = Array.create (salt.Length + 4) 0uy

    let hmac = new System.Security.Cryptography.HMACSHA256(normalized)
    System.Buffer.BlockCopy(salt, 0, salt1, 0, salt.Length)
    salt1[salt1.Length - 1] <- 1uy

    let mutable hi = hmac.ComputeHash(salt1)
    let mutable u1 = hi

    for _ in 1 .. default_iterations - 1 do
        let u2 = hmac.ComputeHash(u1)
        for i in 0 .. hi.Length - 1 do
            hi[i] <- hi[i] ^^^ u2[i]
        u1 <- u2

    let client_key = (new System.Security.Cryptography.HMACSHA256(hi)).ComputeHash(System.Text.Encoding.UTF8.GetBytes("Client Key"))
    let stored_key = (System.Security.Cryptography.SHA256.Create()).ComputeHash(client_key)
    let server_key = (new System.Security.Cryptography.HMACSHA256(hi)).ComputeHash(System.Text.Encoding.UTF8.GetBytes("Server Key"))

    let builder = new System.Text.StringBuilder()
    builder.Append("'SCRAM-SHA-256$").Append(default_iterations.ToString()).Append(":").Append(System.Convert.ToBase64String(salt)).Append("$")
        .Append(System.Convert.ToBase64String(stored_key)).Append(":").Append(System.Convert.ToBase64String(server_key)).Append("'").ToString()