Closed GoogleCodeExporter closed 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
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
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
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
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
Original issue reported on code.google.com by
exavolt
on 23 Sep 2011 at 10:38