Hashing
Making a Hash Brown
Hashing
A one-way function that takes a string input and outputs a string of X length.

(Source: https://xkcd.com/936/)
- Problem: sha256 can calculate billions of hashes quickly. Solution: use a slow hash like BCrypt.
- Problem: People reuse or share common passwords. Solution: use salting to add another string.
Fast Hashes
Takes a short time to hash, is portable, doesn't lead to collisions. Commonly used for data integrity. However:
- Rainbow Tables exist: precomputed and compressed tables from hash -> passwords
- Seclists exist: lists of commonly used keys, e.g. passwords, usernames, etc.
With this information, we can create lists of all commonly used passwords and their variants within seconds.
Review Question: What is a hash collision? (Hint: the answer is the same as for hash tables.)
A collision is when two inputs lead to the same generated hash. In hash tables, we typically handle this by generating a linked list for a given hash table cell.
Slow Hashes
Takes longer to hash, and significantly longer to test all combinations.
Why are we saying "test" instead of "break"?
Hashes are not "broken," as they are designed to be one-way functions that cannot be reversed. Instead, we must brute-force all possibilities.
BCrypt
BCrypt is a slow hash. Process:
- Ask for a cost parameter
- Ask for a password
- Generate a random salt
- Generate key (
2^costkey expansion rounds) - Prepend version number, cost, salt, and hashed password
BCrypt Disclaimer
BCrypt only handles up to 72 bytes, limiting password length. We can use hash and compress the password to handle this prior. For example: convert password to SHA256 -> convert to base64 -> pass to BCrypt.
Example Output
$2a$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy
2ais the version number10is the costN9qo8uLOickgx2ZMRZoMyeis the salt- The rest of the string is the hashed password
BCrypt in Flask
Install with pip3 install flask-bcrypt and import BCrypt class:
from flask import Flask
from flask_bcrypt import BCrypt
app = Flask(__name__)
bcrypt = BCrypt(app)Generating Hashes
Create a hash with generate_password_hash() and decode():
hash = bcrypt.generate_password_hash("a_password")
# This creates a byte string with version 2b and cost 12:
# b'$2b$12$nz0fVuySYJBzJ.sXocYsuuHfUw5weyLCzOEJHjjLwvf1u/hChnam2'
hash.decode()
# This converts the byte string into a normal string:
# '$2b$12$nz0fVuySYJBzJ.sXocYsuuHfUw5weyLCzOEJHjjLwvf1u/hChnam2'We can rerun our code a few times to see that BCrypt hashes the password differntly on each call:
>>> bcrypt.generate_password_hash('a_password').decode()
'$2b$12$nz0fVuySYJBzJ.sXocYsuuHfUw5weyLCzOEJHjjLwvf1u/hChnam2'
>>> bcrypt.generate_password_hash('a_password').decode()
'$2b$12$TPu20eIGwAl050BcZfd60uFDNpiiob3CYr9lKSuAPCJ0k/MuXML1e'
>>> bcrypt.generate_password_hash('a_password').decode()
'$2b$12$Q0c.ZCab7QqPjQ3znxXqK.1xtuUYUhhMhy0GYj8I1r14823D/ZupK'
>>> bcrypt.generate_password_hash('a_password').decode()
'$2b$12$HfAZT2hsoY4frIl2R6qDn.HAs6A2VUyplkQs6O9D3ont2t9dS9bEe'
>>> bcrypt.generate_password_hash('a_password').decode()
'$2b$12$eIQ8MELmSkZW5kmF1MG/5uz8ufit6KrFXwRGStIM1FkUUxVbFIbMO'
>>> bcrypt.generate_password_hash('a_password').decode()
'$2b$12$vGWOxXd/Ee00ubeglwxy7eSY7ryMyGm/1k9MDejvixYkixcChLqe2'
>>> bcrypt.generate_password_hash('a_password').decode()
'$2b$12$9AnFKXs66A86ZQUknV4/m.w7zCZB3rLpfZgT7vJnLAQ7D/sl12wHa'Checking Hashes
Compare a generated hash to an input password:
>>> pass_hash = bcrypt.generate_password_hash('a_password')
>>> pass_hash
b'$2b$12$exCUucO2pcFhvahPR/mDNeM7CJhuoj7cMJ4s1CxHZdzHApsQqbwYq'
>>> bcrypt.check_password_hash(pass_hash, 'a_password')
True
>>> bcrypt.check_password_hash(hash, 'another_password')
False