There is a mystical aura around password creation. Some kind of occult knowledge reached only by the distant cryptographic hermits who ascended to nirvana after decades of meditation on the Patagonian glaciers and liters of instant coffee. In this article, we will try to translate a few drops of this ancient wisdom so that we, mere mortals, have secure accounts to store pictures of cats and e-books that we will never read.
First of all, this article is recommended not only for novice programmers, but for users who want to know why the hell they need a humanly unpronounceable password.
If you are reading this article, I assume you are familiar with programming languages or, at least, with their logic. We will use Python 3 to create a user creation program as simply as possible. If you are a beginner, please read about data entry and exit before proceeding.
In the beginning, God created a to-do list, and human beings, in his image and likeness, tend to copy this strategy. This is what we need to do:
There are no secrets here. Let the user be called the way they prefer, it is not our duty to judge him. Let's take the opportunity to get our basic class ready. The digital representation of God's Ctrl + C/Ctrl + V:
class User:
def __init__(self, user, passwd, salt):
self.username = user
self.passwd = passwd
self.salt = salt
Don't worry about "salt". We will talk about it soon.
I would love to talk about how a code of less than forty lines can generate millions of passwords per second, pull hidden words from the depths of your soul and test them with the same ease with which we procrastinate on Netflix or check our email. However, stick to what is strictly necessary. Let us leave the whys to the philosophers, drunks and beggars.
Safety <----------------------------------------------------------------------> Practicality
The stunningly beautiful hieroglyph above [found in the gold halls of Eldorado, South America] describes how advanced societies calculated the need for their passwords. They found that the security of a system is proportionally inverse to its practicality of use. That is, pulling the coal to our sardine, the more secure a password is, the more complex it will be.
In our study, we will arbitrarily decide that a decent password needs:
Let's create the method that will receive the password and check the level of dignity (For the sake of simplicity, we are not distinguishing between uppercase and lowercase characters.):
def check_passwd(self, passwd):
""" Check if is a valid password """
while True:
# Check Size
if len(passwd) < 14:
print("This password is too short.")
break
# Check Ascii Letters
counter = 0
for i in passwd:
if i in self.passwd_characteres:
counter += 1
if counter < 6:
print("Your password need at last six (6) characteres.")
break
# Check Digits
counter = 0
for i in passwd:
if i in self.passwd_numbers:
counter += 1
if counter < 6:
print("Your password need at last six (6) digits.")
break
# Check Special Characteres
counter = 0
for i in passwd:
if i in self.passwd_special:
counter += 1
if counter < 2:
print("Your password need at last two (2) special characteres.")
break
# The password has passed!
return True
You may have noticed that this method belongs to a class. We created a manager to test these functions. We are only discussing the main functions, the complete code will be available at the end.
"Seems" is exactly the word. Nothing prevents the user from typing
aaaaaa000000 **
or @abcedf123456@
, or anything like that. We are taking on the role of enlightened beings and therefore do not trust the user. If his password is weak it is our system that will be insecure, not just his data. Damn users! Fortunately, in Python, there is a secret called secrets:import secrets
Everything you need to know about it is here. (#ReadtheDocs) Let's get straight to the point. That's where our "salt" comes in. "Salt" is a pinch, a little thing that we add to the user's password to ensure it is secure. We can generate it automatically using the secrets module. It provides several types of tokens, particularly I prefer this one:
# 32 is the number of bytes of the text to be generated.
secrets.token_urlsafe(32)
You can add the salt at the beginning, in the middle or at the end of the password. Here, we will use the end and the password will end more or less like this:
"aaaaaa*000000*ALE_tEwJXs87D-yfgU-D7DWbASX5g_D-oENWUIBi1q8"
If someone can figure this out through trial and error, please give him all of your other passwords. He deserves it for the effort.
No. Bring the mouse pointer back and read. Now we have a reasonably secure password, how should we save it? It's time to activate the last trap card in the article: you must know something about hashes before continuing. If you don't know, roll the dice with disadvantage and try again later. There are a few reasons why you should encrypt a password before saving it.
The most important is that if an attacker has access to the database, he will not have the passwords available immediately. Therefore, even if a large part is compromised, he will not be able to access the accounts and their respective data. Finally, let's turn the potentially suspicious password into a nightmare for spy decoders:
worthy_password = hashlib.sha256((password + salt).encode()).hexdigest()
Stay strong, life won't wait for you. We are finishing.
In the end, God made another list. One about the completed tasks. He (or us) had a username, a "salt" for this user and an encrypted password. We saved this separately, as the User class allowed us. We saved the salt so that, in an attempt to login, we add it to the password entered exactly as we did at creation and check if the hashes are identical. If they are, the password is correct and we will have no complications for the next few minutes. Or until the user forgets the password again, whichever comes first.
So, we're done. Now we understand, now we get it. Now all the pieces fit together and we feel stupid for not having noticed before how the gears turn and the world rotates. Below is the complete code, organized in a manager ready to be tested. For more drops of Patagonian wisdom - or excerpts of guessing - access the Prometheus Prototype on Telegram.
# ================================================================== #
# The **basics** of how to save a reasonably secure username and
# password.
# ================================================================== #
# DEPENDENCIES
# ================================================================== #
import hashlib
import secrets
import string
# ================================================================== #
# USER CLASS
# ================================================================== #
class User:
def __init__(self, user, passwd, salt):
self.username = user
self.passwd = passwd
self.salt = salt
# ================================================================== #
# MANAGER CLASS
# ================================================================== #
class Manager:
def __init__(self):
self.users = []
self.passwd_characteres = string.ascii_letters
self.passwd_numbers = string.digits
self.passwd_special = string.punctuation
def input_username(self):
""" Gets the Username """
return input("Enter your user name: ")
def input_passwd(self):
""" Gets the Password """
return input("Enter your user name: ")
def check_username(self, new_username):
""" Check if is a valid Username """
for user in self.users:
if user.username == new_username:
print("This username already exists.")
return True
return False
def check_passwd(self, passwd):
""" Check if is a valid password """
while True:
# Check Size
if len(passwd) < 14:
print("This password is too short.")
break
# Check Ascii Letters
counter = 0
for i in passwd:
if i in self.passwd_characteres:
counter += 1
if counter < 6:
print("Your password need at last six (6) characteres.")
break
# Check Digits
counter = 0
for i in passwd:
if i in self.passwd_numbers:
counter += 1
if counter < 6:
print("Your password need at last six (6) digits.")
break
# Check Special Characteres
counter = 0
for i in passwd:
if i in self.passwd_special:
counter += 1
if counter < 2:
print("Your password need at last two (2) special characteres.")
break
# The password has passed!
return True
def protect_passwd(self, passwd):
""" Encrypt the password before save it. """
salt = secrets.token_urlsafe(32)
encripted_passwd = hashlib.sha256((passwd + salt).encode()).hexdigest()
return (encripted_passwd, salt)
def create_new_user(self):
""" Create a new user. """
while True:
user = input("Type your username: ")
if not self.check_username(user):
break
while True:
passwd = input("Type your password: ")
if self.check_passwd(passwd):
pass_pack = self.protect_passwd(passwd)
break
# Well done!
new_user = User(user, pass_pack[0], pass_pack[1])
self.users.append(new_user)
print("\nWellcome to this database!")
# ================================================================== #
# We're done here. To load the user safely, simply add the 'salt'
# saved in the profile to the password entered at the time of login.
# ================================================================== #
# TESTING:
manager = Manager()
manager.create_new_user()
# Let's see:
print("\n")
print("User name: " + manager.users[0].username)
print("Saved password: " + manager.users[0].passwd)
print("This-user salt: " + manager.users[0].salt)
print("\n")
input("Press anything to quit.")