Last active
December 20, 2015 09:19
-
-
Save shawnbutts/6107091 to your computer and use it in GitHub Desktop.
Goal: Securely authenticate a user without storing their username and password in the database. Also, the authentication records and user records must be separated and unconnect-able without a successful authentication. This would permit a layer of obfuscation between authentication and account details in the database. i.e. The connection betwee…
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
# encoding: utf-8 | |
""" | |
Copyright (c) 2013, Shawn Butts. All rights reserved. | |
Permission to use, copy, modify, and/or distribute this software for any | |
purpose with or without fee is hereby granted, provided that the above | |
copyright notice and this permission notice appear in all copies. | |
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | |
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | |
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR | |
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | |
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN | |
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF | |
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | |
""" | |
import hashlib | |
import base64 | |
import bcrypt | |
import scrypt | |
import uuid | |
import pickle | |
""" | |
Goal: | |
Securely authenticate a user without storing their username | |
and password in the database. Also, the authentication records and user | |
records must be separated and unconnect-able without a successful | |
authentication. | |
This would permit a layer of obfuscation between authentication and account | |
details in the database. i.e. The connection between a user's authentication | |
record and their user account record can only be made with a successful | |
authentication attempt. | |
Concept: | |
Account Generation: | |
1) Generate a unique ID based on the hash of the supplied username + a salt | |
2) Generate a secure hash based on the supplied username and password | |
3) Generate a UUID | |
4) Securely encrypt the results from 2 & 3 above with a salt + the user supplied password | |
5) Store the data from 4 above in the authentication database with a record ID from 1 above | |
6) Authenticate and aquire the UUID from the database from 5 above | |
7) Create the user account record in the user database with a record ID of the UUID from 6 above | |
Account Authentication: | |
1) Generate a record with a unique ID based on the hash of the supplied username + a salt | |
2) Load the records data from the authentication database for the record ID from 1 above | |
3) decrypt the data from 2 above with a salt + the user supplied password | |
4) Generate a secure hash based on the supplied username and password | |
5) Compare the results from 2 & 3 above | |
6) If 5 above is successful, return the UUID | |
7) If 5 above is unsuccessful, return None | |
8) If we have a valid UUID, use it to lookup the user's account details from the user database | |
""" | |
def mkhash(username, password, salt, maxtime=0.5, rounds=12): | |
# hash the username | |
u = hashlib.sha1(username).hexdigest() | |
#hash the password | |
p = hashlib.sha256(password).hexdigest() | |
# create a dict to store the data to encrypt | |
b = {} | |
# hash and store the "password" | |
b['pw'] = bcrypt.hashpw(u + p, bcrypt.gensalt(rounds)) | |
# generate a UUID for the user | |
b['uuid'] = uuid.uuid1() | |
# encrypt the data | |
s = scrypt.encrypt(pickle.dumps(b), salt + p, maxtime=maxtime) | |
# create a dict to return the results | |
r = {} | |
# set the id that will be used to look up this record | |
r['id'] = hashlib.sha1(username + salt).hexdigest() | |
# set the data with the base64 encoded encrypted data | |
r['data'] = base64.b64encode(s) | |
return r | |
def chkhash(storhash, username, password, salt, maxtime=1): | |
# decode data | |
a = base64.b64decode(storhash) | |
# hash the username | |
u = hashlib.sha1(username).hexdigest() | |
# hash the password | |
p = hashlib.sha256(password).hexdigest() | |
try: | |
# decrypt the data | |
z = pickle.loads(scrypt.decrypt(a, salt + p, maxtime=maxtime)) | |
# create the hashed 'password' for verification | |
b = bcrypt.hashpw(u + p, z['pw']) | |
if z['pw'] == b: | |
# if the password is good, return the user's uuid | |
r = z['uuid'] | |
else: | |
# if the password is bad, return 'None' | |
r = None | |
except Exception, e: | |
print('\t' + str(e)) | |
r = None | |
return r | |
def test(): | |
''' | |
test it | |
''' | |
# create test variables | |
username = 'username' | |
password = 'password' | |
salt = 'salt' | |
rounds = 10 | |
maxtime = 0.5 | |
#make fake databases | |
authdatabase = {} | |
userdatabase = {} | |
print('\ncreating base64 encoded storage value...') | |
# create a user's hash | |
mkhashresult = mkhash(username, password, salt, maxtime, rounds) | |
# save the returned values | |
authdatabase[mkhashresult['id']] = mkhashresult['data'] | |
# test an account creation | |
print('\ntesting a user account creation...') | |
# this simulates a lookup from the database | |
d = authdatabase[hashlib.sha1(username + salt).hexdigest()] | |
authsuccess = chkhash(d, username, password, salt, maxtime + 1) | |
if authsuccess != None: | |
# print the returned values | |
print('\tuuid = ' + str(authsuccess)) | |
# create the user account record | |
userdatabase[authsuccess] = 'this data is from the user account database' | |
else: | |
print('\tfailed') | |
# test a successful authentication and account lookup | |
print('\ntesting a user login...') | |
# this simulates a lookup from the database | |
d = authdatabase[hashlib.sha1(username + salt).hexdigest()] | |
# authenticate the user (should successed) | |
accountlookup = chkhash(d, username, password, salt, maxtime + 1) | |
if accountlookup != None: | |
# print the returned values | |
print('\tuuid = ' + str(authsuccess)) | |
# print the user account record | |
print('\t' + userdatabase[accountlookup]) | |
else: | |
print('\tfailed') | |
# authenticate the user (should fail) | |
print('\ntesting an unsuccesful attempt...') | |
authfail = chkhash(d, username + 'a', password, salt, maxtime + 1) | |
if authfail != None: | |
print('\tuuid = ' + str(authfail)) | |
else: | |
print('\tfailed') | |
if __name__ == "__main__": | |
test() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment