Skip to content

How to Hash Passwords With Bcrypt in Flask in Python

Learn how to securely implement password hashing in Flask using Flask-Bcrypt, covering installation, hashing, verification, and best practices for user registration and login.

Password hashing is a critical security practice that converts plaintext passwords into irreversible, encrypted strings before storing them. If a database is ever compromised, attackers only see hashed values, i.e. not the actual passwords. Bcrypt is one of the most widely recommended hashing algorithms because it is intentionally slow, includes a built-in salt, and allows you to increase the computational cost over time as hardware gets faster.

In this guide, you will learn how to implement password hashing in a Flask application using Flask-Bcrypt, covering everything from installation to a complete user registration and login example.

How Bcrypt Works

Bcrypt is based on the Blowfish cipher and incorporates three key security features:

Feature Description
Salting Bcrypt automatically generates a random salt for each password, ensuring that identical passwords produce different hashes
Cost factor The algorithm includes a configurable work factor (number of hashing rounds) that controls how slow the hashing process is
One-way function The hash cannot be reversed to recover the original password: verification is done by hashing the input again and comparing

Setting Up Flask-Bcrypt

Step 1: Install Flask-Bcrypt

pip install flask flask-bcrypt

Step 2: Initialize Bcrypt in Your Flask App

from flask import Flask
from flask_bcrypt import Bcrypt

app = Flask(__name__)
bcrypt = Bcrypt(app)

The Bcrypt object wraps your Flask app and provides methods for hashing and verifying passwords.

Hashing a Password

Use generate_password_hash() to hash a plaintext password:

from flask import Flask
from flask_bcrypt import Bcrypt

app = Flask(__name__)
bcrypt = Bcrypt(app)

password = 'my_secure_password'
hashed = bcrypt.generate_password_hash(password).decode('utf-8')

print(f"Original:  {password}")
print(f"Hashed:    {hashed}")

Output:

Original:  my_secure_password
Hashed:    $2b$12$vBaedEhJCOZFIMmei9LWRuRf3Wb72RD.1yoIuDR4jqqExZCU6YlHC

The hash includes the algorithm identifier ($2b$), the cost factor ($12$), the salt, and the hashed password: all in a single string.

Verifying a Password

Use check_password_hash() to verify a password against its stored hash:

from flask import Flask
from flask_bcrypt import Bcrypt

app = Flask(__name__)
bcrypt = Bcrypt(app)

hashed = bcrypt.generate_password_hash('my_secure_password')

is_valid = bcrypt.check_password_hash(hashed, 'my_secure_password')
print(f"Password matches: {is_valid}")        # True

is_wrong = bcrypt.check_password_hash(hashed, 'wrong_password')
print(f"Wrong password matches: {is_wrong}")  # False

Output:

Password matches: True
Wrong password matches: False

The function internally extracts the salt from the stored hash, applies it to the input password, hashes the result, and compares the two hashes.

Basic Flask Example

Here is a minimal Flask app that demonstrates hashing and verification:

from flask import Flask
from flask_bcrypt import Bcrypt

app = Flask(__name__)
bcrypt = Bcrypt(app)


@app.route('/')
def index():
    password = 'Password@123'

    hashed_password = bcrypt.generate_password_hash(password).decode('utf-8')

    is_valid = bcrypt.check_password_hash(hashed_password, password)
    is_invalid = bcrypt.check_password_hash(hashed_password, 'wrong_pass')

    return (
        f"<h3>Password Hashing Demo</h3>"
        f"<p><strong>Original:</strong> {password}</p>"
        f"<p><strong>Hashed:</strong> {hashed_password}</p>"
        f"<p><strong>Correct password check:</strong> {is_valid}</p>"
        f"<p><strong>Wrong password check:</strong> {is_invalid}</p>"
    )


if __name__ == '__main__':
    app.run(debug=True)

Output in browser:

Password Hashing Demo
Original: Password@123
Hashed: $2b$12$Na1e8y0Ws.ldqQwTzw3IxOMaUEWDnTzezUuStssY0huTZEIA8sDAG
Correct password check: True
Wrong password check: False

Complete Example: User Registration and Login

Here is a more realistic example with user registration and login routes:

from flask import Flask, request, jsonify
from flask_bcrypt import Bcrypt

app = Flask(__name__)
bcrypt = Bcrypt(app)

users = {}


@app.route('/register', methods=['POST'])
def register():
    data = request.get_json()
    username = data.get('username')
    password = data.get('password')

    if not username or not password:
        return jsonify({'error': 'Username and password are required'}), 400

    if username in users:
        return jsonify({'error': 'User already exists'}), 409

    hashed_password = bcrypt.generate_password_hash(password).decode('utf-8')
    users[username] = hashed_password

    return jsonify({'message': f'User {username} registered successfully'}), 201


@app.route('/login', methods=['POST'])
def login():
    data = request.get_json()
    username = data.get('username')
    password = data.get('password')

    if username not in users:
        return jsonify({'error': 'Invalid username or password'}), 401

    if bcrypt.check_password_hash(users[username], password):
        return jsonify({'message': f'Welcome back, {username}!'}), 200
    else:
        return jsonify({'error': 'Invalid username or password'}), 401


if __name__ == '__main__':
    app.run(debug=True)

Testing With curl

Register a user:

curl -X POST http://localhost:5000/register \
  -H "Content-Type: application/json" \
  -d '{"username": "alice", "password": "secure_pass_123"}'

Response:

{"message": "User alice registered successfully"}

Login with correct password:

curl -X POST http://localhost:5000/login \
  -H "Content-Type: application/json" \
  -d '{"username": "alice", "password": "secure_pass_123"}'

Response:

{"message": "Welcome back, alice!"}

Login with wrong password:

curl -X POST http://localhost:5000/login \
  -H "Content-Type: application/json" \
  -d '{"username": "alice", "password": "wrong_password"}'

Response:

{"error": "Invalid username or password"}

Configuring the Cost Factor (Rounds)

The cost factor controls how many hashing rounds Bcrypt performs. Higher values make hashing slower (more secure) but increase server load:

app = Flask(__name__)
app.config['BCRYPT_LOG_ROUNDS'] = 14  # Default is 12

bcrypt = Bcrypt(app)
Rounds Approximate Time Recommendation
10 ~70ms Minimum for production
12 ~250ms Default: good balance
14 ~1s High-security applications
16 ~4s Very high security (may impact UX)

Common Mistakes and Best Practices

Mistake 1: Storing Plaintext Passwords

users[username] = password

users[username] = bcrypt.generate_password_hash(password).decode('utf-8')

Mistake 2: Comparing Hashes With ==

if stored_hash == bcrypt.generate_password_hash(password).decode('utf-8'):

if bcrypt.check_password_hash(stored_hash, password):

Mistake 3: Using the Same Hash to Compare

Each call to generate_password_hash() produces a different hash for the same password (due to random salting):

hash1 = bcrypt.generate_password_hash('password').decode('utf-8')
hash2 = bcrypt.generate_password_hash('password').decode('utf-8')

print(hash1 == hash2)  # False: different salts!

print(bcrypt.check_password_hash(hash1, 'password'))  # True
print(bcrypt.check_password_hash(hash2, 'password'))  # True

Security Best Practices

Practice Description
Always hash passwords Never store plaintext passwords
Use check_password_hash() Never compare hashes with ==
Use HTTPS Encrypt passwords in transit
Set appropriate cost factor Balance security and performance
Return generic error messages Say "Invalid username or password": never reveal which one was wrong
Add rate limiting Prevent brute-force login attempts
Use a secret key Set app.secret_key for session management

Conclusion

Implementing password hashing with Bcrypt in Flask is straightforward with the Flask-Bcrypt extension. Use generate_password_hash() to hash passwords before storing them and check_password_hash() to verify passwords during login.

Bcrypt's built-in salting ensures that identical passwords produce different hashes, and its configurable cost factor allows you to increase security as hardware improves.

By following the best practices outlined in this guide, never storing plaintext, using constant-time comparison, and returning generic error messages, you can build a secure authentication system that protects your users' credentials.

Navigation