Basic Authentication in Password Manager using Tkinter

Programming PASSWORD MANAGER USING TKINTER SERIES Image

    This is the second part of the Password Manager using Tkinter series where we are going to build a Password Manager using Tkinter which will help us manage our passwords easily. Since this will be a Graphical User Interface(GUI) application, it doesn't rely on any kind of web connection. So, let's see what libraries and concepts we are going to use in this application:

    The basic workflow of the application will be as follows:

    • The user opens the application
    • If a master password is already present in the database, i.e., the user is already registered, ask the user to log in.
    • If no master password is there, ask the user to create a master password and then ask him to log in.
    • After login, the user has two options, one to Generate password and another button to add a new password.
    • Whenever a password is added, it has three options - Update Password, Copy Password and Delete Password.

    So, let's jump into the coding part now.
     

    Managing Database

    Since we'll be storing the user's master password and other passwords, we'll be using an SQLite database. The basic idea is, whenever the application is opened, we check if the corresponding databases are already present or not. In case they're not, they are created. Let's create a database.py file and add the following code:

    import sqlite3
    
    
    def init_database():
        with sqlite3.connect("password_vault.db") as db:
            cursor = db.cursor()
        cursor.execute("""
                CREATE TABLE IF NOT EXISTS master(
                id INTEGER PRIMARY KEY,
                password TEXT NOT NULL);
                """)
    
        cursor.execute("""
                CREATE TABLE IF NOT EXISTS vault(
                id INTEGER PRIMARY KEY,
                platform TEXT NOT NULL,
                userid TEXT NOT NULL,
                password TEXT NOT NULL);
                """)
        return db, cursor
    

    The reason we're using SQLite database is that it is a disk-based database and doesn't require a separate database server to be running. We'll be using the in-built Python module sqlite3 to interact with the database.

    In the above script, we have created a init_database() function. Inside the function, we are connecting to the password_vault.db database using sqlite3.connect() method. Note that, if the database is not present, it is automatically created. Further, we are using the db.cursor() method that returns an object of the Cursor class, which we'll call cursor. Next, we are executing SQL queries using the cursor.execute() method to create two tables - master and vault, if they don't exist.

    CREATE TABLE IF NOT EXISTS master(
    id INTEGER PRIMARY KEY,
    password TEXT NOT NULL);

    The above query creates a table called master having two fields, an integer id(a primary key) and a text password which should not be null.

    CREATE TABLE IF NOT EXISTS vault(
    id INTEGER PRIMARY KEY,
    platform TEXT NOT NULL,
    userid TEXT NOT NULL,
    password TEXT NOT NULL);

    The above command will create a table called vault having four fields - an integer id(a primary key), a text platform to store the website or app name, a text userid to store the email or username, and a text password that should not be null.

    After these commands are executed, we return the db and cursor to be used by other classes and methods.
     

    Password Manager Class

    We'll be creating a PasswordManager class in a Python file manager.py that will have the following components:

    1. Welcome New User
    2. Login User
    3. Password Vault Screen

    Let us start by first creating the constructor for the PasswordManager class.

    from database import init_database
    from tkinter import Tk
    
    class PasswordManager:
    
        def __init__(self):
            self.db, self.cursor = init_database()
            self.window = Tk()
            self.window.update()
            self.window.title("Password Manager")
            self.window.geometry("650x350")

    The above script is pretty simple where were are first initializing the database using the function we created in the previous section. Next, we are creating a window of size 650x350 with the title Password Manager.
     

    Utility Methods

    We'll be using two utility methods - one to encrypt password and other to copy text. Let's create them one by one:

    import hashlib
    
    
    def encrypt_password(self, password):
        password = password.encode("utf-8")
        encoded_text = hashlib.md5(password).hexdigest()
        return encoded_text

    We are using hashlib module to encrypt our passwords. We're using the md5 hashing algorithm. This hash function splits the string into equal parts and then computes a 128-bit hash value of the entire string through chaining. The hexdigest() method returns the encoded text as a string.

    def copy_text(self, text):
        self.window.clipboard_clear()
        self.window.clipboard_append(text)

    First, we are clearing our clipboard so that if anything is already copied, it is erased. Then we are appending the text which is passed as an argument to the copy_text() method.
     

    1. Welcome New User

    A new user will be welcomed with another window of a different size. This window would just have two labels, and two entry boxes, and a button. The button, when pressed, will call another method to save the password.

    from functools import partial
    from tkinter import Button, Entry, Label, Tk
    from tkinter.constants import CENTER
    
    
    def welcome_new_user(self):
        self.window.geometry("450x200")
    
        label1 = Label(self.window, text="Create New Master Password")
        label1.config(anchor=CENTER)
        label1.pack(pady=10)
    
        mp_entry_box = Entry(self.window, width=20, show="*")
        mp_entry_box.pack()
        mp_entry_box.focus()
    
        label2 = Label(self.window, text="Enter the password again")
        label2.config(anchor=CENTER)
        label2.pack(pady=10)
    
        rmp_entry_box = Entry(self.window, width=20, show="*")
        rmp_entry_box.pack()
    
        self.feedback = Label(self.window)
        self.feedback.pack()
    
        save_btn = Button(self.window, text="Create Password",
                              command=partial(self.save_master_password, mp_entry_box, rmp_entry_box))
        save_btn.pack(pady=5)

    As mentioned above, we're first setting the window size to 450x200. We are then creating and packing a label with the text Create New Master Password. Next, we are creating and packing an entry box. Since this will be a password field, we have show="*" in the entry box object. Similarly, we are creating and packing another label with the text Enter the password again and a password entry box for the user to re-enter the password. Next, we have a save button to create the user. In the command, we are passing a save_master_password method and the two entry boxes as arguments using the partial function. Additionally, we have a feedback label to show feedback if the two passwords are not the same.

    Let's create the method to save the master password:

    def save_master_password(self, eb1, eb2):
        password1 = eb1.get()
        password2 = eb2.get()
        if password1 == password2:
            hashed_password = self.encrypt_password(password1)
            insert_command = """INSERT INTO master(password)
            VALUES(?) """
            self.cursor.execute(insert_command, [hashed_password])
            self.db.commit()
            self.login_user()
        else:
            self.feedback.config(text="Passwords do not match", fg="red")

    In the above script, we're first getting the values from the two password entry boxes using the get() method in the Entry class. If the passwords are not same, we are showing a feedback to the user. If they're same, we first hash the password using the encrypt_password() method that we've created above. Then we have a SQL query to insert the hashed password into the master table. We execute the command and commit the session, and then ask the user to log in. We'll be creating this method next.


     

    2. Login User

    In this window, we'll have just two labels, one entry box and a button. 

    def login_user(self):
        for widget in self.window.winfo_children():
            widget.destroy()
    
        self.window.geometry("450x200")
    
        label1 = Label(self.window, text="Enter your master password")
        label1.config(anchor=CENTER)
        label1.place(x=150, y=50)
    
        self.password_entry_box = Entry(self.window, width=20, show="*")
        self.password_entry_box.place(x=160, y=80)
        self.password_entry_box.focus()
    
        self.feedback = Label(self.window)
        self.feedback.place(x=170, y=105)
    
        login_btn = Button(self.window, text="Log In", command=partial(
                self.check_master_password, self.password_entry_box))
        login_btn.place(x=200, y=130)

    First, we are destroying all the children of the current window and setting the current window size to 450x200. Then as we did in the welcome user section, we are creating a label and a password entry box for the user to enter the password. A feedback label is also there to show an error message if the password is wrong. The button this time will have check_master_password method in the command with the password entry box as the argument.

    Let's create this check_master_password method:

    def check_master_password(self, eb):
        hashed_password = self.encrypt_password(eb.get())
        self.cursor.execute(
                "SELECT * FROM master WHERE id = 1 AND password = ?", [hashed_password])
        if self.cursor.fetchall():
            self.password_vault_screen()
        else:
            self.password_entry_box.delete(0, END)
            self.feedback.config(text="Incorrect password", fg="red")

    In the above method, we first hash the password value from the entry box using the encrypt_password() method. Then we check in the database if there exists any row which has id=1 and password=hashed_password. The id=1 is there because we're assuming only one person will be using this application. So whenever the user sets his master password, id 1 is assigned to it. We use the cursor.fetchall() method to fetch all (remaining) rows of the query result. If we get any result, it means the correct password is entered, and hence we show the password vault screen to the user(we'll create this soon). In case it returns an empty list, we clear the entry box and add the error message in the feedback label.


     

    3. Password Vault Screen

    This is the main screen of the application where users will be able to add new passwords, modify previously saved passwords, delete passwords and copy passwords.
     

    Conclusion

    We're not going to see the Password Vault Screen in this part, else the post will be too long. In the next part of the series, we'll continue right from here and end this series. In this part, we covered the basic authentication part of the application. Stay tuned for the next part!

    0 Comments

    To add a comment, please Signup or Login