Flask web authentication portal with two-factor authentication using 100-iteration SHA-256 hash chain-based OTP and RSA-encrypted database storage
Features β’ Installation β’ Usage β’ Security β’ Documentation
- Overview
- Features
- Security Architecture
- Installation
- Quick Start
- Technical Documentation
- Database Structure
- Academic Context
- Security Considerations
- API Reference
- Contributing
- License
- Author
This project implements a modern web authentication system with two-factor authentication (2FA) using a unique hash chain-based One-Time Password (OTP) mechanism. It combines traditional password authentication with cryptographic OTP verification for enhanced security.
Unlike traditional TOTP (Time-based OTP) or HOTP (HMAC-based OTP), this system uses a hash chain approach:
Password β SHA-256 β SHA-256 β ... (100 times) β OTP Chain
Sβ β Sβ β Sβ β ... β Sββ β Sβββ
Usage: Sβββ, Sββ, Sββ, ... (reverse order)
Verification: SHA-256(Sβββ) = Sβ
Advantages:
- β Offline Capable - No time synchronization needed
- β Replay Attack Resistant - Each OTP used exactly once
- β Lightweight - No additional hardware/apps required
- β Cryptographically Secure - SHA-256 hash chain
- β Two-Factor Authentication (2FA) - Password + OTP verification
- β Hash Chain-Based OTP - 100-iteration SHA-256 chain
- β RSA Encrypted Database - PKCS1_OAEP encryption for data at rest
- β SHA-256 Password Hashing - Secure credential storage
- β Automatic OTP Chain Renewal - Seamless chain regeneration
- β Session Management - Flask secure sessions
- β Flask Framework - Modern Python web framework
- β Responsive UI - HTML/CSS templates
- β User Registration - Secure account creation
- β User Login - Multi-step authentication
- β Welcome Page - Post-authentication dashboard
- β Input Validation - Username/password requirements
- β Flash Messages - User feedback and error handling
- β Encrypted Storage - RSA encryption for database
- β OTP Counter Tracking - Prevents OTP reuse
- β Chain Exhaustion Handling - Automatic renewal at 100 logins
- β Server-Side Validation - All security logic on backend
- β Password Complexity Requirements - Minimum 6 characters
- β Username Restrictions - Alphanumeric only
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Flask Authentication Portal Architecture β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
CLIENT (Flask Frontend) SERVER (Backend Logic)
βββββββββββββββββββββββ ββββββββββββββββββββββββ
β Registration Form β β User Database β
β - Username ββββββββββββββββββββΆβ (RSA Encrypted) β
β - Password β Hash Password β username;hash;OTP β
βββββββββββββββββββββββ Generate OTP ββββββββββββββββββββββββ
β β
β β
βΌ βΌ
βββββββββββββββββββββββ ββββββββββββββββββββββββ
β Login Form β β Verification Logic β
β - Username ββββββββββββββββββββΆβ 1. Check Password β
β - Password β Send Hashed β 2. Verify OTP β
β β Password + OTP β 3. Update Counter β
βββββββββββββββββββββββ ββββββββββββββββββββββββ
β β
β β Valid β
βΌ βΌ
βββββββββββββββββββββββ ββββββββββββββββββββββββ
β Welcome Page βββββββββββββββββββββ Session Created β
β "Welcome, user!" β Redirect β Login Successful β
βββββββββββββββββββββββ ββββββββββββββββββββββββ
1. User Input:
- Username (letters only)
- Password (>6 chars)
2. Client-Side:
- Validate username format: ^[A-Za-z]+$
- Validate password length: >6
- Hash password: SHA-256(password)
- Generate OTP chain: hash_otp(password, 100)
- Send: username, hashed_password, OTP[0] (Sβββ)
3. Server-Side:
- Check username uniqueness
- Encrypt database entry with RSA
- Store: username;hash;Sβββ;0
- Save encrypted to database.txt
4. Result:
- Redirect to login page
- Flash: "User registered successfully"1. User Input:
- Username
- Password
2. Client-Side (Password Verification):
- Hash password: SHA-256(password)
- Send: username, hashed_password
3. Server-Side (Password Check):
- Decrypt database
- Verify: username exists AND hash matches
- Return: True/False
4. Client-Side (OTP Generation):
- Generate OTP chain: hash_otp(password, 100)
- Get current index: counter % 100
- Send: OTP[counter] (Sβββ)
5. Server-Side (OTP Verification):
- Load stored OTP: Sβ
- Hash received OTP: SHA-256(Sβββ)
- Verify: SHA-256(Sβββ) == Sβ
- Update: store Sβββ, increment counter
- Re-encrypt database
6. Result:
- If valid: Redirect to /welcome
- If invalid: Show error messagePython 3.8 or higher
pip (Python package manager)git clone https://github.com/memo-13-byte/secure-flask-auth-portal.git
cd secure-flask-auth-portalpip install Flask pycryptodomeRequired Libraries:
Flask- Web frameworkpycryptodome- Cryptographic operations
# generate_keys.py
from Crypto.PublicKey import RSA
# Generate 2048-bit RSA key pair
key = RSA.generate(2048)
# Save private key
with open('private.pem', 'wb') as f:
f.write(key.export_key())
# Save public key
with open('public.pem', 'wb') as f:
f.write(key.publickey().export_key())
print("Keys generated successfully!")python generate_keys.pyEnsure your project has this structure:
secure-flask-auth-portal/
βββ Client.py # Flask application (frontend)
βββ Server.py # Backend logic
βββ templates/
β βββ register.html # Registration form
β βββ login.html # Login form
β βββ welcome.html # Welcome page
βββ public.pem # RSA public key
βββ private.pem # RSA private key
βββ database.txt # Encrypted user database (auto-generated)
βββ requirements.txt # Python dependencies
βββ .gitignore
βββ LICENSE
βββ README.md
python Client.pyOpen your browser and navigate to:
http://localhost:5000
- Click "Register" or go to
http://localhost:5000/register - Enter username (letters only):
alice - Enter password (>6 chars):
mysecurepassword - Click "Register"
- You'll be redirected to login page
- Enter username:
alice - Enter password:
mysecurepassword - Click "Submit"
- OTP is automatically verified
- You'll see: "Welcome, alice!"
def hash_otp(seed: str, n: int = 100) -> list:
"""
Generate hash chain OTP
Process:
1. Start with password as seed
2. Hash 100 times using SHA-256
3. Return chain in reverse order
Example:
Sβ = "password"
Sβ = SHA-256(Sβ)
Sβ = SHA-256(Sβ)
...
Sβββ = SHA-256(Sββ)
Return: [Sβββ, Sββ, Sββ, ..., Sβ]
"""
chain = []
current_hash = bytes(seed, 'utf-8')
for i in range(n):
hash_obj = SHA256.new(current_hash)
current_hash = hash_obj.digest()
chain.append(current_hash.hex())
return chain[::-1] # Reverse for usageServer stores: Sβ
Client sends: Sβββ
Server verification:
1. Hash client OTP: SHA-256(Sβββ)
2. Compare: SHA-256(Sβββ) == Sβ
3. If match:
- Update stored OTP: Sβ β Sβββ
- Increment counter: counter += 1
- Save encrypted database
4. Return: True/FalseWhen counter reaches 100:
1. Client generates new chain
2. Reset counter to 0
3. Store new Sβββ
4. Continue authenticationRoutes:
| Route | Methods | Description |
|---|---|---|
/ |
GET | Redirects to /login |
/register |
GET, POST | User registration form |
/login |
GET, POST | User login with OTP |
/welcome |
GET | Post-authentication page |
Key Functions:
# Password hashing
hash_password(password, algorithm="SHA256")
β Returns: Hex digest of hashed password
# OTP chain generation
hash_otp(seed, n=100)
β Returns: List of 100 OTP values in reverse order
# Registration handler
@app.route('/register', methods=['GET', 'POST'])
β Validates input β Generates OTP β Registers user
# Login handler
@app.route('/login', methods=['GET', 'POST'])
β Verifies password β Generates OTP β Validates OTP β RedirectsClass: Server
Attributes:
database_path: str = "database.txt"
public_key: RSA.RsaKey
private_key: RSA.RsaKey
index: int = 0
register_otp: str = "000000"
otp_mod: int = 99 # 100 - 1Methods:
| Method | Purpose | Input | Output |
|---|---|---|---|
encrypt_line() |
Encrypt data with RSA | plaintext | ciphertext |
decrypt_line() |
Decrypt data with RSA | ciphertext | plaintext |
load_database() |
Load encrypted database | - | List of users |
save_database() |
Save encrypted database | user list | - |
register_user() |
Register new user | username, hash, OTP | True/False |
verify_login() |
Verify credentials | username, hash | True/False |
validate_otp() |
Verify OTP | username, OTP | True/False |
hash_one_time_otp() |
Hash single OTP | OTP | hashed OTP |
Database Encryption:
RSA PKCS1_OAEP Encryption Flow:
Write:
1. Format: "username;hash;OTP;counter"
2. Encrypt with public key
3. Store: [4 bytes size] + [encrypted data]
Read:
1. Read 4-byte size
2. Read encrypted data
3. Decrypt with private key
4. Parse: split by ";"Filename: database.txt (RSA encrypted)
Decrypted Format:
username1;hashed_password;OTP_token;counter
username2;hashed_password;OTP_token;counter
Field Descriptions:
| Field | Type | Description | Example |
|---|---|---|---|
username |
str | Letters only | alice |
hashed_password |
hex | SHA-256 hash | 5e884898da... |
OTP_token |
hex | Current OTP (Sβ) | a3f2b8c1... |
counter |
int | OTP usage count | 0-99 |
Example Entry (Decrypted):
alice;5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8;a3f2b8c1d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6;5
Storage Process:
1. Format line: "user;hash;otp;counter"
2. Encrypt with RSA public key (PKCS1_OAEP)
3. Get encrypted bytes
4. Write: [len(encrypted).to_bytes(4, 'big')] + [encrypted]
Retrieval Process:
1. Read 4 bytes β get encrypted data length
2. Read N bytes β get encrypted data
3. Decrypt with RSA private key
4. Parse decrypted string by ";"Course: BBM 465 - Information Security Laboratory
Institution: Hacettepe University, Computer Engineering Department
Semester: Fall 2024
Group: 28
Team Members:
- Mehmet YiΔit (b2210356159)
- Mehmet OΔuz Kocadere (b2210356021)
Topics Covered:
- Multi-factor authentication (MFA/2FA)
- Hash chain-based OTP systems
- RSA public key encryption
- SHA-256 cryptographic hashing
- Flask web application security
- Secure password storage
- Session management
- Database encryption
Assignment Objectives:
- Implement secure user authentication
- Design hash chain OTP mechanism
- Encrypt sensitive data with RSA
- Build Flask web application
- Understand 2FA principles
- Practice secure coding
-
Two-Factor Authentication
- Password + OTP verification
- Each factor independently secure
-
Hash Chain Security
- SHA-256 cryptographic strength
- One-time use prevents replay attacks
- Offline capable (no time sync)
-
Database Encryption
- RSA PKCS1_OAEP encryption
- Data encrypted at rest
- Private key required for access
-
Password Protection
- SHA-256 hashing
- Never stored in plaintext
- Server-side verification only
-
OTP Counter Tracking
- Prevents OTP reuse
- Automatic chain renewal
- State management
-
Password Hashing (Educational)
- Issue: Basic SHA-256 without salt
- Production Fix: Use bcrypt, Argon2, or PBKDF2
- Example:
# Instead of: SHA256(password) # Use: bcrypt.hashpw(password, bcrypt.gensalt(rounds=12))
-
RSA Key Management
- Issue: Keys stored in plaintext files
- Production Fix: Use HSM or key vault
- Example: AWS KMS, Azure Key Vault
-
Session Management
- Issue: Basic Flask sessions
- Production Fix: Use secure session storage
- Example: Redis, database-backed sessions
-
No Rate Limiting
- Issue: Vulnerable to brute force
- Production Fix: Implement rate limiting
- Example: Flask-Limiter
-
Global Index Variable
- Issue: Not thread-safe
- Production Fix: Use database-stored counter
- Example: Store counter per-user in database
# 1. Password Hashing
import bcrypt
hashed = bcrypt.hashpw(password.encode(), bcrypt.gensalt(12))
# 2. Rate Limiting
from flask_limiter import Limiter
limiter = Limiter(app, default_limits=["5 per minute"])
# 3. HTTPS Only
app.config['SESSION_COOKIE_SECURE'] = True
app.config['SESSION_COOKIE_HTTPONLY'] = True
# 4. CSRF Protection
from flask_wtf.csrf import CSRFProtect
csrf = CSRFProtect(app)
# 5. Environment Variables for Keys
import os
private_key_path = os.environ.get('PRIVATE_KEY_PATH')
# 6. Logging
import logging
logging.basicConfig(level=logging.INFO)
# 7. Input Sanitization
from bleach import clean
username = clean(username, tags=[])Register a new user account.
Request Body (Form):
username: str (letters only)
password: str (>6 chars)
Process:
- Validate username/password
- Hash password (SHA-256)
- Generate OTP chain (100 iterations)
- Send to server: username, hash, OTP[0]
Response:
- Success: Redirect to
/login+ flash message - Failure: Error message displayed
Authenticate user with password + OTP.
Request Body (Form):
username: str
password: str
Process:
- Hash password
- Verify credentials with server
- Generate OTP chain
- Send OTP[counter] to server
- Verify OTP
Response:
- Success: Redirect to
/welcome - Failure: Error message displayed
Display welcome page after successful login.
Response:
<h1>Welcome, {username}!</h1>Input:
username(str): Unique usernamehashed_password(str): SHA-256 hashotp_token(str): Initial OTP (Sβββ)
Output:
True: Registration successfulFalse: Username exists
Input:
username(str): Username to verifyhashed_password(str): SHA-256 hash
Output:
True: Valid credentialsFalse: Invalid credentials
Input:
username(str): Usernameclient_otp(str): OTP from client (Sβββ)
Process:
- Load stored OTP (Sβ)
- Hash client OTP: SHA-256(Sβββ)
- Compare: SHA-256(Sβββ) == Sβ
- Update: store Sβββ, counter++
Output:
True: Valid OTPFalse: Invalid OTP
Contributions welcome! Areas for improvement:
- Add bcrypt password hashing
- Implement rate limiting
- Add CSRF protection
- Create user profile pages
- Add password reset functionality
- Implement email verification
- Add logging and monitoring
- Create admin dashboard
- Fork repository
- Create feature branch
- Make changes
- Test thoroughly
- Submit pull request
MIT License - see LICENSE file
Mehmet OΔuz Kocadere
- π Computer Engineering Student @ Hacettepe University
- π Focus: Web Security, Authentication, Cryptography
- πΌ LinkedIn
- π§ Email: canmehmetoguz@gmail.com
- π GitHub: @memo-13-byte
- Classical Cryptography Toolkit - Cipher implementation & cryptanalysis
- File Integrity Checker - RSA digital signatures & PKI
- Hybrid Kerberos System - Enterprise authentication
- Hacettepe University - Computer Engineering Department
- BBM 465 Course - Information Security Laboratory
- Flask Documentation - Web framework guide
- PyCryptoDome - Cryptographic library
β Star this repository if you found it helpful!
Made with β€οΈ for modern web security education