GeezerVisonThe World Wide Web, restored to 800 pixels of glory. |
| Home|About|Gaming cabinet|Archives|How it works|Starter sites |
GeezerVison is showing zetcode.com — Python bcrypt - hashing passwords in Python with bcrypt Python bcryptlast modified October 30, 2026 Python 3.15 In this article we show how to hash passwords in Python with the bcrypt library. We define basic terms including encryption, hashing, and salt.
The Python $ pip install bcrypt EncryptionEncryption is the process of encoding a message or information in such a way that only authorized people can read it with a corresponding key. The intended information or message, referred to as plaintext, is encrypted using an encryption algorithm – a cipher – generating ciphertext that can be read only if decrypted. Encryption is a two-way function. When we encrypt something, we do so with the intention of decrypting it later. Encryption is used to protect data when transmitted, for example in mail communication. HashingHashing is the process of using an algorithm to map data of any size to a fixed length. The result is called a hash value. Whereas encryption is a two-way function, hashing is a one-way function. It is not possible to compute the input from the hash; an attacker can only guess inputs and compare their hashes. While encryption is meant to protect data in transit, hashing is meant to verify that data has not been altered and that it is authentic.
Note: Hashing is not limited to security. It is also used
to compare large amounts of data or for fast key lookups.
Passwords are not stored as plaintext in databases but as hashed values. When a user logs in, the application hashes the provided password and compares the result with the stored hash. SaltSalt is a fixed-length cryptographically-strong random value that is added to the input of hash functions to create unique hashes for every input. A salt makes a password hash unique even for users adopting common passwords, and it makes precomputed tables of hashes (rainbow tables) useless. The bcrypt hashing functionbcrypt is a password hashing function designed by Niels Provos and David Mazières, based on the Blowfish cipher. It is the default password hash algorithm for OpenBSD. There are implementations of bcrypt for C, C++, C#, Java, JavaScript, PHP, Python and other languages. The bcrypt algorithm salts and hashes the password for us. Unlike fast hash functions such as SHA-256, it is deliberately slow. The computation cost of the algorithm is parameterized, so it can be increased as computers get faster. The computation cost is called work factor or cost factor. It slows down the hashing, making brute force attempts harder and slower. The downside of a high cost factor is increased load on system resources and longer response times for users. Python bcrypt create hashed passwordIn the next example, we create a hashed password. create_hashed_password.py
import bcrypt passwd = b's$cret12' salt = bcrypt.gensalt() hashed = bcrypt.hashpw(passwd, salt) print(salt) print(hashed) The example creates a salt and a hashed password with bcrypt. The library works with bytes, so the password is a bytes literal. salt = bcrypt.gensalt()
A salt is generated with the hashed = bcrypt.hashpw(passwd, salt)
A hashed value is created with the We run the program twice. Note that the salt is the first part of the generated hash value, and that each run produces a unique salt and hash. $ python create_hashed_password.py b'$2b$12$Ix5G/YcivaEqQsttniYBoO' b'$2b$12$Ix5G/YcivaEqQsttniYBoOwaa7d7vrp/wxWL6UhiYYthE2HXKpU5a' $ python create_hashed_password.py b'$2b$12$NAU6hHbZ.A7zUDOqXFEqf.' b'$2b$12$NAU6hHbZ.A7zUDOqXFEqf.GOOCfRD7sz8sxj4LL88G2Y78i4.TOSS' The structure of a bcrypt hashA bcrypt hash is a 60-character string that contains everything needed to verify a password: the algorithm version, the cost factor, the salt, and the hash itself. hash_structure.py
import bcrypt
hashed = bcrypt.hashpw(b's$cret12', bcrypt.gensalt())
_, prefix, cost, rest = hashed.decode().split('$')
salt, digest = rest[:22], rest[22:]
print(f'hash: {hashed.decode()} ({len(hashed)} chars)')
print(f'prefix: {prefix}')
print(f'cost: {cost}')
print(f'salt: {salt} ({len(salt)} chars)')
print(f'digest: {digest} ({len(digest)} chars)')
We split the hash into its parts. _, prefix, cost, rest = hashed.decode().split('$')
salt, digest = rest[:22], rest[22:]
The fields are separated with the $ python hash_structure.py hash: $2b$12$3wzVPbk5ZboCQSBczCt4wuv3gDBQGDwk8SKeaZbAh6RPTjkuDzB4C (60 chars) prefix: 2b cost: 12 salt: 3wzVPbk5ZboCQSBczCt4wu (22 chars) digest: v3gDBQGDwk8SKeaZbAh6RPTjkuDzB4C (31 chars) Python bcrypt check passwordThe following example checks passwords against a hashed value. check_passwd.py
import bcrypt
passwd = b's$cret12'
hashed = bcrypt.hashpw(passwd, bcrypt.gensalt())
for attempt in [b's$cret12', b's$cret13', b'S$CRET12']:
if bcrypt.checkpw(attempt, hashed):
print(f'{attempt}: match')
else:
print(f'{attempt}: does not match')
A password is checked with the $ python check_passwd.py b's$cret12': match b's$cret13': does not match b'S$CRET12': does not match Storing hashes in a databaseThe hash consists of ASCII characters only, so it can be decoded and stored in a text column. In the next example, we register and log in users with an in-memory SQLite database. user_store.py
import sqlite3
import bcrypt
con = sqlite3.connect(':memory:')
con.execute('CREATE TABLE users(name TEXT PRIMARY KEY, pwd_hash TEXT)')
def register(name, password):
hashed = bcrypt.hashpw(password.encode(), bcrypt.gensalt())
con.execute('INSERT INTO users VALUES (?, ?)', (name, hashed.decode()))
def login(name, password):
row = con.execute('SELECT pwd_hash FROM users WHERE name = ?',
(name,)).fetchone()
if row is None:
return False
return bcrypt.checkpw(password.encode(), row[0].encode())
register('john', 'falcon42')
register('jane', 'žltý kôň')
print(login('john', 'falcon42'))
print(login('john', 'falcon43'))
print(login('jane', 'žltý kôň'))
print(login('roger', 'falcon42'))
for name, pwd_hash in con.execute('SELECT name, pwd_hash FROM users'):
print(name, pwd_hash)
The hashed = bcrypt.hashpw(password.encode(), bcrypt.gensalt())
con.execute('INSERT INTO users VALUES (?, ?)', (name, hashed.decode()))
Passwords are strings, so we encode them to UTF-8 bytes before hashing. The resulting hash is decoded to a string for storage. return bcrypt.checkpw(password.encode(), row[0].encode()) When checking, both the password and the stored hash must be encoded back to bytes. An unknown user name simply fails the login. $ python user_store.py True False True False john $2b$12$QFIxoBoDiag2BISSeLY/HeXnfJT/HX8XaKCT5GkCXNTX38uNp.3Nq jane $2b$12$pKt.rJjALWt0wpqK7.0bY.P4tXvLfZhiblAW26I2NOvHHy9j.T70. Python bcrypt cost factor
The cost factor increases security by slowing down the hashing. The number
of rounds is cost_factor.py
import time
import bcrypt
passwd = b's$cret12'
for rounds in [4, 8, 10, 12, 13, 14]:
salt = bcrypt.gensalt(rounds=rounds)
start = time.perf_counter()
hashed = bcrypt.hashpw(passwd, salt)
end = time.perf_counter()
print(f'rounds={rounds:2} {end - start:8.4f} s {hashed.decode()[:7]}')
We set the cost factor with the print(f'rounds={rounds:2} {end - start:8.4f} s {hashed.decode()[:7]}')
The cost is part of the hash prefix. A common goal is a value that takes about a quarter of a second on the production server; the default cost of 12 is close to that on the machine used here. $ python cost_factor.py rounds= 4 0.0010 s $2b$04$ rounds= 8 0.0149 s $2b$08$ rounds=10 0.0587 s $2b$10$ rounds=12 0.2360 s $2b$12$ rounds=13 0.4668 s $2b$13$ rounds=14 0.9455 s $2b$14$ Upgrading the cost factorWhen we raise the cost factor, the existing hashes keep their old cost. Since the plain password is available only during login, that is the time to rehash it. rehash.py
import bcrypt
CURRENT_COST = 12
def cost_of(hashed):
return int(hashed.split(b'$')[2])
def login(password, hashed):
if not bcrypt.checkpw(password, hashed):
return False, hashed
if cost_of(hashed) < CURRENT_COST:
hashed = bcrypt.hashpw(password, bcrypt.gensalt(CURRENT_COST))
print(f'rehashed with cost {CURRENT_COST}')
return True, hashed
stored = bcrypt.hashpw(b's$cret12', bcrypt.gensalt(rounds=10))
print(f'stored cost: {cost_of(stored)}')
ok, stored = login(b's$cret12', stored)
print(ok, f'stored cost: {cost_of(stored)}')
ok, stored = login(b's$cret12', stored)
print(ok, f'stored cost: {cost_of(stored)}')
The if cost_of(hashed) < CURRENT_COST:
hashed = bcrypt.hashpw(password, bcrypt.gensalt(CURRENT_COST))
After a successful login, a hash with a lower cost is replaced with a new one. In a real application, the new hash would be saved to the database. The second login finds the current cost and does nothing. $ python rehash.py stored cost: 10 rehashed with cost 12 True stored cost: 12 True stored cost: 12 The 72-byte limit
bcrypt uses at most 72 bytes of the password. Older versions of the library
silently ignored the rest; since version 5.0, long_password.py
import base64
import hashlib
import hmac
import bcrypt
passwd = 'ľ' * 40
data = passwd.encode()
print(len(passwd), len(data))
try:
bcrypt.hashpw(data, bcrypt.gensalt())
except ValueError as e:
print(f'ValueError: {e}')
PEPPER = b'server-side secret key'
def prehash(password):
digest = hmac.digest(PEPPER, password.encode(), hashlib.sha256)
return base64.b64encode(digest)
key = prehash(passwd)
print(key, len(key))
hashed = bcrypt.hashpw(key, bcrypt.gensalt())
print(bcrypt.checkpw(prehash(passwd), hashed))
print(bcrypt.checkpw(prehash(passwd[:-1]), hashed))
The password has 40 characters, but each def prehash(password):
digest = hmac.digest(PEPPER, password.encode(), hashlib.sha256)
return base64.b64encode(digest)
A common solution is to pre-hash the password with HMAC-SHA256 and a secret key (a pepper) kept outside the database. The digest is Base64-encoded to 44 bytes; the encoding avoids NUL bytes, which some bcrypt implementations treat as the end of the password. $ python long_password.py 40 80 ValueError: password cannot be longer than 72 bytes, truncate manually if necessary (e.g. my_password[:72]) b'zzrzQIzQpGIV/vIPy5Wf6Bg72cL8jx+oXGbGotJkHnw=' 44 True False Deriving keys with bcrypt
The derive_key.py
import os import bcrypt passwd = b's$cret12' salt = os.urandom(16) key = bcrypt.kdf(password=passwd, salt=salt, desired_key_bytes=32, rounds=100) print(len(key), key.hex()) key2 = bcrypt.kdf(password=passwd, salt=salt, desired_key_bytes=32, rounds=100) print(key == key2)
We derive a 32-byte key from a password and a random salt. Unlike
$ python derive_key.py 32 23ca1b7414d33793e39a1c2642f9460a331712d6d289d27c0757bd469b78f27f True SourceIn this article we have used the Python bcrypt module to generate and verify password hashes. See also Python hashing and Python secrets. AuthorMy name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming. List all Python tutorials. |
| You are visitor 01402503 — GeezerVison does not store the pages you look up. Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required. |