def test_hash_fast(self, func, secret): """ Hash various secrets as cheaply as possible. """ hash_secret( secret, salt=b"12345678", time_cost=1, memory_cost=8, parallelism=1, hash_len=8, type=Type.I, )
def hash(self, string): """Hash a provided string using argon2id Arguments: string {str} -- string you want to hash Returns: str -- argon2 hash of the given string """ encoded_str = string.encode(self.string_encoding) # random salt, 16 bytes long salt = os.urandom(self.salt_len) # hash the given string using argon2id algorithm result = hash_secret( secret=encoded_str, salt=salt, time_cost=self.time_cost, memory_cost=self.memory_cost, parallelism=self.parallelism, hash_len=self.hash_len, type=Type.ID, ) return result.decode(self.hash_encoding)
def test_hash_secret(self, type, hash): """ Creates the same encoded hash as the Argon2 CLI client. """ rv = hash_secret( TEST_PASSWORD, TEST_SALT, TEST_TIME, TEST_MEMORY, TEST_PARALLELISM, TEST_HASH_LEN, type, ) assert hash == rv assert isinstance(rv, bytes)
def test_argument_ranges(password, time_cost, parallelism, memory_cost, hash_len, salt_len): """ Ensure that both hashing and verifying works for most combinations of legal values. Limits are intentionally chosen to be *not* on 2^x boundaries. This test is rather slow. """ assume(parallelism * 8 <= memory_cost) hash = hash_secret( secret=password, salt=os.urandom(salt_len), time_cost=time_cost, parallelism=parallelism, memory_cost=memory_cost, hash_len=hash_len, type=Type.I, ) assert verify_secret(hash, password, Type.I)
def _hash_argon2id13_secret(password, salt, iterations, memory): """ Internal helper. Returns the salted/hashed password using the argon2id-13 algorithm. The return value is base64-encoded. """ rawhash = hash_secret( secret=password, salt=base64.b64decode(salt), time_cost=iterations, memory_cost=memory, parallelism=1, # hard-coded by WAMP-SCRAM spec hash_len=32, type=Type.ID, version=0x13, # note this is decimal "19" which appears in places ) # spits out stuff like: # '$argon2i$v=19$m=512,t=2,p=2$5VtWOO3cGWYQHEMaYGbsfQ$AcmqasQgW/wI6wAHAMk4aQ' _, tag, ver, options, salt_data, hash_data = rawhash.split(b'$') return hash_data
def hash(self, password): """ Hash *password* and return an encoded hash. :param password: Password to hash. :type password: ``bytes`` or ``unicode`` :raises argon2.exceptions.HashingError: If hashing fails. :rtype: unicode """ return hash_secret( secret=_ensure_bytes(password, self.encoding), salt=_ensure_bytes(self.salt, self.encoding), time_cost=self.time_cost, memory_cost=self.memory_cost, parallelism=self.parallelism, hash_len=self.hash_len, type=Type.ID, ).decode("ascii")
def hashWithFixedSalt(self, password, salt): """ Hash *password* and return an encoded hash. :param password: Password to hash. :param salt: Password salt, should be array of bytes (generate using os.urandom) :type password: ``bytes`` or ``unicode`` :raises argon2.exceptions.HashingError: If hashing fails. :rtype: unicode """ return low_level.hash_secret( secret=_ensure_bytes(password, self.encoding), salt=salt, time_cost=self.time_cost, memory_cost=self.memory_cost, parallelism=self.parallelism, hash_len=self.hash_len, type=Type.I, ).decode("ascii")
def derive_scram_credential(email: str, password: str, salt: Optional[bytes] = None) -> Dict: """ Derive WAMP-SCRAM credentials from user email and password. The SCRAM parameters used are the following (these are also contained in the returned credentials): * kdf ``argon2id-13`` * time cost ``4096`` * memory cost ``512`` * parallelism ``1`` See `draft-irtf-cfrg-argon2 <https://datatracker.ietf.org/doc/draft-irtf-cfrg-argon2/>`__ and `argon2-cffi <https://argon2-cffi.readthedocs.io/en/stable/>`__. :param email: User email. :param password: User password. :param salt: Optional salt to use (must be 16 bytes long). If none is given, compute salt from email as ``salt = SHA256(email)[:16]``. :return: WAMP-SCRAM credentials. When serialized, the returned credentials can be copy-pasted into the ``config.json`` node configuration for a Crossbar.io node. """ assert HAS_ARGON, 'missing dependency argon2' from argon2.low_level import hash_secret from argon2.low_level import Type # derive salt from email if not salt: m = hashlib.sha256() m.update(email.encode('utf8')) salt = m.digest()[:16] assert len(salt) == 16 hash_data = hash_secret( secret=password.encode('utf8'), salt=salt, time_cost=4096, memory_cost=512, parallelism=1, hash_len=32, type=Type.ID, version=19, ) _, tag, v, params, _, salted_password = hash_data.decode('ascii').split( '$') assert tag == 'argon2id' assert v == 'v=19' # argon's version 1.3 is represented as 0x13, which is 19 decimal... params = {k: v for k, v in [x.split('=') for x in params.split(',')]} salted_password = salted_password.encode('ascii') client_key = hmac.new(salted_password, b"Client Key", hashlib.sha256).digest() stored_key = hashlib.new('sha256', client_key).digest() server_key = hmac.new(salted_password, b"Server Key", hashlib.sha256).digest() credential = { "kdf": "argon2id-13", "memory": int(params['m']), "iterations": int(params['t']), "salt": binascii.b2a_hex(salt).decode('ascii'), "stored-key": binascii.b2a_hex(stored_key).decode('ascii'), "server-key": binascii.b2a_hex(server_key).decode('ascii'), } return credential
from pprint import pprint from argon2.low_level import hash_secret from argon2.low_level import Type if len(sys.argv) != 2: print("usage: {} password".format(sys.argv[0])) sys.exit(2) password = sys.argv[1].encode('ascii') salt = os.urandom(16) hash_data = hash_secret( secret=password, salt=salt, time_cost=4096, memory_cost=512, parallelism=2, hash_len=16, type=Type.ID, version=19, ) _, tag, v, params, othersalt, salted_password = hash_data.decode( 'ascii').split('$') assert tag == 'argon2id' assert v == 'v=19' params = {k: v for k, v in [x.split('=') for x in params.split(',')]} salted_password = salted_password.encode('ascii') client_key = hmac.new(salted_password, b"Client Key", hashlib.sha256).digest() stored_key = hashlib.new('sha256', client_key).digest() server_key = hmac.new(salted_password, b"Server Key", hashlib.sha256).digest()
if len(sys.argv) not in (2, 3): print("usage: {} password".format(sys.argv[0])) sys.exit(2) password = sys.argv[1].encode('ascii') if len(sys.argv) == 3: salt = binascii.a2b_hex(sys.argv[2].encode('ascii')) assert len(salt) == 16 else: salt = os.urandom(16) hash_data = hash_secret( secret=password, salt=salt, time_cost=4096, memory_cost=512, parallelism=1, hash_len=32, type=Type.ID, version=19, ) _, tag, v, params, othersalt, salted_password = hash_data.decode('ascii').split('$') assert tag == 'argon2id' assert v == 'v=19' # argon's version 1.3 is represented as 0x13, which is 19 decimal... params = { k: v for k, v in [x.split('=') for x in params.split(',')] } salted_password = salted_password.encode('ascii')
def on_challenge(self, session, challenge): assert challenge.method == u"scram" assert self._client_nonce is not None required_args = ['nonce', 'salt', 'cost'] optional_args = ['memory', 'parallel', 'channel_binding'] # probably want "algorithm" too, with either "argon2id-19" or # "pbkdf2" as values for k in required_args: if k not in challenge.extra: raise RuntimeError( "WAMP-SCRAM challenge option '{}' is " " required but not specified".format(k) ) for k in challenge.extra: if k not in optional_args + required_args: raise RuntimeError( "WAMP-SCRAM challenge has unknown attribute '{}'".format(k) ) channel_binding = challenge.extra.get(u'channel_binding', u'') server_nonce = challenge.extra[u'nonce'] # base64 salt = challenge.extra[u'salt'] # base64 cost = int(challenge.extra[u'cost']) memory = int(challenge.extra.get(u'memory', 512)) parallel = int(challenge.extra.get(u'parallel', 2)) password = self._args['password'].encode('utf8') # supplied by user authid = saslprep(self._args['authid']) client_nonce = self._client_nonce auth_message = ( "{client_first_bare},{server_first},{client_final_no_proof}".format( client_first_bare="n={},r={}".format(authid, client_nonce), server_first="r={},s={},i={}".format(server_nonce, salt, cost), client_final_no_proof="c={},r={}".format(channel_binding, server_nonce), ) ) rawhash = hash_secret( secret=password, salt=base64.b64decode(salt), time_cost=cost, memory_cost=memory, parallelism=parallel, hash_len=16, # another knob? type=Type.ID, version=19, ) # spits out stuff like: # '$argon2i$v=19$m=512,t=2,p=2$5VtWOO3cGWYQHEMaYGbsfQ$AcmqasQgW/wI6wAHAMk4aQ' _, tag, ver, options, salt_data, hash_data = rawhash.split(b'$') salted_password = hash_data client_key = hmac.new(salted_password, b"Client Key", hashlib.sha256).digest() stored_key = hashlib.new('sha256', client_key).digest() client_signature = hmac.new(stored_key, auth_message.encode('ascii'), hashlib.sha256).digest() client_proof = xor_array(client_key, client_signature) def confirm_server_signature(session, details): """ When the server is satisfied, it sends a 'WELCOME' message. This will cause the session to be set up and 'join' gets notified. Here, we check the server-signature thus authorizing the server -- if it fails we drop the connection. """ alleged_server_sig = base64.b64decode(details.authextra['scram_server_signature']) server_key = hmac.new(salted_password, b"Server Key", hashlib.sha256).digest() server_signature = hmac.new(server_key, auth_message.encode('ascii'), hashlib.sha256).digest() if not hmac.compare_digest(server_signature, alleged_server_sig): session.log.error("Verification of server SCRAM signature failed") session.leave( u"wamp.error.cannot_authenticate", u"Verification of server signature failed", ) else: session.log.info( "Verification of server SCRAM signature successful" ) session.on('join', confirm_server_signature) return base64.b64encode(client_proof)