def derive_reply_keys(message_id): """Derive the lookup key and encrytion key from an aliased message id.""" algorithm = hashes.SHA256() hkdf = HKDFExpand(algorithm=algorithm, length=16, info=b"replay replies lookup key") lookup_key = hkdf.derive(message_id) hkdf = HKDFExpand(algorithm=algorithm, length=32, info=b"replay replies encryption key") encryption_key = hkdf.derive(message_id) return (lookup_key, encryption_key)
def test_already_finalized(self, backend): info = binascii.unhexlify(b"f0f1f2f3f4f5f6f7f8f9") hkdf = HKDFExpand(hashes.SHA256(), 42, info, backend) hkdf.derive(b"first") with pytest.raises(AlreadyFinalized): hkdf.derive(b"second")
def test_already_finalized(self, backend): info = binascii.unhexlify(b"f0f1f2f3f4f5f6f7f8f9") hkdf = HKDFExpand(hashes.SHA256(), 42, info, backend) hkdf.derive(b"first") with pytest.raises(AlreadyFinalized): hkdf.derive(b"second")
def generate_session_keys(self, shared_secret): hkdf = HKDFExpand(algorithm=hashes.SHA256(), backend=default_backend(), length=72, info=b"key_generation") key = hkdf.derive(shared_secret) kf = key[:32] kb = key[32:64] sf = key[64:68] sb = key[68:72] return [kf, kb, sf, sb, 1, 1]
def generate_session_keys(self, shared_secret): hkdf = HKDFExpand(algorithm=hashes.SHA256(), backend=default_backend(), length=40, info=b"key_generation") key = hkdf.derive(shared_secret) kf = key[:16] kb = key[16:32] sf = key[32:36] sb = key[36:40] return [kf, kb, sf, sb, 1, 1]
def aeskeygen(shared_key): backend = default_backend() info = b"hkdf-example" hkdf = HKDFExpand(algorithm=hashes.SHA256(), length=32, info=info, backend=backend) key = hkdf.derive(shared_key) return key
def hkdf_expand_test(backend, algorithm, params): hkdf = HKDFExpand(algorithm, int(params["l"]), info=binascii.unhexlify(params["info"]) or None, backend=backend) okm = hkdf.derive(binascii.unhexlify(params["prk"])) assert okm == binascii.unhexlify(params["okm"])
def test_derive(self, backend): prk = binascii.unhexlify(b"077709362c2e32df0ddc3f0dc47bba6390b6c73bb50f9c3122ec844ad7c2b3e5") okm = b"3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c" b"5bf34007208d5b887185865" info = binascii.unhexlify(b"f0f1f2f3f4f5f6f7f8f9") hkdf = HKDFExpand(hashes.SHA256(), 42, info, backend) assert binascii.hexlify(hkdf.derive(prk)) == okm
def aeskeygen(shared_key): backend = default_backend() info = b"hkdf-example" hkdf = HKDFExpand( algorithm=hashes.SHA256(), length=32, info=info, backend=backend ) key = hkdf.derive(shared_key) return key
def hkdf_expand_test(backend, algorithm, params): hkdf = HKDFExpand( algorithm, int(params["l"]), info=binascii.unhexlify(params["info"]) or None, backend=backend ) okm = hkdf.derive(binascii.unhexlify(params["prk"])) assert okm == binascii.unhexlify(params["okm"])
def test_derive(self, backend): prk = binascii.unhexlify( b"077709362c2e32df0ddc3f0dc47bba6390b6c73bb50f9c3122ec844ad7c2b3e5" ) okm = (b"3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c" b"5bf34007208d5b887185865") info = binascii.unhexlify(b"f0f1f2f3f4f5f6f7f8f9") hkdf = HKDFExpand(hashes.SHA256(), 42, info, backend) assert binascii.hexlify(hkdf.derive(prk)) == okm
async def ingameRegistration(request: Request) -> Union[dict, bytes]: start = time.time() mpargs = request.args name = mpargs["user[username]"].strip() # what is this setup osu lol email = mpargs["user[user_email]"].strip() pw = mpargs["user[password]"].strip() if not mpargs.get("check") or not all((name, email, pw)): return b"missing required paramaters" errors = defaultdict(list) if " " in name and "_" in name: errors["username"].append('Username cannot contain both "_" and " "') if await glob.db.fetchval("SELECT 1 FROM users WHERE name = %s", [name]): errors["username"].append("Username already taken!") if await glob.db.fetchval("SELECT 1 FROM users WHERE email = %s", [email]): errors["user_email"].append("Email already in use!") if not len(pw) >= 8: errors["password"].append("Password must be 8+ characters!") if errors: return {"form_error": {"user": errors}} if int(mpargs["check"]) == 0: md5 = hashlib.md5(pw.encode()).hexdigest().encode() k = HKDFExpand( algorithm=hashes.SHA256(), length=32, info=b"", backend=backend(), ) bc = k.derive(md5).decode("unicode-escape") glob.cache["pw"][bc] = md5 uid = await glob.db.execute( "INSERT INTO users (name, email, pw, safe_name, registered_at) VALUES (%s, %s, %s, %s, %s)", [name, email, bc, name.lower().replace(" ", "_"), time.time()], ) await glob.db.execute("INSERT INTO stats (id) VALUES (%s)", [uid]) info( f"{name} successfully registered. | Time Elapsed: {(time.time() - start) * 1000:.2f}ms", ) return b"ok"
async def ingameRegistration(request: Request) -> Union[dict, bytes]: start = time.time() mpargs = request.args name = mpargs['user[username]'].strip() # what is this setup osu lol email = mpargs['user[user_email]'].strip() pw = mpargs['user[password]'].strip() if not mpargs.get('check') or not all((name, email, pw)): return b'missing required paramaters' errors = defaultdict(list) if ' ' in name and '_' in name: errors['username'].append('Username cannot contain both "_" and " "') if await glob.db.fetchval("SELECT 1 FROM users WHERE name = %s", [name]): errors['username'].append('Username already taken!') if await glob.db.fetchval("SELECT 1 FROM users WHERE name = %s", [email]): errors['user_email'].append('Email already in use!') if not len(pw) >= 8: errors['password'].append('Password must be 8+ characters!') if errors: return {'form_error': {'user': errors}} if int(mpargs['check']) == 0: md5 = hashlib.md5(pw.encode()).hexdigest().encode() k = HKDFExpand(algorithm=hashes.SHA256(), length=32, info=b'', backend=backend()) bc = k.derive(md5).decode('unicode-escape') glob.cache['pw'][bc] = md5 uid = await glob.db.execute( "INSERT INTO users (name, email, pw, safe_name, registered_at) VALUES (%s, %s, %s, %s, %s)", [name, email, bc, name.lower().replace(' ', '_'), time.time()]) await glob.db.execute('INSERT INTO stats (id) VALUES (%s)', [uid]) log( f'{name} successfully registered. | Time Elapsed: {(time.time() - start) * 1000:.2f}ms', Ansi.LBLUE) return b'ok'
def derive_key(key_material, identifier, length=32, strong=False, backend=DEFAULT_BACKEND): if not isinstance(key_material, bytes): raise TypeError('key_material must be bytes.') if not isinstance(identifier, bytes): raise TypeError('identifier must be bytes.') if strong: kdf = HKDFExpand(algorithm=DEFAULT_HASH, length=length, info=identifier, backend=backend) else: kdf = PBKDF2HMAC(algorithm=DEFAULT_HASH, length=length, salt=identifier, iterations=131072, backend=backend) return kdf.derive(key_material)
def expand(self, prk, info, L): h = self.hash hkdf = HKDFExpand(h, L, info, default_backend()) return hkdf.derive(prk)
def hkdf(key, length=16, info=''): hkdf = HKDFExpand(algorithm=SHA256(), length=length, info=info, backend=bend) return hkdf.derive(key)
def derive_new_key(key): hkdf = HKDFExpand(algorithm=hashes.SHA256(), length=BLOCK_SIZE, info=None, backend=BACKEND) return hkdf.derive(key)
def getBitwardenSecrets(email, password, kdfIterations, encKey, encPrivateKey): BitwardenSecrets = {} BitwardenSecrets['email'] = email BitwardenSecrets['kdfIterations'] = kdfIterations BitwardenSecrets['MasterPassword'] = password BitwardenSecrets['ProtectedSymmetricKey'] = encKey BitwardenSecrets['ProtectedRSAPrivateKey'] = encPrivateKey kdf = PBKDF2HMAC( algorithm=hashes.SHA256(), length=32, salt=bytes(BitwardenSecrets['email'], 'utf-8'), iterations=BitwardenSecrets['kdfIterations'], backend=default_backend() ) BitwardenSecrets['MasterKey'] = kdf.derive(BitwardenSecrets['MasterPassword']) BitwardenSecrets['MasterKey_b64'] = base64.b64encode(BitwardenSecrets['MasterKey']).decode('utf-8') kdf = PBKDF2HMAC( algorithm=hashes.SHA256(), length=32, salt=bytes(BitwardenSecrets['MasterPassword']), iterations=1, backend=default_backend() ) BitwardenSecrets['MasterPasswordHash'] = base64.b64encode(kdf.derive(BitwardenSecrets['MasterKey'])).decode('utf-8') hkdf = HKDFExpand( algorithm=hashes.SHA256(), length=32, info=b"enc", backend=default_backend() ) BitwardenSecrets['StretchedEncryptionKey'] = hkdf.derive(BitwardenSecrets['MasterKey']) BitwardenSecrets['StretchedEncryptionKey_b64'] = base64.b64encode(BitwardenSecrets['StretchedEncryptionKey']).decode('utf-8') hkdf = HKDFExpand( algorithm=hashes.SHA256(), length=32, info=b"mac", backend=default_backend() ) BitwardenSecrets['StretchedMacKey'] = hkdf.derive(BitwardenSecrets['MasterKey']) BitwardenSecrets['StretchedMacKey_b64'] = base64.b64encode(BitwardenSecrets['StretchedMacKey']).decode('utf-8') BitwardenSecrets['StretchedMasterKey'] = BitwardenSecrets['StretchedEncryptionKey'] + BitwardenSecrets['StretchedMacKey'] BitwardenSecrets['StretchedMasterKey_b64'] = base64.b64encode(BitwardenSecrets['StretchedMasterKey']).decode('utf-8') BitwardenSecrets['GeneratedSymmetricKey'], \ BitwardenSecrets['GeneratedEncryptionKey'], \ BitwardenSecrets['GeneratedMACKey'] = decryptMasterEncryptionKey(BitwardenSecrets['ProtectedSymmetricKey'], BitwardenSecrets['StretchedEncryptionKey'], BitwardenSecrets['StretchedMacKey']) BitwardenSecrets['GeneratedSymmetricKey_b64'] = base64.b64encode(BitwardenSecrets['GeneratedSymmetricKey']).decode('utf-8') BitwardenSecrets['GeneratedEncryptionKey_b64'] = base64.b64encode(BitwardenSecrets['GeneratedEncryptionKey']).decode('utf-8') BitwardenSecrets['GeneratedMACKey_b64'] = base64.b64encode(BitwardenSecrets['GeneratedMACKey']).decode('utf-8') BitwardenSecrets['RSAPrivateKey'] = decryptRSAPrivateKey(BitwardenSecrets['ProtectedRSAPrivateKey'], \ BitwardenSecrets['GeneratedEncryptionKey'], \ BitwardenSecrets['GeneratedMACKey']) return(BitwardenSecrets)
def hkdf_sha256(key, length=16, info=''): hkdf = HKDFExpand(algorithm=hashes.SHA256(), length=length, info=info, backend=bend) return hkdf.derive(key)
def test_unicode_error(self, backend): info = binascii.unhexlify(b"f0f1f2f3f4f5f6f7f8f9") hkdf = HKDFExpand(hashes.SHA256(), 42, info, backend) with pytest.raises(TypeError): hkdf.derive("first") # type: ignore[arg-type]
warning( f"{username}'s login attempt failed: provided an incorrect password", ) request.resp_headers[ "cho-token" ] = "no" # client knows there is something up if we set token to 'no' return writer.userID(-1) else: # correct password, we allow the user to continue but lets convert the password to our new format first k = HKDFExpand( algorithm=hashes.SHA256(), length=32, info=b"", backend=backend(), ) new_pw = k.derive(pw).decode("unicode-escape") await glob.db.execute( "UPDATE users SET pw = %s WHERE id = %s", [new_pw, user["id"]], ) # add to cache for the future glob.cache["pw"][new_pw] = pw else: # password is already converted or db already has correct formats bcache = glob.cache["pw"] # get our cached pws to potentially enhance speed user_pw = ( user["pw"] .encode("ISO-8859-1") .decode("unicode-escape") .encode("ISO-8859-1")
request.resp_headers['cho-token'] = 'no' # client knows there is something up if we set token to 'no' return writer.userID(-1) # if server is migrated then passwords are previously stored as bcrypt # lets check if we need to convert and do so if needed if glob.config.server_migration and ('$' in user['pw'] and len(user['pw']) == 60): user_pw = user['pw'].encode() if not bcrypt.checkpw(pw, user_pw): if glob.config.debug: log(f"{username}'s login attempt failed: provided an incorrect password", Ansi.LRED) request.resp_headers['cho-token'] = 'no' # client knows there is something up if we set token to 'no' return writer.userID(-1) else: # correct password, we allow the user to continue but lets convert the password to our new format first k = HKDFExpand(algorithm=hashes.SHA256(), length=32, info=b'', backend=backend()) new_pw = k.derive(pw).decode('unicode-escape') await glob.db.execute('UPDATE users SET pw = %s WHERE id = %s', [new_pw, user['id']]) # add to cache for the future glob.cache['pw'][new_pw] = pw else: # password is already converted or db already has correct formats bcache = glob.cache['pw'] # get our cached pws to potentially enhance speed user_pw = user['pw'].encode('ISO-8859-1').decode('unicode-escape').encode('ISO-8859-1') # this is cursed SHUT UP if user_pw in bcache: if pw != bcache[user_pw]: # compare provided md5 with the stored (cached) pw to ensure they have provided the correct password if glob.config.debug: log(f"{username}'s login attempt failed: provided an incorrect password", Ansi.LRED) request.resp_headers['cho-token'] = 'no' # client knows there is something up if we set token to 'no'
def test_unicode_error(self, backend): info = binascii.unhexlify(b"f0f1f2f3f4f5f6f7f8f9") hkdf = HKDFExpand(hashes.SHA256(), 42, info, backend) with pytest.raises(TypeError): hkdf.derive(u"first")
def expand(self, prk, info, L): h = self.hash hkdf = HKDFExpand(h, L, info, default_backend()) return hkdf.derive(prk)
async def register_post(): if 'authenticated' in session: return await flash('error', "You're already logged in.", 'home') if not glob.config.registration: return await flash('error', 'Registrations are currently disabled.', 'home') form = await request.form username = form.get('username', type=str) email = form.get('email', type=str) passwd_txt = form.get('password', type=str) if username is None or email is None or passwd_txt is None: return await flash('error', 'Invalid parameters.', 'home') if glob.config.hCaptcha_sitekey != 'changeme': captcha_data = form.get('h-captcha-response', type=str) if ( captcha_data is None or not await utils.validate_captcha(captcha_data) ): return await flash('error', 'Captcha failed.', 'register') # Usernames must: # - be within 2-15 characters in length # - not contain both ' ' and '_', one is fine # - not be in the config's `disallowed_names` list # - not already be taken by another player # check if username exists if not regexes.username.match(username): return await flash('error', 'Invalid username syntax.', 'register') if '_' in username and ' ' in username: return await flash('error', 'Username may contain "_" or " ", but not both.', 'register') if username in glob.config.disallowed_names: return await flash('error', 'Disallowed username; pick another.', 'register') if await glob.db.fetch('SELECT 1 FROM users WHERE name = %s', username): return await flash('error', 'Username already taken by another user.', 'register') # Emails must: # - match the regex `^[^@\s]{1,200}@[^@\s\.]{1,30}\.[^@\.\s]{1,24}$` # - not already be taken by another player if not regexes.email.match(email): return await flash('error', 'Invalid email syntax.', 'register') if await glob.db.fetch('SELECT 1 FROM users WHERE email = %s', email): return await flash('error', 'Email already taken by another user.', 'register') # Passwords must: # - be within 8-32 characters in length # - have more than 3 unique characters # - not be in the config's `disallowed_passwords` list if not 8 <= len(passwd_txt) <= 32: return await flash('error', 'Password must be 8-32 characters in length.', 'register') if len(set(passwd_txt)) <= 3: return await flash('error', 'Password must have more than 3 unique characters.', 'register') if passwd_txt.lower() in glob.config.disallowed_passwords: return await flash('error', 'That password was deemed too simple.', 'register') # TODO: add correct locking # (start of lock) pw_md5 = hashlib.md5(passwd_txt.encode()).hexdigest().encode() k = HKDFExpand(algorithm=hashes.SHA256(), length=32, info=b'', backend=backend()) pw_hash = k.derive(pw_md5).decode('unicode-escape') glob.cache['pw'][pw_hash] = pw_md5 # cache pw safe_name = utils.get_safe_name(username) # fetch the users' country if 'CF-Connecting-IP' in request.headers: ip = request.headers['CF-Connecting-IP'] else: ip = request.headers['X-Forwarded-For'].split(',')[0] try: country = await utils.fetch_geoloc(ip) except: country = 'xx' user = await glob.db.execute( 'INSERT INTO users ' '(name, safe_name, email, pw, country, registered_at) ' 'VALUES (%s, %s, %s, %s, %s, UNIX_TIMESTAMP())', [username, safe_name, email, pw_hash, country] ) user_id = user # add to `stats` table. await glob.db.execute( 'INSERT INTO stats ' '(id) VALUES (%s)', user_id ) # (end of lock) if glob.config.debug: log(f'{username} has registered - awaiting verification.', Ansi.LGREEN) # user has successfully registered return await render_template('verify.html')
async def settings_password_post(): form = await request.form old_password = form.get('old_password') new_password = form.get('new_password') repeat_password = form.get('repeat_password') # new password and repeat password don't match; deny post if new_password != repeat_password: return await flash('error', "Your new password doesn't match your repeated password!", 'settings/password') # new password and old password match; deny post if old_password == new_password: return await flash('error', 'Your new password cannot be the same as your old password!', 'settings/password') # Passwords must: # - be within 8-32 characters in length # - have more than 3 unique characters # - not be in the config's `disallowed_passwords` list if not 8 < len(new_password) <= 32: return await flash('error', 'Your new password must be 8-32 characters in length.', 'settings/password') if len(set(new_password)) <= 3: return await flash('error', 'Your new password must have more than 3 unique characters.', 'settings/password') if new_password.lower() in glob.config.disallowed_passwords: return await flash('error', 'Your new password was deemed too simple.', 'settings/password') # cache and other password related information pw_cache = glob.cache['pw'] pw_hash = (await glob.db.fetchval( 'SELECT pw ' 'FROM users ' 'WHERE id = %s', [session['user_data']['id']]) ).encode('ISO-8859-1').decode('unicode-escape').encode('ISO-8859-1') pw_md5 = hashlib.md5(old_password.encode()).hexdigest().encode() # check old password against db # intentionally slow, will cache to speed up if pw_hash in pw_cache: if pw_md5 != pw_cache[pw_hash]: # ~0.1ms if glob.config.debug: log(f"{session['user_data']['name']}'s change pw failed - pw incorrect.", Ansi.LYELLOW) return await flash('error', 'Your old password is incorrect.', 'settings/password') else: # ~200ms k = HKDFExpand(algorithm=hashes.SHA256(), length=32, info=b'', backend=backend()) try: k.verify(pw_hash, pw_md5) except: if glob.config.debug: log(f"{session['user_data']['name']}'s change pw failed - pw incorrect.", Ansi.LYELLOW) return await flash('error', 'Your old password is incorrect.', 'settings/password') # remove old password from cache if pw_hash in pw_cache: del pw_cache[pw_hash] # calculate new md5 & bcrypt pw pw_md5 = hashlib.md5(new_password.encode()).hexdigest().encode() k = HKDFExpand(algorithm=hashes.SHA256(), length=32, info=b'', backend=backend()) pw_hash_new = k.derive(pw_md5).decode('unicode-escape') # update password in cache and db pw_cache[pw_hash_new] = pw_md5 await glob.db.execute( 'UPDATE users ' 'SET pw = %s ' 'WHERE safe_name = %s', [pw_hash_new, utils.get_safe_name(session['user_data']['name'])] ) # logout session.pop('authenticated', None) session.pop('user_data', None) return await flash('success', 'Your password has been changed! Please log in again.', 'login')