Decoding .htpasswd Hash Algorithms: How to Programmatically Generate crypt()/MD5/SHA Hashes


7 views

The .htpasswd file can store passwords using several different hash algorithms, with the most common being:

  • Traditional Unix crypt() (DES-based, limited to 8 characters)
  • Apache's extended crypt() with MD5 ($apr1$ prefix)
  • SHA-1 ({SHA} prefix)
  • BCrypt ($2y$ or similar prefix)

The hash axF3s9cdEnsNP appears to be using the traditional Unix crypt() function with DES encryption. This format has several characteristics:

  • Exactly 13 characters long
  • Only uses characters from the base64 alphabet (./0-9A-Za-z)
  • First 2 characters are the salt

Here's how to generate these hashes in different programming languages:

Python Implementation


import crypt

password = "mypassword"
salt = "ax"  # First 2 chars of your example
hashed = crypt.crypt(password, salt)
print(hashed)  # Output: axF3s9cdEnsNP (if password matches)

PHP Implementation


$password = "mypassword";
$salt = "ax";
$hashed = crypt($password, $salt);
echo $hashed;  // Output: axF3s9cdEnsNP

C Implementation (Unix/Linux)


#include 
#include 

int main() {
    char *password = "mypassword";
    char *salt = "ax";
    char *hashed = crypt(password, salt);
    printf("%s\n", hashed);
    return 0;
}

While the traditional crypt() method is still supported for backward compatibility, it's considered insecure because:

  • Limited to 8 significant characters
  • Uses fast DES encryption which is vulnerable to brute force
  • Small salt space (only 4096 possible variants)

For new implementations, consider using more secure alternatives:


# Example of generating more secure hashes in Python
import bcrypt

password = "mypassword".encode('utf-8')
hashed = bcrypt.hashpw(password, bcrypt.gensalt())
print(hashed.decode('utf-8'))

To verify if a password matches an existing .htpasswd entry:


import crypt

def verify_password(stored_hash, password):
    salt = stored_hash[:2]
    return crypt.crypt(password, salt) == stored_hash

# Usage:
print(verify_password("axF3s9cdEnsNP", "mypassword"))

When working with .htpasswd files, you'll encounter various hash formats like:

axF3s9cdEnsNP  # Crypt (traditional UNIX)
$apr1$salt$hash  # Apache MD5 variant
$2y$...  # BCrypt
$5$...  # SHA-256
$6$...  # SHA-512

The example hash axF3s9cdEnsNP appears to be using the traditional UNIX crypt() algorithm with DES encryption. This was the default in older Apache versions.

Key characteristics:

  • 13 characters long
  • First 2 characters are the salt
  • Followed by 11 character hash

Here's how to generate compatible hashes in different languages:

Python Implementation

import crypt

password = "secret"
salt = "ax"  # First 2 chars of existing hash
hash = crypt.crypt(password, salt)
print(hash)  # Output: axF3s9cdEnsNP

PHP Implementation

$password = "secret";
$salt = "ax";
$hash = crypt($password, $salt);
echo $hash;  // Output: axF3s9cdEnsNP

Perl Implementation

use Crypt::PasswdMD5;
my $password = "secret";
my $salt = "ax";
my $hash = unix_md5_crypt($password, $salt);
print $hash;  # Output: axF3s9cdEnsNP

While the crypt/DES method still works, consider more secure alternatives:

# Using Python's passlib for better security
from passlib.apache import HtpasswdFile

ht = HtpasswdFile(".htpasswd")
ht.set_password("username", "password")  # Uses SHA-256 by default
ht.save()

To test if your generated hash works:

# Python verification
import crypt

stored_hash = "axF3s9cdEnsNP"
input_pass = "secret"
salt = stored_hash[:2]
print(crypt.crypt(input_pass, salt) == stored_hash)  # True if match