def testUnit(): clientFinalMessageBare = "c=biws,r=" + "fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j" saltedPassword = pbkdf2_hmac("sha1", saslprep(unicode("pencil")), b64decode("QSXCR+Q6sek8bf92"), 4096) print saltedPassword.encode("hex") clientKey = hmac.new(saltedPassword, "Client Key", sha1) print clientKey.hexdigest() storedKey = sha1(clientKey.digest()) print storedKey.hexdigest() authMessage = "n=user,r=fyko+d2lbbFgONRv9qkxdawL,r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,s=QSXCR+Q6sek8bf92,i=4096,c=biws,r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j" clientSignature = hmac.new(storedKey.digest(), authMessage, sha1) print clientSignature.hexdigest() clientProof = _strxor(clientKey.digest(), clientSignature.digest()) print clientProof.encode("hex") serverKey = hmac.new(saltedPassword, "Server Key", sha1) print serverKey.hexdigest() serverSignature = hmac.new(serverKey.digest(), authMessage, sha1) print serverSignature.hexdigest() clientFinalMessage = clientFinalMessageBare + ",p=" + b64encode(clientProof) print clientFinalMessage serverFinalMessage = "v=" + b64encode(serverSignature.digest()) print serverFinalMessage
def derive_digest(cls, 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) :type password: unicode or utf-8 bytes :arg password: password to run through digest :type salt: bytes :arg salt: raw salt data :type rounds: int :arg rounds: number of iterations. :type alg: str :arg alg: name of digest to use (e.g. ``"sha-1"``). :returns: raw bytes of ``SaltedPassword`` """ if isinstance(password, bytes): password = password.decode("utf-8") # NOTE: pbkdf2_hmac() will encode secret & salt using utf-8, # and handle normalizing alg name. return pbkdf2_hmac(alg, saslprep(password), salt, rounds)
def derive_digest(cls, 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) :type password: unicode or utf-8 bytes :arg password: password to run through digest :type salt: bytes :arg salt: raw salt data :type rounds: int :arg rounds: number of iterations. :type alg: str :arg alg: name of digest to use (e.g. ``"sha-1"``). :returns: raw bytes of ``SaltedPassword`` """ if isinstance(password, bytes): password = password.decode("utf-8") password = saslprep(password).encode("utf-8") if not isinstance(salt, bytes): raise TypeError("salt must be bytes") if rounds < 1: raise ValueError("rounds must be >= 1") alg = norm_hash_name(alg, "hashlib") return pbkdf2(password, salt, rounds, None, "hmac-" + alg)
def _create_admin(self): admin_data = { "username": "******", "password": pbkdf2_sha256.encrypt(saslprep("admin")), "admin": True, "preferred_channel": "email", "channels": { "email": { "address": "*****@*****.**" }, "facebook": { "user_id": "user" }, "telegram": { "user_id": "user" }, "irc": { "nickname": "user", "network": "user" }, "slack": { "channel": "user", "username": "******" } } } cursor = self.user_collection.find({'username': '******'}) if cursor.count() == 0: self.user_collection.insert_one(admin_data) return else: return
def post(self): """ Post a new user to the database. Make a dictionary to pass to the db_handler.""" try: validate(request.json, user_schema) except Exception as e: error_msg = str(e).split("\n") return {"msg": "error with input data:" + str(error_msg[0])}, 400 user_data = {} data = request.get_json() user_data["username"] = data.get("username") user_data["password"] = pbkdf2_sha256.encrypt(saslprep( data.get("password")), rounds=200000, salt_size=16) user_data["preferred_channel"] = data.get("preferred_channel") user_data["channels"] = data.get("channels") user_data["admin"] = False response = self.db_handler.create_user(user_data) if response == "used": return {'msg': "Username already in use"}, 400 elif response is None: return {"msg": "Error during data handling"} else: return {"msg": "User created", "user_id": response}, 200
def create_authentication(password, salt): hash_data = argon2.low_level.hash_secret( secret=saslprep(password).encode('ascii'), salt=salt, time_cost=4096, memory_cost=512, parallelism=1, hash_len=32, type=argon2.low_level.Type.ID, version=0x13, ) _, 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() return { 'memory': int(params['m']), 'kdf': 'argon2id-13', 'iterations': int(params['t']), 'salt': binascii.b2a_hex(salt).decode('ascii'), 'storedKey': binascii.b2a_hex(stored_key).decode('ascii'), 'serverKey': binascii.b2a_hex(server_key).decode('ascii') }
def on_challenge(self, session, challenge): assert challenge.method == u"scram" assert self._client_nonce is not None required_args = ['nonce', 'kdf', 'salt', 'iterations'] optional_args = ['memory', 'channel_binding'] 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'].encode('ascii') # base64 salt = challenge.extra[u'salt'] # base64 iterations = int(challenge.extra[u'iterations']) memory = int(challenge.extra.get(u'memory', -1)) password = self._args['password'].encode('utf8') # supplied by user authid = saslprep(self._args['authid']) algorithm = challenge.extra[u'kdf'] client_nonce = self._client_nonce self._auth_message = ( u"{client_first_bare},{server_first},{client_final_no_proof}". format( client_first_bare=u"n={},r={}".format(authid, client_nonce), server_first=u"r={},s={},i={}".format( server_nonce.decode('ascii'), salt.decode('ascii'), iterations), client_final_no_proof=u"c={},r={}".format( channel_binding, server_nonce.decode('ascii')), ).encode('ascii')) if algorithm == u'argon2id-13': if memory == -1: raise ValueError( "WAMP-SCRAM 'argon2id-13' challenge requires 'memory' parameter" ) self._salted_password = _hash_argon2id13_secret( password, salt, iterations, memory) elif algorithm == u'pbkdf2': self._salted_password = _hash_pbkdf2_secret( password, salt, iterations) else: raise RuntimeError( "WAMP-SCRAM specified unknown KDF '{}'".format(algorithm)) client_key = hmac.new(self._salted_password, b"Client Key", hashlib.sha256).digest() stored_key = hashlib.new('sha256', client_key).digest() client_signature = hmac.new(stored_key, self._auth_message, hashlib.sha256).digest() client_proof = xor_array(client_key, client_signature) return base64.b64encode(client_proof)
def on_challenge(self, session, challenge): assert challenge.method == u"scram" assert self._client_nonce is not None required_args = ['nonce', 'kdf', 'salt', 'iterations'] optional_args = ['memory', 'channel_binding'] 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'].encode('ascii') # base64 salt = challenge.extra[u'salt'] # base64 iterations = int(challenge.extra[u'iterations']) memory = int(challenge.extra.get(u'memory', -1)) password = self._args['password'].encode('utf8') # supplied by user authid = saslprep(self._args['authid']) algorithm = challenge.extra[u'kdf'] client_nonce = self._client_nonce self._auth_message = ( u"{client_first_bare},{server_first},{client_final_no_proof}".format( client_first_bare=u"n={},r={}".format(authid, client_nonce), server_first=u"r={},s={},i={}".format(server_nonce.decode('ascii'), salt.decode('ascii'), iterations), client_final_no_proof=u"c={},r={}".format(channel_binding, server_nonce.decode('ascii')), ).encode('ascii') ) if algorithm == u'argon2id-13': if memory == -1: raise ValueError( "WAMP-SCRAM 'argon2id-13' challenge requires 'memory' parameter" ) self._salted_password = _hash_argon2id13_secret(password, salt, iterations, memory) elif algorithm == u'pbkdf2': self._salted_password = _hash_pbkdf2_secret(password, salt, iterations) else: raise RuntimeError( "WAMP-SCRAM specified unknown KDF '{}'".format(algorithm) ) client_key = hmac.new(self._salted_password, b"Client Key", hashlib.sha256).digest() stored_key = hashlib.new('sha256', client_key).digest() client_signature = hmac.new(stored_key, self._auth_message, hashlib.sha256).digest() client_proof = xor_array(client_key, client_signature) return base64.b64encode(client_proof)
def mysaslprep(pwd): try: if type(pwd) is not str: pwd = pwd.decode('utf8') pwd = saslprep(pwd) except (UnicodeDecodeError, ValueError): pass if type(pwd) is str: return pwd.encode('utf8') else: return pwd
def on_connect(self): auth_methods = [u'scram'] auth_role = u'user' authid = u'{}'.format(self.component_config.session.username) password = u'{}'.format(self.component_config.session.password) self.authenticator = create_authenticator(AuthScram.name, authid=authid, password=saslprep(password)) self.join(self.config.realm, authmethods=auth_methods, authid=authid, authrole=auth_role, authextra=self.authenticator.authextra)
def authenticate(self, signed_message): """ Verify the signed message sent by the client. :param signed_message: the base64-encoded result "ClientProof" from the SCRAM protocol """ channel_binding = "" client_nonce = base64.b64encode(self._client_nonce).decode('ascii') server_nonce = base64.b64encode(self._server_nonce).decode('ascii') salt = base64.b64encode(self._salt).decode('ascii') auth_message = ( "{client_first_bare},{server_first},{client_final_no_proof}". format( client_first_bare="n={},r={}".format(saslprep(self._authid), client_nonce), server_first="r={},s={},i={}".format(server_nonce, salt, self._iterations), client_final_no_proof="c={},r={}".format( channel_binding, server_nonce), )) received_client_proof = base64.b64decode(signed_message) client_signature = hmac.new(self._stored_key, auth_message.encode('ascii'), hashlib.sha256).digest() recovered_client_key = util.xor(client_signature, received_client_proof) recovered_stored_key = hashlib.new('sha256', recovered_client_key).digest() # if we adjust self._authextra before _accept() it gets sent # back to the client server_signature = hmac.new(self._server_key, auth_message.encode('ascii'), hashlib.sha256).digest() if self._authextra is None: self._authextra = {} self._authextra['scram_server_signature'] = base64.b64encode( server_signature).decode('ascii') if hmac.compare_digest(recovered_stored_key, self._stored_key): return self._accept() self.log.error("SCRAM authentication failed for '{authid}'", authid=self._authid) return types.Deny(message='SCRAM authentication failed')
def generate(cls, owner: User, username: str, password: str, alg: SCRAMAlgorithm, salt: Optional[bytes] = None, iterations: int = 4096): """ Generate SCRAM credentials, hashing the given password using the given algorithm. If salt is unset, a random one is used. If iterations is unset, 4096 (the Kafka default) is used. """ # Set number of rounds: scram_hasher = scram.using(rounds=iterations) # Set salt: if salt is not None: scram_hasher = scram_hasher.using(salt=salt) # Set algorithm (sha-1 must always be in the algorithm list): scram_hasher = scram_hasher.using(f"sha-1,{alg.iana_name()}") # Run the hash, taking care to normalize the password scram_hash = scram_hasher.hash(saslprep(password)) salt, _, hashed_pw = scram.extract_digest_info(scram_hash, alg.iana_name()) hash_func = alg.hash_function() server_key = hmac.new(hashed_pw, b"Server Key", hash_func).digest() client_key = hmac.new(hashed_pw, b"Client Key", hash_func).digest() stored_key = hash_func(client_key).digest() val = cls( owner=owner, username=username, algorithm=alg, salt=salt, server_key=server_key, stored_key=stored_key, iterations=iterations, string_encoding=cls.string_encode(salt, stored_key, server_key, iterations), ) return val
def patch(self, user_id): try: validate(request.json, user_patch_schema) except Exception as e: error_msg = str(e).split("\n") return {"msg": "error with input data:" + str(error_msg[0])}, 400 if self.check_authorization(user_id) is True: data = request.get_json() user_data = {} for key in data: if key == "admin" and self.check_admin( ) != True and data[key] not in [True, False]: return { "Error": "Admin field should be True or False. Olnly admin can modify this value." }, 400 elif key == "username": return { "Error": "Not modified. Cannot modify username" }, 400 elif key == "preferred_channel": if data[key] not in [ "email", "slack", "irc", "facebook", "telegram" ]: return {"Error": "Not modified. Channel unknown"}, 400 if key == "password": user_data[str(key)] = pbkdf2_sha256.encrypt(saslprep( data["password"]), rounds=200000, salt_size=16) else: user_data[str(key)] = data.get(key) response = self.db_handler.update_user(user_data, user_id) if response == 200: return {"msg": "modified"}, response else: return {"msg": "Not modified"}, response else: return {"msg": "Unauthorized"}, 401
def post(self): try: validate(request.json, login_schema) except Exception as e: error_msg = str(e).split("\n") return {"msg": "error with input data:" + str(error_msg[0])}, 400 parser = reqparse.RequestParser() parser.add_argument("username", location="json") parser.add_argument("password", location="json") args = parser.parse_args() user = self.db_handler.get_user_name(args['username']) if user is None: return {"msg": "Authentication error"}, 401 else: if pbkdf2_sha256.verify(saslprep(args["password"]), user["password"]): try: access_token = create_access_token( identity={ "username": user["username"], "admin": user["admin"], "_id": user["_id"] }) refresh_token = create_refresh_token( identity={ "username": user["username"], "admin": user["admin"], "_id": user["_id"] }) resp = jsonify({'msg': 'logged in'}) set_access_cookies(resp, access_token) set_refresh_cookies(resp, refresh_token) return resp except Exception as e: return {"msg": "Authentication error"}, 401 else: return {"msg": "Authentication error"}, 401
def authenticate(self, signed_message): """ Verify the signed message sent by the client. :param signed_message: the base64-encoded result "ClientProof" from the SCRAM protocol """ channel_binding = "" client_nonce = base64.b64encode(self._client_nonce).decode('ascii') server_nonce = base64.b64encode(self._server_nonce).decode('ascii') salt = base64.b64encode(self._salt).decode('ascii') auth_message = ( "{client_first_bare},{server_first},{client_final_no_proof}".format( client_first_bare="n={},r={}".format(saslprep(self._authid), client_nonce), server_first="r={},s={},i={}".format(server_nonce, salt, self._iterations), client_final_no_proof="c={},r={}".format(channel_binding, server_nonce), ) ) received_client_proof = base64.b64decode(signed_message) client_signature = hmac.new(self._stored_key, auth_message.encode('ascii'), hashlib.sha256).digest() recovered_client_key = util.xor(client_signature, received_client_proof) recovered_stored_key = hashlib.new('sha256', recovered_client_key).digest() # if we adjust self._authextra before _accept() it gets sent # back to the client server_signature = hmac.new(self._server_key, auth_message.encode('ascii'), hashlib.sha256).digest() if self._authextra is None: self._authextra = {} self._authextra['scram_server_signature'] = base64.b64encode(server_signature).decode('ascii') if hmac.compare_digest(recovered_stored_key, self._stored_key): return self._accept() self.log.error("SCRAM authentication failed for '{authid}'", authid=self._authid) return types.Deny(message=u'SCRAM authentication failed')
def checkPassword(passwd): clientFinalMessageBare = "c=biws,r=xxxxxxxxxxxxxxxxxxxxxxxxxxxx" saltedPassword = pbkdf2_hmac("sha1", saslprep(unicode(passwd)), b64decode("yyyyyyyyyyyyyyyyy"), 4096) #print saltedPassword.encode("hex") clientKey = hmac.new(saltedPassword, "Client Key", sha1) #print clientKey.hexdigest() storedKey = sha1(clientKey.digest()) #print storedKey.hexdigest() authMessage = "n=username,r=hydra,r=xxxxxxxxxxxxxxxxxxxxxxxxxxxx,s=yyyyyyyyyyyyyyyyy,i=4096,c=biws,r=xxxxxxxxxxxxxxxxxxxxxxxxxxxx" clientSignature = hmac.new(storedKey.digest(), authMessage, sha1) #print clientSignature.hexdigest() clientProof = _strxor(clientKey.digest(), clientSignature.digest()) #print clientProof.encode("hex") serverKey = hmac.new(saltedPassword, "Server Key", sha1) #print serverKey.hexdigest() serverSignature = hmac.new(serverKey.digest(), authMessage, sha1) #print serverSignature.hexdigest() clientFinalMessage = clientFinalMessageBare + ",p=" + b64encode(clientProof) #print clientFinalMessage serverFinalMessage = "v=" + b64encode(serverSignature.digest()) if (serverFinalMessage == "v=base64_encoded_string"): return True else: return False
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)
def set_password(self, password): """throws ValueError if password violates RFC 4013""" global PWD_CONTEXT self.pwd_hash = PWD_CONTEXT.hash(saslprep(password))
def generate_hash(user_password: str) -> str: """Generate a hashed version of the password.""" return pbkdf2_sha256.hash(saslprep(user_password))
print "password: "******"client Nonce: " + sys.argv[3] print "server Nonce: " + sys.argv[4] print "server salt: " + sys.argv[5] print "iteration: " + sys.argv[6] print "clientProof to test with: " + sys.argv[7] username = sys.argv[1] password = sys.argv[2] clientNonce = sys.argv[3] serverNonce = sys.argv[4] serverSalt = sys.argv[5] iteration = sys.argv[6] clientProofToFound = sys.argv[7] normalizedPassword = utils.saslprep(unicode(password)) clientInitMessage = "n=" + username + ",r=" + clientNonce serverInitReply = "r=" + clientNonce + serverNonce + ",s=" + serverSalt + ",i=" + iteration clientFinal = "c=biws,r=" + clientNonce + serverNonce saltedPassword = pbkdf2_sha1.using( rounds=iteration, salt=serverSalt.decode("base64")).hash(normalizedPassword).split('$')[-1] clientKey = hmac.new(utils.ab64_decode(saltedPassword), "Client Key", hashlib.sha1) storedKey = hashlib.sha1(clientKey.digest())
def _client_first_message_bare(username, c_nonce): return ','.join(('n=' + saslprep(username), 'r=' + c_nonce))