pass256 logo

pass256

Back to Home

Password Storage Tips: Securing User Credentials

8 min read
Updated May 2, 2025

This article covers essential techniques for securely storing user passwords in your applications, helping you protect sensitive user data from potential breaches.

Table of Contents

Proper password storage is a critical aspect of application security. When user credentials are compromised, the damage can extend far beyond your application due to password reuse. This guide covers fundamental techniques and best practices for securely storing passwords in your systems.

Key Takeaway

Never store passwords in plaintext. Always use modern hashing algorithms with salting to protect user credentials, even in the event of a database breach.

Password Storage Fundamentals

The core principle of secure password storage is that you should never store the actual password. Instead, you store a cryptographic hash of the password that can be used for verification without revealing the original password.

Why Not Store Plaintext Passwords?

Storing passwords in plaintext poses several serious risks:

  • If your database is compromised, attackers immediately gain access to all user accounts
  • Internal staff with database access can view user passwords
  • Users often reuse passwords across multiple sites, so a breach of your system could compromise their accounts elsewhere
  • It violates security best practices and likely breaches data protection regulations

Warning

Even encrypted passwords (as opposed to hashed) are vulnerable if the encryption key is compromised. Hashing is the preferred approach for password storage.

Secure Hashing Techniques

A cryptographic hash function converts data of arbitrary size into a fixed-size output. Good password hashing functions have these properties:

  • One-way: It should be computationally infeasible to reverse the hash to obtain the original password
  • Deterministic: The same input always produces the same output
  • Collision-resistant: It should be extremely unlikely for two different inputs to produce the same hash
  • Slow: The function should be deliberately slow to compute to resist brute-force attacks

Recommended Hashing Algorithms

Modern password hashing algorithms are specifically designed to be slow and resource-intensive to prevent brute-force attacks. The most recommended algorithms include:

Algorithm Pros Cons Recommendation
Argon2id Winner of the Password Hashing Competition, resistant to side-channel attacks Newer, less widely supported in some frameworks Highly recommended
bcrypt Well-established, widely supported, adaptive cost factor Limited to 72 bytes of input, memory-constant Recommended
PBKDF2 FIPS-approved, widely available Vulnerable to GPU/ASIC acceleration Acceptable
scrypt Memory-hard function, resistant to hardware attacks Complex parameter selection Recommended
MD5/SHA1 Fast computation Extremely vulnerable to brute-force attacks, cryptographically broken Avoid

Note

The security of password hashing depends not just on the algorithm choice, but also on proper implementation and configuration of work factors.

Salt and Pepper

Salting is a critical technique that adds an additional layer of security to password hashing.

What is a Salt?

A salt is a random value that is generated for each user and combined with their password before hashing. This ensures that even if two users have the same password, their hashed passwords will be different. Salts are typically stored alongside the password hash in the database.

How Salting Works:

  1. Generate a unique random salt for each user (typically 16+ bytes)
  2. Combine the salt with the plaintext password
  3. Hash the combined value
  4. Store both the salt and the hash in the database
  5. During verification, retrieve the salt, combine it with the provided password, and compare the resulting hash

What is a Pepper?

A pepper is similar to a salt but with one key difference: it's not stored in the database. Instead, it's a secret value stored in application configuration or environment variables. This adds an extra layer of protection if the database is compromised but the application server remains secure.

Implementation Tip

Most modern hashing libraries handle salt generation and storage automatically. You typically don't need to manage salts manually unless you're implementing custom cryptographic solutions.

Implementation Examples

Here are examples of how to implement secure password hashing in different programming languages:

Node.js with bcrypt

const bcrypt = require('bcrypt');
const saltRounds = 12;
// Hashing a password
async function hashPassword(plainTextPassword) {
  try {
    const hash = await bcrypt.hash(plainTextPassword, saltRounds);
    return hash;
  } catch (err) {
    console.error(err);
    throw err;
  }
}

// Verifying a password
async function verifyPassword(plainTextPassword, hashedPassword) {
  try {
    const match = await bcrypt.compare(plainTextPassword, hashedPassword);
    return match;
  } catch (err) {
    console.error(err);
    throw err;
  }
}

Python with Argon2

from argon2 import PasswordHasher
from argon2.exceptions import VerifyMismatchError

ph = PasswordHasher(
    time_cost=3,      # Number of iterations
    memory_cost=65536, # Memory usage in kibibytes (64 MB)
    parallelism=4,    # Degree of parallelism
    hash_len=32,      # Length of the hash in bytes
    salt_len=16       # Length of the salt in bytes
)

# Hashing a password
def hash_password(password):
    return ph.hash(password)

# Verifying a password
def verify_password(password, hash):
    try:
        ph.verify(hash, password)
        return True
    except VerifyMismatchError:
        return False

PHP with password_hash

// Hashing a password
function hashPassword($password) {
    // PASSWORD_ARGON2ID is available in PHP 7.3+
    // For older versions, use PASSWORD_BCRYPT
    $hash = password_hash($password, PASSWORD_ARGON2ID, [
        'memory_cost' => 65536, // 64MB
        'time_cost' => 4,       // 4 iterations
        'threads' => 3          // 3 threads
    ]);
    
    return $hash;
}

// Verifying a password
function verifyPassword($password, $hash) {
    return password_verify($password, $hash);
}

Security Notice

Never implement your own cryptographic functions. Always use well-established libraries that are regularly maintained and audited by security experts.

Best Practices

Follow these guidelines to ensure your password storage implementation is as secure as possible:

Use Work Factors

Configure your hashing algorithm with appropriate work factors that balance security and performance. Increase work factors as hardware improves.

Implement Rate Limiting

Limit login attempts to prevent brute-force attacks, with exponential backoff for repeated failures.

Enforce Strong Passwords

Require minimum length (12+ characters), complexity, and check against common password lists.

Secure Transmission

Always use HTTPS to protect passwords during transmission, with proper certificate validation.

Password Upgrade Strategy

As cryptographic standards evolve, you may need to upgrade your password hashing algorithm. Here's a strategy for upgrading existing password hashes:

  1. Add a field to your user database to track which hashing algorithm was used
  2. When users log in successfully with the old algorithm, rehash their password with the new algorithm
  3. Update the algorithm field and store the new hash
  4. During verification, check the algorithm field to determine which verification method to use
Example: Gradual Password Upgrade Implementation
function verifyAndUpgradePasswordIfNeeded(user, providedPassword) {
  let isValid = false;
  
  // Check which algorithm was used
  if (user.passwordVersion === 'bcrypt') {
    isValid = bcrypt.compareSync(providedPassword, user.passwordHash);
  } else if (user.passwordVersion === 'pbkdf2') {
    isValid = verifyPbkdf2Password(providedPassword, user.passwordHash);
  }
  
  // If valid and using old algorithm, upgrade to new algorithm
  if (isValid && user.passwordVersion !== 'argon2id') {
    const newHash = argon2.hash(providedPassword);
    
    // Update user record with new hash and algorithm version
    updateUserPassword(user.id, newHash, 'argon2id');
  }
  
  return isValid;
}

Common Mistakes to Avoid

Be aware of these common password storage mistakes that can compromise security:

Using Deprecated Algorithms

MD5, SHA1, and even SHA256 are not suitable for password hashing as they're designed for speed, not security.

Insufficient Salt Length

Using short salts reduces their effectiveness. Salts should be at least 16 bytes of cryptographically secure random data.

Reusing Salts

Using the same salt for multiple users defeats the purpose of salting. Each user must have a unique salt.

Weak Work Factors

Setting work factors too low to improve performance significantly reduces security. The hash computation should take at least 250ms on your server hardware.

Security Reminder

Password hashing is just one part of a comprehensive security strategy. Implement additional measures like multi-factor authentication, secure session management, and regular security audits.

Compliance and Regulations

Different industries and regions have specific requirements for password storage. Here are some key regulations to be aware of:

Regulation Requirements Applies To
GDPR Requires "appropriate technical measures" for data protection, which includes secure password storage Organizations handling EU citizens' data
PCI DSS Requires strong cryptography for password protection and prohibits storing plaintext passwords Organizations handling payment card data
HIPAA Requires implementation of technical safeguards for authentication Healthcare organizations in the US
NIST Guidelines Recommends using approved algorithms (PBKDF2, bcrypt, scrypt, Argon2) with appropriate parameters US federal agencies and recommended for others

Compliance Tip

Always consult with a legal expert or compliance officer to ensure your password storage practices meet the specific requirements for your industry and region.

Conclusion

Secure password storage is a fundamental aspect of application security. By implementing modern hashing algorithms with proper salting, you can significantly reduce the risk of credential compromise even if your database is breached.

Remember these key points:

  • Never store plaintext passwords
  • Use modern, slow hashing algorithms (Argon2id, bcrypt, scrypt)
  • Always salt passwords with unique, random values
  • Configure appropriate work factors for your environment
  • Implement a strategy for upgrading password hashes as technology evolves

By following these guidelines, you'll create a robust foundation for protecting your users' credentials and maintaining their trust in your application.