Makuhari Development Corporation
5 min read, 813 words, last updated: 2025/3/4
TwitterLinkedInFacebookEmail

A client recently asked me about a common authentication implementation: storing client-generated password hash values directly in the database. While this approach might seem secure at first glance, it introduces several critical security vulnerabilities that could compromise user data.

The Problem

The question was straightforward: "Is it secure to store client-generated password hash values directly in the database? If not, how should we fix it?"

This scenario is more common than you might think. Developers often implement client-side password hashing thinking it adds an extra layer of security, but storing these hashes directly in the database creates new attack vectors.

Investigation

Let's examine the security issues with this approach:

1. Weak Client-Side Hashing

Client-side hashing is often implemented using weak algorithms:

  • Weak hash algorithms: If the client uses MD5 or SHA-1, the hashes are vulnerable to brute force attacks and rainbow table lookups
  • No salt: Without additional random salt values, attackers can use precomputed hash tables to crack passwords

2. Loss of Server-Side Control

When password hashing is delegated to the client:

  • Cannot enforce security policies: The server cannot control hash algorithm strength or upgrade to more secure algorithms like bcrypt or Argon2
  • Cannot implement unified password policies: Server cannot enforce password complexity requirements or additional security measures

3. Transmission Vulnerabilities

Client-side hashing creates potential attack vectors:

  • Man-in-the-middle attacks: If hashing occurs over unencrypted connections, attackers can intercept original passwords
  • Hash replay attacks: Attackers could potentially use captured hash values for authentication

Root Cause

The fundamental issue is misplaced trust. Security-critical operations like password hashing should be controlled by the server, not delegated to potentially compromised clients. Client-side environments are inherently less secure and harder to control than server environments.

Solution

Here are three approaches to fix this security issue, ranked by effectiveness:

This is the gold standard for password security:

  1. Client sends plaintext password over HTTPS
  2. Server handles all password hashing
from bcrypt import hashpw, gensalt, checkpw
 
def hash_password(password):
    """Generate secure hash with automatic salt handling"""
    salt = gensalt()
    hashed = hashpw(password.encode('utf-8'), salt)
    return hashed
 
def verify_password(password, stored_hash):
    """Verify password against stored hash"""
    return checkpw(password.encode('utf-8'), stored_hash)
 
# Registration
password = "user_input_password"
secure_hash = hash_password(password)
# Store secure_hash in database
 
# Login verification
is_valid = verify_password(input_password, stored_hash)

Approach 2: Enhanced Client-Side Hashing (If Required)

If client-side hashing is absolutely necessary:

  1. Use HMAC-SHA256 with server-provided dynamic salt
  2. Server performs additional hashing on received values
// Client-side: Enhanced hashing with server salt
async function hashPasswordWithSalt(password, serverSalt) {
    const encoder = new TextEncoder();
    const key = await crypto.subtle.importKey(
        "raw", 
        encoder.encode(password),
        { name: "HMAC", hash: "SHA-256" },
        false, 
        ["sign"]
    );
    
    const signature = await crypto.subtle.sign(
        "HMAC", 
        key, 
        encoder.encode(serverSalt)
    );
    
    return Array.from(new Uint8Array(signature))
        .map(b => b.toString(16).padStart(2, '0'))
        .join('');
}
# Server-side: Additional hashing of client result
def process_client_hash(client_hash):
    """Apply server-side bcrypt to client-generated hash"""
    return hashpw(client_hash.encode('utf-8'), gensalt())

Approach 3: Challenge-Response Authentication

For maximum security without transmitting passwords:

// Client-side: Challenge-response implementation
async function createChallengeResponse(password, challenge) {
    const encoder = new TextEncoder();
    const key = await crypto.subtle.importKey(
        "raw",
        encoder.encode(password),
        { name: "HMAC", hash: "SHA-256" },
        false,
        ["sign"]
    );
    
    return crypto.subtle.sign(
        "HMAC",
        key,
        encoder.encode(challenge)
    );
}

Security Impact Analysis

Let me address the follow-up question about salt security. If Approach 2 lacks proper salting, the security degradation is significant:

Security Aspect No Salt Impact Risk Level
Rainbow table attacks Highly vulnerable Critical
Brute force resistance Minimal protection High
Password uniqueness Same passwords = same hashes High
Future upgrades Difficult migration Medium

Without salt, modern GPUs can attempt billions of SHA-256 calculations per second, making password cracking feasible within hours or days.

Lessons Learned

Key Takeaways:

  1. Server-side control is crucial: Password security should never depend on client-side implementations
  2. Use proven algorithms: bcrypt, Argon2, or PBKDF2 are designed specifically for password hashing
  3. HTTPS is mandatory: Never transmit passwords over unencrypted connections
  4. Unique salts matter: Each password should have its own random salt to prevent rainbow table attacks

Prevention Tips:

  • Always validate security assumptions during code review
  • Use established cryptographic libraries instead of rolling your own
  • Implement proper logging to detect authentication anomalies
  • Regular security audits of authentication systems
  • Consider modern alternatives like WebAuthn for enhanced security

Migration Strategy:

If you're currently storing client-generated hashes, plan a gradual migration:

  1. Implement server-side hashing for new users
  2. Migrate existing users during their next password change
  3. Maintain backward compatibility during the transition period

The bottom line: password security is too critical to leave to client-side implementations. Move password hashing to the server where you can control the security policies and use battle-tested cryptographic libraries.

Makuhari Development Corporation
法人番号: 6040001134259
ご利用にあたって
個人情報保護方針
個人情報取扱に関する同意事項
お問い合わせ
Copyright© Makuhari Development Corporation. All Rights Reserved.