The format implemented by passlib.hash.scram is insecure as a storage format for SCRAM credentials, although not as a storage format for passwords. Specifically, is trivial for an attacker possessing the hashed password to perform SCRAM authentication with a server using the same round count and salt. That is, if a server uses passlib.hash.scram for its password database and an attacker manages to steal that database, then the attacker can impersonate any user using SCRAM authentication. (The attacker cannot, however, recover the original passwords or attack other servers where the passwords have been reused, if the other servers generated their own salts or iteration counts.)
This is not an implementation bug. It is a design bug: the format itself carries the wrong information. I believe it affects all versions of libpass and versions of passlib since 1.6. I would recommend updating the documentation to warn about the dangers of the scram format, and possibly marking it deprecated.
The problem is that the passlib scram format contains the SaltedPassword, to use the name from RFC 5802, and that is sufficient for the client to perform the SCRAM protocol, because everything sent by the client is computed from SaltedPassword, not from the original password. (In contrast, a server using Argon2, for example, requires the client to send the original password, which cannot be computed from the Argon2 hash.)
RFC 5802 uses the phrase "authentication information" for the information that a server should store. It is defined in section 2.1:
Authentication information: Information used to verify an identity claimed by a SCRAM client. The authentication information for a SCRAM identity consists of salt, iteration count, "StoredKey" and "ServerKey" (as defined in the algorithm overview) for each supported cryptographic hash function.
Here are some other references for storage of SCRAM credentials:
- RFC 5803 specifies a format for storing SCRAM credential using LDAP:
scram-authValue = stored-key ":" server-key
- PgBouncer specifies a format for SCRAM credentials containing separate
storedkey and serverkey fields, not a single saltedpassword.
The following program (tested with libpass 1.9.3) demonstrates that passlib's output includes SaltedPassword (which is the output of PBKDF2):
>>> import base64
>>> from passlib.hash import scram
>>> from passlib.hash import pbkdf2_sha1
>>> scram.using(algs="sha-1").hash("password") # note, random salt
'$scram$100000$cY4RYixlrBXiXCuF$sha-1=laG97Nt1vckIFUGwGE.Zamrqs3E'
>>> pbkdf2_sha1.using(rounds=100000, salt=base64.b64decode("cY4RYixlrBXiXCuF")).hash("password")
'$pbkdf2$100000$cY4RYixlrBXiXCuF$laG97Nt1vckIFUGwGE.Zamrqs3E'
Note that the suffixes of the two outputs are equal (after "$sha-1=" and after the last "$", respectively).
The format implemented by
passlib.hash.scramis insecure as a storage format for SCRAM credentials, although not as a storage format for passwords. Specifically, is trivial for an attacker possessing the hashed password to perform SCRAM authentication with a server using the same round count and salt. That is, if a server usespasslib.hash.scramfor its password database and an attacker manages to steal that database, then the attacker can impersonate any user using SCRAM authentication. (The attacker cannot, however, recover the original passwords or attack other servers where the passwords have been reused, if the other servers generated their own salts or iteration counts.)This is not an implementation bug. It is a design bug: the format itself carries the wrong information. I believe it affects all versions of libpass and versions of passlib since 1.6. I would recommend updating the documentation to warn about the dangers of the scram format, and possibly marking it deprecated.
The problem is that the passlib scram format contains the
SaltedPassword, to use the name from RFC 5802, and that is sufficient for the client to perform the SCRAM protocol, because everything sent by the client is computed fromSaltedPassword, not from the original password. (In contrast, a server using Argon2, for example, requires the client to send the original password, which cannot be computed from the Argon2 hash.)RFC 5802 uses the phrase "authentication information" for the information that a server should store. It is defined in section 2.1:
Here are some other references for storage of SCRAM credentials:
scram-authValue = stored-key ":" server-keystoredkeyandserverkeyfields, not a singlesaltedpassword.The following program (tested with libpass 1.9.3) demonstrates that passlib's output includes
SaltedPassword(which is the output of PBKDF2):Note that the suffixes of the two outputs are equal (after "$sha-1=" and after the last "$", respectively).