I have created a simple authentication script in python that can add users and provides a login function. I've tried to thow as much security into this as possible, which probably wasn't the best idea.
To my knowledge, the code works fine and withstands as much invalid input as I can find (although I haven't written any proper tests). I can see straight away that I've repeated myself quite a lot, but I'm not entirely sure how to cut it down.
Usage:
login(username, password)
Checks if this username/password combination is valid.
Returns bool
add_user(username, password)
Adds a user to the password file.
Returns bool
import hashlib, random, string
USERNAME_CHARACTERS=string.digits+string.ascii_letters
PASSWORD_CHARACTERS=string.digits+string.ascii_letters+string.punctuation
SALT_CHARACTERS="0123456789abcdefghijklmnopqrstuv"
SALT_CHARACTER_BITS=5
SALT_LENGTH=16
PEPPER_LENGTH=1
FILE_PATH="./shad"
FILE_SEPERATOR=":"
def int_to_string(num):
string=""
while num!=0:
c=num%(2**SALT_CHARACTER_BITS)
string+=SALT_CHARACTERS[c]
num-=c
num//=2**SALT_CHARACTER_BITS
return string
def generate_new_salt():
return int_to_string(random.getrandbits(SALT_CHARACTER_BITS*SALT_LENGTH))
def generate_new_pepper():
return int_to_string(random.getrandbits(SALT_CHARACTER_BITS*PEPPER_LENGTH))
def generate_all_peppers():
for i in range(1,SALT_CHARACTER_BITS*PEPPER_LENGTH):
yield int_to_string(i)
def do_hash(string):
return hashlib.sha3_256(string.encode()).hexdigest()
def get_user_data(username):
with open(FILE_PATH) as h:
data=h.read()
for line in data.split("\n"):
l=line.split(FILE_SEPERATOR)
if l[0]==username:
return {"user":l[0],
"pass":l[1],
"salt":l[2]}
raise ValueError("Username not found: "+username)
def is_valid_username(username):
for i in username:
if not(i in USERNAME_CHARACTERS):
return False
return True
def is_valid_password(username):
for i in username:
if not(i in PASSWORD_CHARACTERS):
return False
return True
def add_user(username, password):
if not(is_valid_username(username)):
print("Invalid username")
return False
elif not(is_valid_password(password)):
print("Invalid password")
return False
with open(FILE_PATH) as h:
data=h.read()
for line in data.split("\n"):
l=line.split(FILE_SEPERATOR)
if l[0]==username:
print("Username already exists")
return False
salt=generate_new_salt()
data=FILE_SEPERATOR.join([username, do_hash(generate_new_pepper()+salt+password), salt])+"\n"
with open(FILE_PATH, "a") as h:
h.write(data)
return True
def login(username, password):
if not(is_valid_username(username)):
print("Invalid username")
return False
elif not(is_valid_password(password)):
print("Invalid password")
return False
data=get_user_data(username)
for pepper in generate_all_peppers():
if do_hash(pepper+data["salt"]+password)==data["pass"]:
return True
return False
login
function uses a constant time method to compare the password authenticator string. \$\endgroup\$