Re: SCRAM authentication, take three

From: Heikki Linnakangas <hlinnaka(at)iki(dot)fi>
To: Aleksander Alekseev <a(dot)alekseev(at)postgrespro(dot)ru>, Michael Paquier <michael(dot)paquier(at)gmail(dot)com>
Cc: Alvaro Herrera <alvherre(at)2ndquadrant(dot)com>, pgsql-hackers <pgsql-hackers(at)postgresql(dot)org>
Subject: Re: SCRAM authentication, take three
Date: 2017-03-07 10:18:11
Message-ID: 76ac7e67-4e3a-f4df-e087-fbac90151907@iki.fi
Views: Raw Message | Whole Thread | Download mbox | Resend email
Thread:
Lists: pgsql-hackers

On 02/20/2017 01:51 PM, Aleksander Alekseev wrote:
> Currently I don't see any significant flaws in these patches. However I
> would like to verify that implemented algorithms are compatible with
> algorithms implemented by third party.

Yes, that's very important.

> For instance, for user 'eax' and password 'pass' I got the following
> record in pg_authid:
>
> ```
> scram-sha-256:
> xtznkRN/nc/1DQ==:
> 4096:
> 2387c124a3139a276b848c910f43ece05dd670d0977ace4f20d724af522312e4:
> 5e3bdd6584880198b0375acabd8754c460af2389499f71a756660a10a8aaa843
> ```
>
> Let's say I would like to implement SCRAM in pure Python, for instance
> add it to pg8000 driver. Firstly I need to know how to generate server
> key and client key having only user name and password. Somehow like
> this:
>
> ```
> >>> import base64
> >>> import hashlib
> >>> base64.b16encode(hashlib.pbkdf2_hmac('sha256', b'pass',
> ... base64.b64decode(b'xtznkRN/nc/1DQ=='), 4096))
> b'14B90CFCF690120399A0E6D30C60DD9D9D221CD9C2E31EA0A00514C41823E6C3'
> ```
>
> Naturally it doesn't work because both SCRAM_SERVER_KEY_NAME and
> SCRAM_CLIENT_KEY_NAME should also be involved. I see PostgreSQL
> implementation just in front of me but unfortunately I'm still having
> problems calculating exactly the same server key and client key. It makes
> me worry whether PostgreSQL implementation is OK.
>
> Could you please give an example of how to do it?

RFC5802 describes the protocol in detail:

> SaltedPassword := Hi(Normalize(password), salt, i)
> ClientKey := HMAC(SaltedPassword, "Client Key")
> StoredKey := H(ClientKey)
> AuthMessage := client-first-message-bare + "," +
> server-first-message + "," +
> client-final-message-without-proof
> ClientSignature := HMAC(StoredKey, AuthMessage)
> ClientProof := ClientKey XOR ClientSignature
> ServerKey := HMAC(SaltedPassword, "Server Key")
> ServerSignature := HMAC(ServerKey, AuthMessage)

You've calculated SaltedPassword correctly with your Python snippet. To
derive ClientKey from it, you need to pass it to the HMAC() function. In
python, that'd be hmac.new(SaltedPassword, "Client Key",
hashlib.sha256). For example:

```
import base64
import hashlib
import hmac

salt = base64.b64decode(b'xtznkRN/nc/1DQ==');
SaltedPassword = hashlib.pbkdf2_hmac('sha256', b'pass',
salt, 4096);
ClientKey = hmac.new(SaltedPassword, "Client Key",
hashlib.sha256).hexdigest()
print 'SaltedPassword: ' + base64.b16encode(SaltedPassword)
print 'ClientKey; ' + ClientKey
```

This prints:

SaltedPassword:
14B90CFCF690120399A0E6D30C60DD9D9D221CD9C2E31EA0A00514C41823E6C3
ClientKey; 5b681195146a2027cb028a921bd0a89ff858b74bd2b38ed8b42561c28b1e369f

Which matches what the libpq implementation calculated. For constructing
the whole client-final-message, you'll also need to calculate
ClientSignature and ClientProof, which depend on the nonces, and is
therefore different on every authentication exchange.

- Heikki

In response to

Browse pgsql-hackers by date

  From Date Subject
Next Message Simon Riggs 2017-03-07 10:20:00 Re: Patch to improve performance of replay of AccessExclusiveLock
Previous Message Dilip Kumar 2017-03-07 10:16:31 Re: Proposal : Parallel Merge Join