Changed the way the master password hashing works

Switched from Bcrypt to Scrypt for master password hashing
Changed models to use new sizes for hashes and salts, doubled the size of enc_login and enc_passwd for accounts
Created new function to check master password validity
Increased salt sizes for accounts and master passwords
Removed bcrypt from requirements
This commit is contained in:
StNicolay 2022-11-04 00:34:01 +03:00
parent 66ab13b45d
commit a1bed9014d
5 changed files with 38 additions and 31 deletions

View File

@ -1,4 +1,3 @@
bcrypt
cryptography
pymysql
python-dotenv

View File

@ -219,7 +219,7 @@ def _add_account5(
return send_tmp_message(bot, mes.chat.id, "Успешная отмена")
salt, hash_ = database.get.get_master_pass(engine, mes.from_user.id)
if cryptography.master_pass.encrypt_master_pass(text, salt) != hash_:
if not cryptography.master_pass.check_master_pass(text, hash_, salt):
return send_tmp_message(bot, mes.chat.id, "Не подходит главный пароль")
name, login, passwd = data["name"], data["login"], data["passwd"]
@ -281,7 +281,7 @@ def _get_account3(
master_salt, hash_pass = database.get.get_master_pass(engine, mes.from_user.id)
if cryptography.master_pass.encrypt_master_pass(text, master_salt) != hash_pass:
if not cryptography.master_pass.check_master_pass(text, hash_pass, master_salt):
return send_tmp_message(bot, mes.chat.id, "Не подходит мастер пароль")
salt, enc_login, enc_pass = database.get.get_account_info(
@ -378,7 +378,7 @@ def _export2(
return send_tmp_message(bot, mes.chat.id, "Успешная отмена")
master_salt, hash_pass = database.get.get_master_pass(engine, mes.from_user.id)
if cryptography.master_pass.encrypt_master_pass(text, master_salt) != hash_pass:
if not cryptography.master_pass.check_master_pass(text, hash_pass, master_salt):
return send_tmp_message(bot, mes.chat.id, "Не подходит мастер пароль")
accounts = get_all_accounts(engine, mes.from_user.id, text)
@ -445,7 +445,7 @@ def _import3(
return send_tmp_message(bot, mes.chat.id, "Успешная отмена")
master_salt, hash_pass = database.get.get_master_pass(engine, mes.from_user.id)
if cryptography.master_pass.encrypt_master_pass(text, master_salt) != hash_pass:
if not cryptography.master_pass.check_master_pass(text, hash_pass, master_salt):
return send_tmp_message(bot, mes.chat.id, "Не подходит мастер пароль")
# List of names of accounts, which failed to be added to the database or failed tests

View File

@ -1,26 +1,35 @@
from typing import overload
import os
import bcrypt
from cryptography.exceptions import InvalidKey
from cryptography.hazmat.primitives.kdf.scrypt import Scrypt
_memory_use = 2**14
@overload
def encrypt_master_pass(passwd: str, salt: bytes) -> bytes:
...
def _get_kdf(salt: bytes) -> Scrypt:
kdf = Scrypt(
salt=salt,
length=128,
n=_memory_use,
r=8,
p=1,
)
return kdf
@overload
def encrypt_master_pass(passwd: str) -> tuple[bytes, bytes]:
...
def encrypt_master_pass(
passwd: str, salt: bytes | None = None
) -> tuple[bytes, bytes] | bytes:
"""Hashes master password and return tuple of hashed password and salt"""
if salt is None:
salt = bcrypt.gensalt()
gened_salt = True
salt = os.urandom(64)
kdf = _get_kdf(salt)
return kdf.derive(passwd.encode("utf-8")), salt
def check_master_pass(passwd: str, enc_pass: bytes, salt: bytes) -> bool:
"""Checks if the master password is correct"""
kdf = _get_kdf(salt)
try:
kdf.verify(passwd.encode("utf-8"), enc_pass)
except InvalidKey:
return False
else:
gened_salt = False
hashed = bcrypt.hashpw(passwd.encode("utf-8"), salt)
return (hashed, salt) if gened_salt else hashed
return True

View File

@ -1,6 +1,5 @@
import base64
import bcrypt
import os
from cryptography.fernet import Fernet
from cryptography.hazmat.backends import default_backend
@ -25,7 +24,7 @@ def encrypt_account_info(
) -> tuple[bytes, bytes, bytes]:
"""Encrypts login and password of a user using their master password as a key.
Returns a tuple of encrypted login, password and salt"""
salt = bcrypt.gensalt()
salt = os.urandom(64)
key = _generate_key(salt, master_pass)
f = Fernet(key)
enc_login = f.encrypt(login.encode("utf-8"))

View File

@ -8,10 +8,10 @@ class MasterPass(sqlmodel.SQLModel, table=True):
id: Optional[int] = sqlmodel.Field(primary_key=True)
user_id: int = sqlmodel.Field(nullable=False, index=True, unique=True)
salt: bytes = sqlmodel.Field(
sa_column=sqlmodel.Column(sqlmodel.VARBINARY(255), nullable=False)
sa_column=sqlmodel.Column(sqlmodel.BINARY(64), nullable=False)
)
passwd: bytes = sqlmodel.Field(
sa_column=sqlmodel.Column(sqlmodel.VARBINARY(255), nullable=False)
sa_column=sqlmodel.Column(sqlmodel.BINARY(128), nullable=False)
)
@ -22,11 +22,11 @@ class Account(sqlmodel.SQLModel, table=True):
user_id: int = sqlmodel.Field(nullable=False, index=True)
name: str = sqlmodel.Field(nullable=False, index=True, max_length=255)
salt: bytes = sqlmodel.Field(
sa_column=sqlmodel.Column(sqlmodel.VARBINARY(255), nullable=False)
sa_column=sqlmodel.Column(sqlmodel.BINARY(64), nullable=False)
)
enc_login: bytes = sqlmodel.Field(
sa_column=sqlmodel.Column(sqlmodel.VARBINARY(255), nullable=False)
sa_column=sqlmodel.Column(sqlmodel.VARBINARY(500), nullable=False)
)
enc_pass: bytes = sqlmodel.Field(
sa_column=sqlmodel.Column(sqlmodel.VARBINARY(255), nullable=False)
sa_column=sqlmodel.Column(sqlmodel.VARBINARY(500), nullable=False)
)