Compare commits

...

3 Commits

Author SHA1 Message Date
a1bed9014d 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
2022-11-07 16:30:45 +03:00
66ab13b45d Changed handlers.get_accounts to use a guard clause, added comment to clarify, why we are putting account in backticks 2022-11-07 16:29:37 +03:00
da42d7ad1d Changed comments in cryptograpy.other_accounts to be more precise 2022-11-07 16:29:31 +03:00
5 changed files with 55 additions and 44 deletions

View File

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

View File

@ -26,17 +26,19 @@ def get_accounts(
) -> None: ) -> None:
base_handler(bot, mes) base_handler(bot, mes)
accounts = database.get.get_accounts(engine, mes.from_user.id) accounts = database.get.get_accounts(engine, mes.from_user.id)
if accounts: if not accounts:
accounts = [f"`{account}`" for account in accounts] return send_tmp_message(bot, mes.chat.id, "У вас нет аккаунтов")
return send_tmp_message(
bot, # Make accounts copyable and escape special chars
mes.chat.id, accounts = [f"`{account}`" for account in accounts]
"Ваши аккаунты:\n" send_tmp_message(
+ "\n".join(accounts) bot,
+ "\nНажмите на название, чтобы скопировать", mes.chat.id,
30, "Ваши аккаунты:\n"
) + "\n".join(accounts)
send_tmp_message(bot, mes.chat.id, "У вас нет аккаунтов") + "\nНажмите на название, чтобы скопировать",
30,
)
def delete_all( def delete_all(
@ -217,7 +219,7 @@ def _add_account5(
return send_tmp_message(bot, mes.chat.id, "Успешная отмена") return send_tmp_message(bot, mes.chat.id, "Успешная отмена")
salt, hash_ = database.get.get_master_pass(engine, mes.from_user.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, "Не подходит главный пароль") return send_tmp_message(bot, mes.chat.id, "Не подходит главный пароль")
name, login, passwd = data["name"], data["login"], data["passwd"] name, login, passwd = data["name"], data["login"], data["passwd"]
@ -279,7 +281,7 @@ def _get_account3(
master_salt, hash_pass = database.get.get_master_pass(engine, mes.from_user.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, "Не подходит мастер пароль") return send_tmp_message(bot, mes.chat.id, "Не подходит мастер пароль")
salt, enc_login, enc_pass = database.get.get_account_info( salt, enc_login, enc_pass = database.get.get_account_info(
@ -376,7 +378,7 @@ def _export2(
return send_tmp_message(bot, mes.chat.id, "Успешная отмена") return send_tmp_message(bot, mes.chat.id, "Успешная отмена")
master_salt, hash_pass = database.get.get_master_pass(engine, mes.from_user.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, "Не подходит мастер пароль") return send_tmp_message(bot, mes.chat.id, "Не подходит мастер пароль")
accounts = get_all_accounts(engine, mes.from_user.id, text) accounts = get_all_accounts(engine, mes.from_user.id, text)
@ -443,7 +445,7 @@ def _import3(
return send_tmp_message(bot, mes.chat.id, "Успешная отмена") return send_tmp_message(bot, mes.chat.id, "Успешная отмена")
master_salt, hash_pass = database.get.get_master_pass(engine, mes.from_user.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, "Не подходит мастер пароль") return send_tmp_message(bot, mes.chat.id, "Не подходит мастер пароль")
# List of names of accounts, which failed to be added to the database or failed tests # 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 _get_kdf(salt: bytes) -> Scrypt:
def encrypt_master_pass(passwd: str, salt: bytes) -> bytes: 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) -> 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""" """Hashes master password and return tuple of hashed password and salt"""
if salt is None: salt = os.urandom(64)
salt = bcrypt.gensalt() kdf = _get_kdf(salt)
gened_salt = True 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: else:
gened_salt = False return True
hashed = bcrypt.hashpw(passwd.encode("utf-8"), salt)
return (hashed, salt) if gened_salt else hashed

View File

@ -1,6 +1,5 @@
import base64 import base64
import os
import bcrypt
from cryptography.fernet import Fernet from cryptography.fernet import Fernet
from cryptography.hazmat.backends import default_backend from cryptography.hazmat.backends import default_backend
@ -23,9 +22,9 @@ def _generate_key(salt: bytes, master_pass: bytes) -> bytes:
def encrypt_account_info( def encrypt_account_info(
login: str, passwd: str, master_pass: bytes login: str, passwd: str, master_pass: bytes
) -> tuple[bytes, bytes, bytes]: ) -> tuple[bytes, bytes, bytes]:
"""Encrypts login and password of a user using hash of their master password as a key. """Encrypts login and password of a user using their master password as a key.
Returns a tuple of encrypted login password and salt""" Returns a tuple of encrypted login, password and salt"""
salt = bcrypt.gensalt() salt = os.urandom(64)
key = _generate_key(salt, master_pass) key = _generate_key(salt, master_pass)
f = Fernet(key) f = Fernet(key)
enc_login = f.encrypt(login.encode("utf-8")) enc_login = f.encrypt(login.encode("utf-8"))
@ -36,6 +35,8 @@ def encrypt_account_info(
def decrypt_account_info( def decrypt_account_info(
enc_login: bytes, enc_pass: bytes, master_pass: bytes, salt: bytes enc_login: bytes, enc_pass: bytes, master_pass: bytes, salt: bytes
) -> tuple[str, str]: ) -> tuple[str, str]:
"""Decrypts login and password using their master password as a key.
Returns a tuple of decrypted login and password"""
key = _generate_key(salt, master_pass) key = _generate_key(salt, master_pass)
f = Fernet(key) f = Fernet(key)
login_bytes = f.decrypt(enc_login) login_bytes = f.decrypt(enc_login)

View File

@ -8,10 +8,10 @@ class MasterPass(sqlmodel.SQLModel, table=True):
id: Optional[int] = sqlmodel.Field(primary_key=True) id: Optional[int] = sqlmodel.Field(primary_key=True)
user_id: int = sqlmodel.Field(nullable=False, index=True, unique=True) user_id: int = sqlmodel.Field(nullable=False, index=True, unique=True)
salt: bytes = sqlmodel.Field( 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( 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) user_id: int = sqlmodel.Field(nullable=False, index=True)
name: str = sqlmodel.Field(nullable=False, index=True, max_length=255) name: str = sqlmodel.Field(nullable=False, index=True, max_length=255)
salt: bytes = sqlmodel.Field( 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( 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( enc_pass: bytes = sqlmodel.Field(
sa_column=sqlmodel.Column(sqlmodel.VARBINARY(255), nullable=False) sa_column=sqlmodel.Column(sqlmodel.VARBINARY(500), nullable=False)
) )