'How to Generate SCRAM-SHA-256 to Create Postgres 13 User
I want to create a Postgres user with the CREATE USER command and an already hashed digest for the password. After much searching, I thought it was only possible with MD5 until I found this link. I've verified that works like so:
CREATE USER test_user WITH LOGIN PASSWORD 'SCRAM-SHA-256$4096:H45+UIZiJUcEXrB9SHlv5Q==$I0mc87UotsrnezRKv9Ijqn/zjWMGPVdy1zHPARAGfVs=:nSjwT9LGDmAsMo+GqbmC2X/9LMgowTQBjUQsl45gZzA=';
I can then log into that user with the password, which the article doesn't necessarily say but it's "postgres". Now that I know it's possible, how using .NET 5 can I generate a scram-sha-256 digest that Postgres 13 will accept? I've seen other Postgres articles using the outdated MD5 hash where the username is concatenated with the password before hashing. Does that need to happen with the new scram-sha-256 as well? I couldn't find much information on this topic anywhere.
Solution 1:[1]
If your intent is to generate a SCRAM-SHA-256 password before you have an operational database, then I found out you can use this method to generate a password hash using Docker tooling.
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
I know it's not a way to do it in .NET, but hopefully it is useful for someone.
Solution 2:[2]
Someone built a Go tool to do this:
https://github.com/supercaracal/scram-sha-256
Here's a python 3 port based on that Go project:
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()
Solution 3:[3]
I had this same problem, and I ended up delving into the Npgsql code and combining it with Shane's answer to come up with this. It's not exactly battle-hardened, but I did manage to create a role with it, and then subsequently log in with it. I prefer F#, but it's not too hard to convert this back to C#. Just remember that, in F#, the value of the last line of a function is the return value of that function. Also, in F#, ^^^ is the bitwise-xor operator.
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()
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Robin Smidsrød |
| Solution 2 | |
| Solution 3 | Sean |
