Skip to content

Instantly share code, notes, and snippets.

@shawnbutts
Last active December 20, 2015 09:19
Show Gist options
  • Save shawnbutts/6107091 to your computer and use it in GitHub Desktop.
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…
#!/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