wushuyi / passlib

Automatically exported from code.google.com/p/passlib
Other
0 stars 0 forks source link

format for storing SCRAM hashes #23

Closed GoogleCodeExporter closed 9 years ago

GoogleCodeExporter commented 9 years ago
This actually an authentication mechanism (rather than just hashing).

http://tools.ietf.org/html/rfc5802
http://tools.ietf.org/html/rfc5803

Original issue reported on code.google.com by exavolt on 23 Sep 2011 at 10:38

GoogleCodeExporter commented 9 years ago
Thanks for the links! SCRAM looks rather interesting, and seems like it would 
be fun to implement, enough so that I gave a bit of consideration towards how 
I'd go about it. Unfortunately, I ran into a couple of issues which at best 
need to be resolved before moving forward, and at worst mean it might not be 
feasible for me to add this to Passlib. To go into those issues in detail... 
(partly so I don't forget them later :)

---

An authentication protocol would be somewhat of a departure from Passlib's 
current focus on password hashing and storage, and I'm always fearful of 
feature creep... if I add this to Passlib, I'll be tempted to add an 
implementation of the Stanford Password Hash protocol, and who knows what else 
:) That aside, it does seem useful, and authentication protocols would be a 
reasonable extension of the features Passlib already has. In particular, 
providing a non-LDAP based method for storing the server-side part of SCRAM 
(compactly encoding the per-user PBKDF2 digest, salt, & iterations). 

My main issue is designing the api... Studying RFC 5802, it seems like a proper 
implementation of SCRAM would require enough of SASL that it would end up 
growing *into* a SASL implementation (which might be too large a project than I 
can take on right now). Looking around, there are already a few python SASL 
implementations... such as [Suelta](https://github.com/dwd/Suelta), which has a 
SCRAM implementation (though it's client-side only, so not a complete 
solution). 

More importantly api-wise: SASL wasn't designed to be standalone, but to be 
integrated into a larger protocol. In particular, one of the main security 
considerations listed for SCRAM (https://tools.ietf.org/html/rfc5802#section-9) 
is that it requires an already-secured channel to operate over; otherwise it's 
computationally equivalent to letting a passive eavesdropper have a copy of the 
pbkdf2 password digest. So any implementation of SCRAM would *have* to easily 
connect into a larger existing framework, and (responsibly) would need to 
provide a default choice to hedge against insecure use.

To this end, designing an implementation would require collecting and studying 
a sufficient number of use-cases to see:

1. what kind of secure channels were being used with SASL/SCRUM
2. what kind of storage systems were being used for the server side
3. study the Python api of the channel and storage layers (eg whether they work 
best with blocking calls, async/deferred polling, or callback hooks)
4. find enough test vectors to properly unit-test the SCRAM implementation (the 
rfc only has one example).
5. come up with a SCRAM/SASL api that could easily and secure connect the above 
two components.

---

Given all that, I'm on the fence as to whether this is a WontFix, assign to 
Wishlist, or assign to a milestone. If you could give some details about how 
you were/are planning to use SCRAM (particularly items 1-3 in the list above), 
that would definitely be useful to me in deciding how to move on this.

Original comment by elic@astllc.org on 27 Sep 2011 at 7:25

GoogleCodeExporter commented 9 years ago
Sorry for the too brief request. Let me reword this feature request.

It should be a request for passlib to support SCRAM hashes. Not to support the 
SCRAM itself.

What I want to see is that passlib supports hashing, storing (LDAP?) and verify 
SCRAM-compatible hashes. Just it, only the hashing, no authentication 
mechanism. The API should be the same like other algorithms (and not for a 
complete SASL/SCRAM mechanism).

An example usage case: recent XMPP servers support SASL/SCRAM for the 
authentication. It'll be nice if I can use the hashes to authenticate users on 
a web service (or other applications).

In XMPP, if the client doesn't support SCRAM, the client will then use the 
plain text mechanism (it sends the password as plain text) and then the server 
does the hashing and verification.

I actually have been playing with this (actually combined it with my own phpass 
implementation: use phpass for migrated passwords, use SCRAM hash for new 
passwords).

Original comment by exavolt on 28 Sep 2011 at 9:16

GoogleCodeExporter commented 9 years ago
Ah, that's much different ... and a bit easier.

Interestingly enough, SCRAM's hash algorithm is just PBKDF2-HMAC-{hashfunc},
which Passlib already has an encoding for. For instance, for SCRAM-SHA-1
hashes could be stored with pbkdf2_sha1
(http://packages.python.org/passlib/lib/passlib.hash.pbkdf2_digest.html).
That page has similar hash formats for pbkdf2_sha256 and pbkdf2_sha512.
As an example...

    from passlib.hash import pbkdf2_sha1

    #
    # server side: generate new salt and hash for a password
    #

    # this returns a modular-crypt-format hash string suitable
    # for storing server-side, which encodes the rounds, salt, and digest.
    hash = pbkdf2_sha1.encrypt(password, rounds=4096, salt_size=12)

    # for SCRAM, you'll need to extract the raw hash & salt from this string.
    # (NOTE: this uses an internal interface which I haven't exactly documented,
    # but have no plans changing in the future)
    hobj = pbkdf2_sha1.from_string(hash)

    # this should be the salt string in base64,
    # as you'd add to the 1st server msg:
    encoded_salt = hobj.salt.encode("base64").strip()

    # this should be the iterations value
    iterations = hobj.rounds

    # and this is the raw digest, equivalent to 'saltedpassword'
    # under the SCRAM rfc.
    raw_digest = hobj.checksum

    #
    # client side: calculate raw digest
    #

    # given encoded_salt and iterations from the 1st server msg,
    # the raw_digest result should again be equivalent to 'saltedpassword'
    # under the SCRAM rfc
    hobj = pbkdf2_sha1(salt=encoded_salt.decode("base64"), rounds=iterations)
    raw_digest = hobj.calc_checksum(password)

---

That said, the above code has a couple of deficiencies:

* it just takes in raw unicode / utf8 bytes, it doesn't use SCRAM's
  stringprep profile to prepare the password first.

* the hash format can only stores the digest for a single hash function.
  for a scram server, it would probably be useful to store
  the digests for multiple hash functions simultanously, so it would
  be prepared to speak SCRAM-SHA-1, SCRAM-SHA-256, etc.

* it would probably be much simpler to offer streamlined functions
  the perform the following ops:

    - generate a string encoding a new salt, rounds,
      and digests for all required hash formats.
    - from such a string, extract (salt, iterations, digest) tuple
      for a given hash format so the server can generate it's first message.
    - quickly calculate the saltpassword client side given a
      (salt, iterations, password) tuple.

All that is definitely enough new functionality that it would be worth adding
a separate hash format to Passlib, and seems reasonable to me. 
I'm thinking something with a format such as:

    "$scram${rounds}${encoded-salt}$sha1={encoded-digest},sha256={encoded-digest},..."

I'll probably add this in Passlib 1.6; but due to my current day-job load,
I may not get into that until late November... so in the meantime, I'm happy
to get feedback on any interface requirements / etc for this.

---

Re: actual storage, I've been trying to avoid getting into actual storage
mechanisms, and leave that up to applications, since they are so many and 
diverse
in nature (fs, ldap, database, etc). 

Original comment by elic@astllc.org on 30 Sep 2011 at 8:59

GoogleCodeExporter commented 9 years ago
As of r75508b3a2d32, passlib now contains a "scram" hash, suitable for storing 
SCRAM credentials using multiple algorithms. It's usuable as-is in the hg repo, 
and barring any glitches, should be included in Passlib 1.6. 

It's compatible with the normal Passlib password hash api, but adds a few more 
specialty methods suitable for integration with a SCRAM protocol stack. The 
.rst documentation can be found at 
http://code.google.com/p/passlib/source/browse/docs/lib/passlib.hash.scram.rst

Original comment by elic@astllc.org on 13 Mar 2012 at 4:29

GoogleCodeExporter commented 9 years ago
Passlib 1.6 has been released, and contains the finshed version of this hash. 
Documentation is available at 
http://packages.python.org/passlib/lib/passlib.hash.scram.html

Original comment by elic@astllc.org on 1 May 2012 at 8:55