Let's begin the session
Hi, welcome back everyone!
So, first of all – do you have any doubts from the previous session?
Let's now move ahead to the ID generation system development.
I am gonna go ahead and show you a sample ID I generated for myself using it:

Excited to make this?
Cool, let's begin.
Let's look at step one of the process – we want the user to enter the password, which we can compare with the correct password to know that yes, this is the right person – authenticate them.
okay, so let's go ahead and ask the user to enter the password:
password = input('Please enter your password: ')
You see the problem here?
The thing is that if someone is sitting near them, they can peek over and find user's password. We don't want that to happen. We don't want any text to appear on the screen when the user enters the password. It's similar to how we type in passwords on websites – It's a little inconvenient, but it provides some security.
Python actually has an in-built library especially for this – it's called getpass
. In the library, there is a function getpass
which we need to use here.
So, we can just do:
from getpass import getpass
password = getpass('Enter admin password: ')
Now that we have the password from the user, we also want the original password – the password which the admin must've set.
Generally, such passwords are stored in databases, but due to brevity, let's assume that this [correct] password is stored in a simple text file (let's say password.txt
)
Since, we don't have a mechanism to create such a file, we can just create a sample password.txt
with the following code:
with open('password.txt', 'w') as f:
f.write('<mystrongpassword>')
So, now whenever a user tries to log in, we ask them for a password and we compare it with the password which is stored in this file
The code for it must look something like:
from getpass import getpass
def authenticate(password):
with open('password.txt') as f:
correct_password = f.readline()
if password == correct_password:
return True
return False
password = getpass('Enter admin password: ')
authenticate(password)
Now, what is the problem with this approach?
It's very unsafe – the person who finds the computer, can find password.txt
and then know the password of the admin (i.e. you).
Even more so, people tend to use same passwords for all their accounts. so, this malicious person might now know the password to their email, facebook, twitter, etc. This is a preposterous way of storing password.
And still, some websites do it!
and one of them is our University! :facepalm:
so, if their servers are breached (which is a very sane possibility), your passwords will be available as such to the cracker.
So, use a different password then what you use on facebook, email, etc. from other sites. We'll have a session on operational security afterwards.
Now, back to this. How can we solve this problem?
One of the ways is hashing the passwords.
Hashing is way of generating a value or values from a string of text using a mathematical function, such that it's (nearly) impossible to find it's inverse.
so, let's say h
is our hashing function, then given any string x
, we can compute h(x)
. but, if we have h(x)
, we can't go back to x
.
There are many popular hashing functions. some of them being md5
, SHA1
, SHA2
, SHA256
(which we would be using), etc.
Don't confuse it with encryption though – You can read the difference here – https://stackoverflow.com/a/4948393/6426752
We can talk about our difference later.
How does this solve our problem?
We can now, instead of storing password as text, store the hashed value of password (which even if someone sees, they cannot get access to our password).
The only question which now remains is – how do we compute these hashes?
Well, Python (and nearly all programming languages) have support for it.
In Python, you can use the hashlib
library.
Code would look like this:
from hashlib import sha256
print(sha256(b'<yourstrongpassword>').hexdigest())
Notice the b'
here – it's because the sha256
function does not work with normal strings. It only accepts “bytes”. (How did I know this?? – well, I just tried a normal string and it threw an error)
There are two ways of converting any string to bytes:
– Just add a b
in the start. eg, byte_string = b'rahul jha'
– use the encode
function, eg, byte_string = 'rahul jha'.encode('utf-8')
Okay, now that we know to generate hashed passwords, let's first write a file hashed_password.txt
using the following code:
from hashlib import sha256
hashed_pass = sha256(b'<yourstrongpassword>')
with open('hashed_password.txt', 'w') as f:
f.write(hashed_pass)
Now, for the authentication system:
from hashlib import sha256
def authenticate(password):
hashed_pass = sha256(password.encode('utf-8'))
with open('hashed_password.txt', 'r') as f:
correct_hash = f.readline()
if hashed_pass == correct_hash:
return True
return False
Okay, our authentication system is complete.
Now, we just want the printing process.
One way I could think of it was to imagine ID card as a single-column table and then find a library which prints tables nicely in Python.
I did exactly that, and after searching around a bit, found texttable – https://pypi.org/project/texttable/.
Reminder, that you can install this library using the command we discussed earlier – !pip install texttable
(The !
is only for people using Azure notebooks)
I just followed their example and filled in my data instead, and I could already get a pretty table:
from texttable import Texttable
table = Texttable()
table.add_rows([["MEMBER - Rahul Jha"],
["Slack Username: RJ722"],
["MOTHER'S NAME: Mrs. KJ XYZ"],
["FATHER'S NAME: Mr. RJ XYZ"]
])
print(table.draw())
This code (copied from the example on their website) gave me pretty good results:
+---------------------------------+
| MEMBER - Rahul Jha |
+=================================+
| Slack Username: RJ722 |
+---------------------------------+
| MOTHER'S NAME: Kumkum Jha |
+---------------------------------+
| FATHER'S NAME: Rajeev Kumar Jha |
+---------------------------------+
Now, I wanted a cool logo of AMU-OSS, so I went on to duckduckgo and searched for ASCII generator (basically, text logos and not images). The first link pointed towards http://www.patorjk.com/software/taag/#p=testall&h=3&v=3&f=Alpha&t=AMU-OSS
So, I got a cool logo:
LOGO = """
█████╗███╗ █████╗ ██╗ ██████╗██████████████╗
██╔══██████╗ ██████║ ██║ ██╔═══████╔════██╔════╝
█████████╔████╔████║ █████████║ ████████████████╗
██╔══████║╚██╔╝████║ ██╚════██║ ██╚════██╚════██║
██║ ████║ ╚═╝ ██╚██████╔╝ ╚██████╔██████████████║
╚═╝ ╚═╚═╝ ╚═╝╚═════╝ ╚═════╝╚══════╚══════╝
"""
and then changed the table code to this:
from texttable import Texttable
table = Texttable()
table.add_rows([[f"{LOGO} \n MEMBER - Rahul Jha"],
["Slack Username: RJ722"],
["MOTHER'S NAME: Mrs. KJ XYZ"],
["FATHER'S NAME: Mr. RJ XYZ"]
])
print(table.draw())
It prints something like what you saw at the start of the session.
Notice the f before the first string here ^^. These are called f-strings.
f-strings are a very powerful feature of Python3.
Basically, first whenever we had to print a variable in a sentence, we would do something like print("Hello, my name is", my_name)
. Now, if we had to print it in the middle of the sentence, we'd do this
print("Hello, my name is " + my_name + " and my brother's name is " + brothers_name)
You see this looks bad. Now, with f-strings, we can do:
print(f"Hello, my name is {my_name} and my brother's name is {brothers_name}")
Looks way cleaner.
What's happening here?
Whatever is written inside braces {}, is computed by Python and is then inserted in the string. So, we can do print(f"4 + 5 = {4 + 5}")
and we will get:
4 + 5 = 9
It's neat!
Next, back onto our table printing code. Let's sensibly put it in a function. Let's call this function print_id
.
Now, let's first think about what will print_id
take as input.
Logically, it should take in whatever fields it is printing – Name, Slack Username, Mother's name and Father's name.
But that seems like a lot of parameters, isn't it?
and now, tomorrow if were to say, increase a field on the ID, let's say blood group. Then we will have to also pass it to the function, which is bad!
A better way would be to pass a list of all these things.
so, when we're taking input from the user, we can just put it all together in a list (let's call it user_info
), and then send this list to the print_id
function.
I've decided beforehand that the first element of the list would be name, second – slack username, then mother's name and finally father's name.
So finally, we can write a function like this:
def print_id(u_info):
table = Texttable()
table.add_rows([[f"{LOGO} \n MEMBER - {u_info[0]}"],
[f"Slack Username: {u_info[1]}"],
[f"MOTHER'S NAME: {u_info[2]}"],
[f"FATHER'S NAME: {u_info[3]}"]
])
print(table.draw())
One more thing, before we move forward – We need a function for taking in all the user data and then creating u_info
for us.
Let's call it get_user_info
. It would actually look something like this:
def get_user_info():
u_info = []
u_info.append(input("Name of applicant: "))
u_info.append(input("Slack username: "))
u_info.append(input("Mother's name: "))
u_info.append(input("Father's name: "))
return u_info
the append
function here is used to add something to the list.
let me give you an example to make things more clear.
eg. my_shopping_list = ['Supercomputer']
now, if I want to add another thing to my_shopping_list
, I can do that by my_shopping_list.append('A good monitor')
try printing print(my_shopping_list)
and you'd see that it's been added.
All good?
One more thing, we need to validate the input data, remember?
Earlier, we wrote this function:
def validate_name(name):
for part in name.split():
if not part.isalpha():
return False
return True
I went ahead and created a function to take user from input continuously until they give me a correct value:
def take_name_input(prompt):
name = input(prompt)
while not validate_name(name):
print("You did not enter the name correctly, please try again!")
name = input(prompt)
return name
and now in the get_user_info
function, we can replace input
by take_name_input
whenever we're asking for names (i.e. name
, mothers_name
, fathers_name
) – not slack (as it can have numbers)
So, it becomes:
def get_user_info():
u_info = []
u_info.append(take_name_input("Name of applicant: "))
u_info.append(input("Slack username: "))
u_info.append(take_name_input("Mother's name: "))
u_info.append(take_name_input("Father's name: "))
return u_info
For authentication, we already have our function:
def authenticate(password):
correct_hash = read_hash("hashed_password.txt")
hashed_password = hashlib.sha256(password.encode('utf-8')).hexdigest()
if hashed_password == correct_hash:
print("[INFO] Successfully authenticated!")
return True
print("Authentication failed! You cannot print ID")
return False
I decided to add a few print
statements to help the user know whether the password was correct, or wrong.
Now, let's write the main
function.
You all have been writing Python code for the past two sessions, and we are writing a main
function for the first time. It's because we aren't required to always have a main function, but it's a good practice to do so.
Notice that the main()
function isn't automatically called, like in C, but we have to call it specially at the end:
main()
Let's start writing the main
function – take the password, check whether it's correct. If correct, gather user's info and then print ID card. If password not correct, do nothing.
We have functions for all of that. Now, let's just call all of them in our main function.
Let's translate this to Python:
def main():
password = getpass("Enter admin password: ")
if authenticate(password):
u_info = get_user_info()
print_id(u_info)
and you're good to go.
Pheww, that was a lot. If you're still confused, you can view the whole final code here – https://nbviewer.jupyter.org/github/amu-oss/id_gen/blob/master/id_gen.ipynb
There's just one minor change there, instead of calling main()
directly, I use:
if __name__ == '__main__':
main()
Why?
This is your assignment.
Assignment
if __name__ == '__main__':
main()
Then, you need to explore what are some other ways in which we can print a beautiful card than printing a table.
Look at your AMU ID Card, and try to replicate it as closely as possible. Try to validate as much data as possible.
Deadline: You need to submit the blog post before Wednesday, 8 PM.