The Passlib documentation has moved to https://passlib.readthedocs.io
passlib.hash.scram
- SCRAM Hash¶
New in version 1.6.
SCRAM is a password-based challenge response protocol defined by RFC 5802. While Passlib does not provide an implementation of SCRAM, applications which use SCRAM on the server side frequently need a way to store user passwords in a secure format that can be used to authenticate users over SCRAM.
To accomplish this, Passlib provides the following
Modular Crypt Format-compatible password hash scheme which uses the
$scram$
identifier. This format encodes a salt, rounds settings, and one
or more pbkdf2_hmac()
digests... one digest for each
of the hash algorithms the server wishes to support over SCRAM.
Since this format is PBKDF2-based, it has equivalent security to Passlib’s other pbkdf2 hashes, and can be used to authenticate users using either the normal PasswordHash API or the SCRAM-specific class methods documented below.
Note
If you aren’t working with the SCRAM protocol, you probably don’t need to use this hash format.
Usage¶
This class can be used like any other Passlib hash, as follows:
>>> from passlib.hash import scram
>>> # generate new salt, hash password against default list of algorithms
>>> hash = scram.hash("password")
>>> hash
'$scram$6400$.Z/znnNOKWUsBaCU$sha-1=cRseQyJpnuPGn3e6d6u6JdJWk.0,sha-256=5G
cjEbRaUIIci1r6NAMdI9OPZbxl9S5CFR6la9CHXYc,sha-512=.DHbIm82ajXbFR196Y.9Ttbs
gzvGjbMeuWCtKve8TPjRMNoZK9EGyHQ6y0lW9OtWdHZrDZbBUhB9ou./VI2mlw'
>>> # same, but with an explicit number of rounds
>>> scram.using(rounds=8000).hash("password")
'$scram$8000$Y0zp/R/DeO89h/De$sha-1=eE8dq1f1P1hZm21lfzsr3CMbiEA,sha-256=Nf
kaDFMzn/yHr/HTv7KEFZqaONo6psRu5LBBFLEbZ.o,sha-512=XnGG11X.J2VGSG1qTbkR3FVr
9j5JwsnV5Fd094uuC.GtVDE087m8e7rGoiVEgXnduL48B2fPsUD9grBjURjkiA'
>>> # verify password
>>> scram.verify("password", hash)
True
>>> scram.verify("secret", hash)
False
See the generic PasswordHash usage examples for more details on how to use the common hash interface.
Additionally, this class provides a number of useful methods for SCRAM-specific actions:
You can override the default list of digests, and/or the number of iterations:
>>> hash = scram.using(rounds=1000, algs="sha-1,sha-256,md5").hash("password") >>> hash '$scram$1000$RsgZo7T2/l8rBUBI$md5=iKsH555d3ctn795Za4S7bQ,sha-1=dRcE2AUjALLF tX5DstdLCXZ9Afw,sha-256=WYE/LF7OntriUUdFXIrYE19OY2yL0N5qsQmdPNFn7JE'
Given a scram hash, you can use a single call to extract all the information the SCRAM needs to authenticate against a specific mechanism:
>>> # this returns (salt_bytes, rounds, digest_bytes) >>> scram.extract_digest_info(hash, "sha-1") ('F\xc8\x19\xa3\xb4\xf6\xfe_+\x05@H', 1000, 'u\x17\x04\xd8\x05#\x00\xb2\xc5\xb5~C\xb2\xd7K\tv}\x01\xfc')
Given a scram hash, you can extract the list of digest algorithms it contains information for (
sha-1
will always be present):>>> scram.extract_digest_algs(hash) ["md5", "sha-1", "sha-256"]
This class also provides a standalone helper which can calculate the
SaltedPassword
portion of the SCRAM protocol, taking care of the SASLPrep step as well:>>> scram.derive_digest("password", b'\x01\x02\x03', 1000, "sha-1") b'k\x086vg\xb3\xfciz\xb4\xb4\xe2JRZ\xaet\xe4`\xe7'
Interface¶
Note
This hash format is new in Passlib 1.6, and its SCRAM-specific API may change in the next few releases, depending on user feedback.
-
class
passlib.hash.
scram
¶ This class provides a format for storing SCRAM passwords, and follows the PasswordHash API.
It supports a variable-length salt, and a variable number of rounds.
The
using()
method accepts the following optional keywords:Parameters: - salt (bytes) – Optional salt bytes. If specified, the length must be between 0-1024 bytes. If not specified, a 12 byte salt will be autogenerated (this is recommended).
- salt_size (int) – Optional number of bytes to use when autogenerating new salts. Defaults to 12 bytes, but can be any value between 0 and 1024.
- rounds (int) – Optional number of rounds to use.
Defaults to 100000, but must be within
range(1,1<<32)
. - algs (list of strings) –
Specify list of digest algorithms to use.
By default each scram hash will contain digests for SHA-1, SHA-256, and SHA-512. This can be overridden by specify either be a list such as
["sha-1", "sha-256"]
, or a comma-separated string such as"sha-1, sha-256"
. Names are case insensitive, and may usehashlib
or IANA hash names. - relaxed (bool) –
By default, providing an invalid value for one of the other keywords will result in a
ValueError
. Ifrelaxed=True
, and the error can be corrected, aPasslibHashWarning
will be issued instead. Correctable errors includerounds
that are too small or too large, andsalt
strings that are too long.New in version 1.6.
In addition to the standard PasswordHash API methods, this class also provides the following methods for manipulating Passlib scram hashes in ways useful for pluging into a SCRAM protocol stack:
-
classmethod
extract_digest_info
(hash, alg)¶ return (salt, rounds, digest) for specific hash algorithm.
Parameters: - hash (str) –
scram
hash stored for desired user - alg (str) –
Name of digest algorithm (e.g.
"sha-1"
) requested by client.This value is run through
norm_hash_name()
, so it is case-insensitive, and can be the raw SCRAM mechanism name (e.g."SCRAM-SHA-1"
), the IANA name, or the hashlib name.
Raises: KeyError – If the hash does not contain an entry for the requested digest algorithm.
Returns: A tuple containing
(salt, rounds, digest)
, where digest matches the raw bytes returned by SCRAM’sHi()
function for the stored password, the provided salt, and the iteration count (rounds). salt and digest are both raw (unencoded) bytes.- hash (str) –
-
classmethod
extract_digest_algs
(hash, format='iana')¶ Return names of all algorithms stored in a given hash.
Parameters: - hash (str) – The
scram
hash to parse - format (str) – This changes the naming convention used by the
returned algorithm names. By default the names
are IANA-compatible; possible values are
"iana"
or"hashlib"
.
Returns: Returns a list of digest algorithms; e.g.
["sha-1"]
- hash (str) – The
-
classmethod
derive_digest
(password, salt, rounds, alg)¶ helper to create SaltedPassword digest for SCRAM.
This performs the step in the SCRAM protocol described as:
SaltedPassword := Hi(Normalize(password), salt, i)
Parameters: - password (unicode or utf-8 bytes) – password to run through digest
- salt (bytes) – raw salt data
- rounds (int) – number of iterations.
- alg (str) – name of digest to use (e.g.
"sha-1"
).
Returns: raw bytes of
SaltedPassword
Format & Algorithm¶
An example scram hash (of the string password
) is:
$scram$6400$.Z/znnNOKWUsBaCU$sha-1=cRseQyJpnuPGn3e6d6u6JdJWk.0,sha-256=5G
cjEbRaUIIci1r6NAMdI9OPZbxl9S5CFR6la9CHXYc,sha-512=.DHbIm82ajXbFR196Y.9Ttb
sgzvGjbMeuWCtKve8TPjRMNoZK9EGyHQ6y0lW9OtWdHZrDZbBUhB9ou./VI2mlw
An scram hash string has the format $scram$rounds$salt$alg1=digest1,alg2=digest2,...
, where:
$scram$
is the prefix used to identify Passlib scram hashes, following the Modular Crypt Formatrounds
is the number of decimal rounds to use (6400 in the example), zero-padding not allowed. this value must be inrange(1, 2**32)
.salt
is a base64 salt string (.Z/znnNOKWUsBaCU
in the example), encoded usingab64_encode()
.alg
is a lowercase IANA hash function name [2], which should match the digest in the SCRAM mechanism name.digest
is a base64 digest for the specific algorithm, encoded usingab64_encode()
. Digests forsha-1
,sha-256
, andsha-512
are present in the example.- There will always be one or more
alg=digest
pairs, separated by a comma. Per the SCRAM specification, the algorithmsha-1
should always be present.
There is also an alternate format ($scram$rounds$salt$alg,...
)
which is used to represent a configuration string that doesn’t contain
any digests. An example would be:
$scram$6400$.Z/znnNOKWUsBaCU$sha-1,sha-256,sha-512
The algorithm used to calculate each digest is:
pbkdf2(salsprep(password).encode("utf-8"), salt, rounds, alg_digest_size, "hmac-"+alg)
...as laid out in the SCRAM specification [1]. All digests should verify against the same password, or the hash is considered malformed.
Note
This format is similar in spirit to the LDAP storage format for SCRAM hashes, defined in RFC 5803, except that it encodes everything into a single string, and does not have any storage requirements (outside of the ability to store 512+ character ascii strings).
Security¶
The security of this hash is only as strong as the weakest digest used by this hash. Since the SCRAM [1] protocol requires SHA1 always be supported, this will generally be the weakest link, since the other digests will generally be stronger ones (e.g. SHA2-256).
None-the-less, since PBKDF2 is sufficiently collision-resistant on its own, any pre-image weaknesses found in SHA1 should be mitigated by the PBKDF2-HMAC-SHA1 wrapper; and should have no flaws outside of brute-force attacks on PBKDF2-HMAC-SHA1.
Footnotes
[1] | (1, 2) The SCRAM protocol is laid out in RFC 5802. |
[2] | The official list of IANA-assigned hash function names - http://www.iana.org/assignments/hash-function-text-names |