Re: [HACKERS] SCRAM authentication, take three - Mailing list pgsql-hackers

From Heikki Linnakangas
Subject Re: [HACKERS] SCRAM authentication, take three
Date
Msg-id 76ac7e67-4e3a-f4df-e087-fbac90151907@iki.fi
Whole thread Raw
In response to Re: [HACKERS] SCRAM authentication, take three  (Aleksander Alekseev <a.alekseev@postgrespro.ru>)
List 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




pgsql-hackers by date:

Previous
From: Dilip Kumar
Date:
Subject: Re: [HACKERS] Proposal : Parallel Merge Join
Next
From: Simon Riggs
Date:
Subject: Re: [HACKERS] Patch to improve performance of replay of AccessExclusiveLock