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.
I don't know how to generate a scram-sha-256 digest with .NET 5.
However, if the scram-sha-256 digest is all you need you can use a workaround by creating a dummy postgres user locally and echoing the encryped password with the createuser command.
For example (usually you run this as Linux postgres user: 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-256$4096:VnimR0aOywxZzY82nzy9Fg==$qF9uMCU6YsKoecvRjP8jSmZZxrXgn5VwzhHwfoWo5Xg=:xGYfBUvGsu9mZFiq1nSFaHi7uN8n47IDwHO32IeK9io=' NOSUPERUSER NOCREATEDB NOCREATEROLE INHERIT LOGIN;
You can now just copy the digest into your query. Of course that only works if you don't need to generate the digest dynamically with .NET.
Don't forget to drop the dummy user:
$ dropuser dummyuser
Also, in case your local postgres db still uses/generates md5 you have to change that to scram by using following query, ran as postgres superuser:
ALTER SYSTEM SET password_encryption = 'scram-sha-256';
SELECT pg_reload_conf();
If you, however, want to create the digest dynamically with .NET, I recommend you to have a look at the source code of the createuser command...
Update one day later:
Here is the specific source code of the createuser command that encrypts the password into a scram-sha-256 string:
To make life easier, here are links to the functions (probably not all) the above function is calling
You should be able to rewrite that code in .NET or any other language. Hope it helps!
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.
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()