Verifying a PBKDF2 password hash in python-pbkdf2 - python

I am using the snippet below to encrypt user password before saving in the database.
from pbkdf2 import crypt
pwhash = crypt(password_from_user)
Example: $p5k2$$Y0qfZ64u$A/pYO.3Mt9HstUtEEhWH/RXBg16EXDMr
Then, I save this in database. Well locally, I can perform a check doing something like this:
from pbkdf2 import crypt
pwhash = crypt("secret")
alleged_pw = raw_input("Enter password: ")
if pwhash == crypt(alleged_pw, pwhash):
print "Password good"
else:
print "Invalid password"
but how do I perform checks with what is on the db as the encrypted string is not always the same. I'm using python-pbkdf2.

Okey, Did more research and figured out that to achieve this, i first have to encrypt the password and save in db.as:
pwhash = crypt("secret",iterations=1000)
which can produce a string like $p5k2$3e8$her4h.6b$.p.OE5Gy4Nfgue4D5OKiEVWdvbxBovxm
and to validate when a user wants to login with same password, i use the function below:
def isValidPassword(userPassword,hashKeyInDB):
result = crypt(userPassword,hashKeyInDB,iterations = 1000)
return reesult == hashKeyInDB #hashKeyInDB in this case is $p5k2$3e8$her4h.6b$.p.OE5Gy4Nfgue4D5OKiEVWdvbxBovxm
this method returns True if the password is same or False if otherwise.

Related

Login/signup script in python using sqlite3

I have created a login/signup script in python using sqlite3 and kivy. Affter creating the database, the user can enter their username, email and password. The password is hashed before being inserted into the database. The signup function works, however I am having an error with validating the user's username and password with those in the database, where the code outputs 'login sucessful' after a wrong username/password.
def login_menu_press(self):
login = False
lusername = self.login_username.text
lpassword = self.login_password.text
print(f"Your username is {lusername}, and your password is {lpassword}.")
self.login_username.text = ""
self.login_password.text = ""
# Hashing inputted password
context = CryptContext(
schemes=["pbkdf2_sha256"],
default="pbkdf2_sha256",
# More rounds = more secure, but slower to hash
pbkdf2_sha256__default_rounds=50000
)
# Hash the inputted password
hash_pass = context.hash(lpassword)
# Connect to database
conn = sqlite3.connect('users.db')
# Create cursor
c = conn.cursor()
# Query database
command = f"SELECT * from users WHERE username='{lusername}' AND Password = '{hash_pass}';"
c.execute(command)
if not c.fetchone():
print("Login successful")
login = True
else:
print("Login failed")
I belive that the error persits after the Query database comment, any help is appreciated, thanks!
I excpeted that login sucessful outputs when the username and password is supposed to be correct, which is correct. However, the code outputs this again when the username and password is anything else random.
Edit the password inputted by the user is hashed with the same algorithm that was used to hash the password that the user signs up with, therefore the hashed values will be the same.
Hash alghoritm which you used use salt, thus for exatly same input string it will output different hash values:
# Hash the inputted password
lpassword = "Rambo"
hash_pass = context.hash(lpassword)
print(hash_pass)
hash_pass = context.hash(lpassword)
print(hash_pass)
hash_pass = context.hash(lpassword)
print(hash_pass)
$pbkdf2-sha256$50000$wniP0br3XguBsJbSuvc.xw$vmebKLd0Uvz4IF7DbX/TzCgD5p1/cmPYApaf2eKeZ4w
$pbkdf2-sha256$50000$wJjT2tv7H8NY633vXYvROg$D0CxPTfO8jIaLPHvUtawhzKMX75LqDYSNa.z3Bf/58s
$pbkdf2-sha256$50000$tZbSWmvNmdPau9eas1ZKaQ$qMVGQWPtlLvFZ/WlG.iDO7.jIHhFiirMZw0gysfqXhs
So there is no way to match previously inserted and hashed password value. You have to use hash function which will always generate same value for same input string, like SHA-256 for instance. Replace:
hash_pass = context.hash(lpassword)
with:
import hashlib # on top of main.py
hash_pass = hashlib.sha256(lpassword.encode()).hexdigest()
And comment out this part of code:
context = CryptContext(
schemes=["pbkdf2_sha256"],
default="pbkdf2_sha256",
# More rounds = more secure, but slower to hash
pbkdf2_sha256__default_rounds=50000
)
Also you you have to change your condition: if not c.fetchone(): to if c.fetchone():

validating encrypted data from sqlite3 database

i am creating a login function and a part of this includes checking a password entered by the user against the password stored in the database.
The problem is, when the password is created during the sign up step, it is stored as an encrypted string using fernet. Now I tried to see if encrypting the password entered by the user with the same key would produce the same encrypted string value as the one in the database, but fernet will create unique strings using the same key on the same plaintext.
What other ways can i check a password entered against its encrypted counterpart in the database?
import sqlite3
from cryptography.fernet import Fernet
key = Fernet.generate_key()
fernet = Fernet(key)
def loginFunc():
email = input("What is your email: ")
password = input("what is your password: ")
#a variable that can be used to save the data from users database
emailList = '''SELECT * FROM users WHERE `email` = ? and `password` = ?'''
#takes arguments into variable
password_checkEnc = fernet.encrypt(password.encode())
inputs = (email, password_checkEnc)
#executes line 62 to save the data that matches the arguments from 'inputs' and stores in emailList
cursor.execute(emailList, inputs)
user = cursor.fetchone()
#if statement to check if the database holds the data that is being searched for, allowing either the user to login or not
if user is not None:
print("login successful")
else:
print("incorrect information")
Why not just decrypt the password and check that way? My understanding of fernet key encryption is to do exactly what you're describing, creating a random encryption of the value each time. The purpose of the key is to decrypt it later. It would probably help to see your code where you initially encrypt the password but just looking at what you have here, it looks like you're generating a new key, not using the same one as before. You have to actually save the key you create initially with something like this:
# key generation
key = Fernet.generate_key()
fernet = Fernet(key)
# string the key in a file
with open('C:/locationofyourchoosing/newkey.key', 'wb') as filekey:
filekey.write(key)
# using the key to encrypt
f = Fernet(key)
f.encrypt(databasepassword)
Then you use this same key to decrypt the value later like this:
# opening the key
with open('C:/locationofyourchoosing/newkey.key', 'rb') as filekey:
key = filekey.read()
f = Fernet(key)
f.decrypt(databasepassword)
Then from there you can check the equality of the password. Forgive me if this isn't what you're looking for but I'm not sure you can produce the same encrypted string each time as I believe that would defeat the purpose. I hope this helps and again, if I'm missing the mark completely, please let me know as I'm just a touch confused as to why you're wanting to check equality this way.

Python - Help needed with file reading

I am trying to create a simple login system. What I'm doing is storing the login data in a text file called 'accounts.txt'
Now, when user tires to login, it first checks if the username given by the user is in the 'accounts.txt'. If it exists, then it asks for the password and then checks if password matches with the password in 'accounts.txt'
fr = open('accounts.txt', 'r')
while True:
username = input('Enter your username: ') # Ask for their username
if username in fr.read(): # Check if username exists
password = input('Enter password: ') # Ask for password if username exists
if username+password in fr.read():
print('Welcome ' + username)
break
else:
print('Wrong password')
Note, the password save in accounts.txt is in the format of usernamepassword so if username is jack and password is gate, the actual password in the txt file will be jackgate, hence im using username+password to check if password is correct.
The problem occuring is if the user enters correct username, then program moves ahead properly but even if the password entered is right, it still displays 'Wrong password' .When the second time user enters username, it even shows error for wrong username. I tried to play with the code for a long time but couldn't come up with a solution. I guess it has something to do with fr.read(). Can I use that 'fr' object only once?
Let me suggest some improvements with my answer to your question. I would read the accounts file in its entirety so you have an in-memory structure. If you do this as a dictionary in the form accounts[USER] -> PASS you can easily check for any account as per the code below.
Regarding my suggestions (they do not exactly only answer your questions, but IMHO the topic of writing login code should be treated with care):
I strongly recommend not to store passwords in plain text, regardless of application importance, always use hashes.
Do not store just the password hash, always use salting.
Do not tell the person trying to log in, if the username or the password was wrong, always just say "that's not the right combination", thus making it harder to break in.
Please find information about hashing functions in Python here: https://docs.python.org/3/library/hashlib.html#randomized-hashing
This site has a good introduction on salting ans securing passwords: https://crackstation.net/hashing-security.htm
Do you users a favor and treat the username as no case-sensitive. That is a totally valid approach, but it annoys me every time I have to use such a site (just like email addr are not case-sensitive)
As I am a total layman regarding password security, maybe one of the other Stackoverflow users can jump in with a comment and expand on this topic.
Anyway, here is my answer for your question on how to check for a login. I created a function check_account() that returns True or False, depending on wether the supplied credentials were correct or not.
import hashlib
import os
import binascii
def check_account(usr, pwd):
# read the accounts file, a simple CSV where
# username, salt value and password hash are
# stored in three columns separated by a pipe char
accounts = {}
fr = open('/users/armin/temp/test.csv', 'r')
for line in [x.strip().split("|") for x in fr.readlines()]:
accounts[line[0].lower()] = (line[1], line[2])
fr.close()
# now go looking if we do have the user account
# in the dictionary
if usr in accounts:
credentials = accounts[usr]
# credentials is a list with salt at index 0
# and pwd hash at index 1
# generate the hash form the functions parameters
# and compare with our account
h = hashlib.blake2b(salt=binascii.unhexlify(credentials[0]))
h.update(pwd.encode('utf-8'))
if credentials[1] == h.hexdigest():
return True
else:
return False
else:
return False
def main():
while True:
username = input('Enter your username: ') # Ask for their username
password = input('Enter password: ') # Ask for password if username exists
if check_account(username.lower(), password):
print("Welcome, {0}".format(username))
else:
print('Username or password unknown')
if __name__ == '__main__':
main()
To create the data for a user account, use may this code.
def create():
username = input('Enter your username: ').lower() # Ask for their username
password = input('Enter password: ') # Ask for password if username exists
salt = binascii.hexlify(os.urandom(hashlib.blake2b.SALT_SIZE))
print("SALT value:", salt)
h = hashlib.blake2b(salt=binascii.unhexlify(salt))
h.update(password.encode('utf-8'))
print("Pwd hash:", h.hexdigest())
You can use startswith and endswith:
fr = [i.strip('\n') for i in open('accounts.txt')]
while True:
username = input()
if any(i.startswith(username) for i in fr):
password = input('Enter password: ')
if any(username+password == i for i in fr):
print("welcome")
break
else:
print("wrong password")
I would do
if password in fr.read():
instead of
if username+password in fr.read():
This is because for it to get to the if password in fr.read loop it first has to pass the if username in fr.read loop. However, the only problem I find with this is that if they enter a correct username but enter the wrong password for that username but correct password for another username it will still pass.
That is why I think you should use a dictionary not a text file.
For example, if the usernames allowed is username and username1 and the password is username and username1, then in a different .py file, you can say.
username_password={'username':'username','username1':'username1'}
that makes a dictionary that has the username and passwords.
let's say you name that file stuff.py. Then in the second file that has to be in the same directory, you can do
from stuff import * #imports all values from stuff.py
while True:
username = input('Enter your username: ') #gets username
if username_password.has_key(username):
password = input('Enter password: ')
if password== username_password[username]:
print('Welcome '+username)
break
else:
print('Wrong password')
break
else:
print('Wrong username')
I still don't get why you have a while loop, but if you want it, it is fine. Also, I added an else loop just in case the username is wrong.

Retrieve a value from MongoDB and set a string to it in Python(Pymongo)

I am trying to retrieve a value from MongoDB and set a string to it in Python with MongoDB. Here is my user registration function:
def registerAccount():
registerUsername = input('Please input a username to be registered into our database.')
registerPassword = input('Please input a password to be entered into our database.')
passwordHash = sha256_crypt.hash(registerPassword)
regDetails = {
"username": registerUsername,
"passwordhash": passwordHash
}
db = client.pinnacle
users = db.users
users.insert(regDetails)
and here is my login function:
def login():
db = client.pinnacle
users = db.users
pwHash = users.find_one({"passwordhash"})
print(str(pwHash))
loginUsername = input('Please input username to be signed in.')
loginPassword = input('Please input password to be signed in.')
# pprint.pprint(users.find_one({"username": "1"}))
# example = users.find_one({"username": "1"})
pbkdf2_sha256.verify(loginPassword, pwHash)
Basically, I need to search the database for the username, and get the string right after the username which is passwordHash. Then I will set pwHash to that string which will be checked with PassLib. Help is appreciated. Thanks!
Also if anyone is interested you can see my full code here.
this happens whenever i select to login, even after not typing in username or password
The error is caused by this line below:
pwHash = users.find_one({"passwordhash"})
Where you should specify a filter of key/value pair, instead it's only specifying a key. You should remove the line above (and the print line right after it). See also pymongo find_one() method for more info.
Also, in the same function login(), you have a typo for key username.
user = users.find_one({"userName": loginUsername})
Note that it should be lowercase username to be consistent with how you're storing them elsewhere in the code.
Lastly, you should replace
pbkdf2_sha256.verify(loginPassword, user['passwordhash'])
with below method, as this is how you encrypt it in the registerAccount()
sha256_crypt.verify(loginPassword, user['passwordhash'])
Please consider adding error checks throughout your code. i.e. if users.find_one() returns None do XYZ.

Python md5 password value

I have this change password request form.In which the user enter their oldpasswords.
this oldpassword is the md5 format.
How to compare the md5 value from db to the oldpassword entered by user
import md5
oldpasswd_byuser=str("tom")
oldpasswd_db="sha1$c60da$1835a9c3ccb1cc436ccaa577679b5d0321234c6f"
opw= md5.new(oldpasswd_byuser)
#opw= md5.new(oldpasswd_byuser).hexdigest()
if(opw == oldpasswd_db):
print "same password"
else:
print "Invalid password"
the hash you put in there is a salted sha1 hexdigest as django (and probably many others) stores it by default.
the code to verify it is in contrib/auth/models.py. From there you can see that django works with md5 by default. All you have to do is to update the old hashes to the following form:
md5$<salt>$<hash>
if your hashes aren't salted yet leave the salt empty (md5$$<hash>), but update the hash to sha1 the next time the user performs a valid login.
I don't think that oldpasswd_db is a MD5. It more looks like a combination of hash method (SHA1 in this case), a salt and the password hash itself.
Try to concatenate the salt value with the password:
import hashlib
hashlib.sha1('c60datom').hexdigest()
It's not md5, it's sha1 - "sha1$xxx.
You'd have to use sha1 functions instead.
There is a documentation on this at http://docs.python.org/library/sha.html
to compare the value of your current password to the password stored in the database you can do:
import md5
input_password = request.POST['password']
md5_hashed_input_password = md5.new(input_password).hexdigest()
#comapre the value to that stored in db
if md5_hashed_input_password == db_password: #password in db should be stored in md5 hash format
print 'password match'
else:
print 'password mismatch'

Categories

Resources