10

i have a protected directory where only user on .htpasswd can access, but sometimes it requires the user to change password or username, edit a specific username password to his username him self

sample users
kevien : kka
mike : mike

And let say i want to change kevien to XYZ

And same thing goes to password

Mahmoud
  • 727
  • 3
  • 11
  • 23

5 Answers5

11

I have modified function to use all types of crypt alghoritms. Someone may find it useful:

/*
Function change password in htpasswd.
Arguments:
$user    > User name we want to change password to.
$newpass > New password
$type    > Type of cryptogrphy: DES, SHA, MD5. 
$salt    > Option: Add your custom salt (hashing string). 
           Salt is applied to DES and MD5 and must be in range 0-9A-Za-z
$oldpass > Option: Add more security, user must known old password to change it. 
           This option is not supported for DES and MD5 without salt!!!
$path    > Path to .htaccess file which contain the password protection. 
           Path to password file is obtained from this .htaccess file. 
*/  

function changePass($user, $newpass, $type="SHA", $salt="", $oldpass="", $path=".htaccess")
{
    switch ($type) {
        case "DES" :
            $salt = substr($salt,0,2);  // Salt must be 2 char range 0-9A-Za-z
            $newpass = crypt($newpass,$salt);
            if ($oldpass != null) {
                $oldpass = crypt($oldpass,$salt);
            }
            break;

        case "SHA" :
            $newpass = '{SHA}'.base64_encode(sha1($newpass, TRUE));
            if ($oldpass != null) {
                $oldpass = '{SHA}'.base64_encode(sha1($oldpass, TRUE));
            }
            break;

        case "MD5" :
            $salt = substr($salt,0,8);  //Salt must be max 8 char range 0-9A-Za-z
            $newpass = crypt_apr1_md5($newpass, $salt);
            if ($oldpass != null) {
                $oldpass = crypt_apr1_md5($oldpass, $salt);
            }
            break;

        default:
            return false;
            break;
    }

    $hta_arr = explode("\n", file_get_contents($path));

    foreach ($hta_arr as $line) {
        $line = preg_replace('/\s+/','',$line); // remove spaces
        if ($line) {
            $line_arr = explode('"', $line);
            if (strcmp($line_arr[0],"AuthUserFile") == 0) {
                $path_htaccess = $line_arr[1];
            }   
        }
    }  
    $htp_arr = explode("\n", file_get_contents($path_htaccess));

    $new_file = "";
    foreach ($htp_arr as $line) {
        $line = preg_replace('/\s+/', '', $line); // remove spaces
        if ($line) {
            list($usr, $pass) = explode(":", $line, 2);
            if (strcmp($user, $usr) == 0) {
                if ($oldpass != null) {
                    if ($oldpass == $pass) {
                        $new_file .= $user.':'.$newpass."\n";
                    } else {
                        return false;
                    }
                } else {
                    $new_file .= $user.':'.$newpass."\n";
                }
            } else {
                $new_file .= $user.':'.$pass."\n";
            }
        }
    }
    $f = fopen($path_htaccess,"w") or die("couldn't open the file");
    fwrite($f, $new_file);
    fclose($f);
    return true;
}

Function for generating Apache like MD5:

/**
 * @param string $password
 * @param string|null $salt
 * @ref https://stackoverflow.com/a/8786956
 */
function crypt_apr1_md5($password, $salt = null)
{
    if (!$salt) {
        $salt = substr(base_convert(bin2hex(random_bytes(6)), 16, 36), 1, 8);
    }
    $len = strlen($password);

    $text = $password . '$apr1$' . $salt;

    $bin = pack("H32", md5($password . $salt . $password));

    for ($i = $len; $i > 0; $i -= 16) {
        $text .= substr($bin, 0, min(16, $i));
    }

    for ($i = $len; $i > 0; $i >>= 1) {
        $text .= ($i & 1) ? chr(0) : $password[0];
    }

    $bin = pack("H32", md5($text));

    for ($i = 0; $i < 1000; $i++) {
        $new = ($i & 1) ? $password : $bin;

        if ($i % 3) {
            $new .= $salt;
        }

        if ($i % 7) {
            $new .= $password;
        }

        $new .= ($i & 1) ? $bin : $password;
        $bin = pack("H32", md5($new));
    }

    $tmp = '';

    for ($i = 0; $i < 5; $i++) {
        $k = $i + 6;
        $j = $i + 12;

        if ($j == 16) {
            $j = 5;
        }

        $tmp = $bin[$i] . $bin[$k] . $bin[$j] . $tmp;
    }

    $tmp = chr(0) . chr(0) . $bin[11] . $tmp;
    $tmp = strtr(
        strrev(substr(base64_encode($tmp), 2)),
        "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",
        "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
    );

    return "$" . "apr1" . "$" . $salt . "$" . $tmp;
}

Demo of crypt_apr1_md5() is available here.

Note that as of Apache 2.4, bcrypt is supported, so you can (and SHOULD) just use password_hash() on newer versions of Apache for this purpose.

Scott Arciszewski
  • 33,610
  • 16
  • 89
  • 206
user1138448
  • 111
  • 1
  • 2
  • The code is very good, just one suggestion that to put $tmp = ""; before: for ($i = 0; $i < 5; $i++) { //so that undefined notice message does not occur. – Ghulam Ali Jul 02 '17 at 11:15
  • I've updated the code to enhance readability, and to point out that bcrypt is supported as of Apache 2.4. Just use bcrypt, you'll be much happier. – Scott Arciszewski Oct 25 '18 at 13:34
4

Don't. Store your authdb in a database instead, via e.g. mod_auth_mysql.

Ignacio Vazquez-Abrams
  • 776,304
  • 153
  • 1,341
  • 1,358
4

Ofc this is just a sample, that will read your current file, find the given username and change either it is password of username.

Please keep in mind that this code is not safe and you would still need to parse the username and password so it does not break your file.

    $username = $_POST['user'];
    $password = $_POST['pass'];
    $new_username = $_POST['newuser'];
    $new_password = $_POST['newpass'];
    $action = $_POST['action'];
    //read the file into an array
    $lines = explode("\n", file_get_contents('.htpasswd'));

    //read the array and change the data if found
    $new_file = "";
    foreach($lines as $line)
    {
        $line = preg_replace('/\s+/','',$line); // remove spaces
        if ($line) {
            list($user, $pass) = split(":", $line, 2);
            if ($user == $username) {
                if ($action == "password") {
                    $new_file .= $user.':'.$new_password."\n";
                } else {
                    $new_file .= $new_username.':'.$pass."\n";
                }
            } else {
                $new_file .= $user.':'.$pass."\n";
            }
        }
    }

    //save the information
    $f=fopen(".htpasswd","w") or die("couldn't open the file");
    fwrite($f,$new_file);
    fclose($f);
Prix
  • 19,417
  • 15
  • 73
  • 132
  • Does this code really work correct? Doesn´t it say "if the user stored in the file does not match the user given by the input than add the user plus password to the file"? – Qullbrune Feb 17 '15 at 10:12
  • If user does not match with one already in the file it will change nothing and it says at the top `Ofc this is just a sample` means its untested, but the logic is there and you can always make a test file and test your self. – Prix Feb 17 '15 at 17:07
3

Googled "php generate htpasswd", got this article: How to create a password for a .htpasswd file using PHP.

The key line seems to be:

$password = crypt($clearTextPassword, base64_encode($clearTextPassword));

So I imagine you'd read in the file contents with file_get_contents, parse it into an associative array, modify the relevant entries (encrypting the password as shown above), write the array back into a string, and use file_put_contents to write the file back out.

This is most definitely not standard practice, however. Sounds like the job for a database. If you feel weird about setting up a whole database server, and your host supports it, SQLite might be a good choice.

Matchu
  • 83,922
  • 18
  • 153
  • 160
  • i found a fully functional class but the problem with it is that if the user exist it doesn't over write it, it adds another line, there i well be having the same user but multiple passwords `http://www.weberdev.com/get_example-4178.html` – Mahmoud Jun 08 '10 at 04:17
  • @Mahmoud - how interesting. I wish you luck in either modifying the code to fit your needs, or writing your own solution. – Matchu Jun 08 '10 at 04:20
  • i am only using there idea, not the code itself, i want to create a solutions but i am stuck after encoding the password, is it possible to search and overriding the text – Mahmoud Jun 08 '10 at 04:26
0

Just in case someone is just looking for a working script, here is a solution.

It is the script published here by Kavoir with a minor change: http://www.kavoir.com/backyard/showthread.php?28-Use-PHP-to-generate-edit-and-update-htpasswd-and-htgroup-authentication-files

<?php
/*
 $pairs = array(
  'username' = 'password',
 );
*/

// Algorithm: SHA1

class Htpasswd {

private $file = '';

public function __construct($file) {
    if (file_exists($file)) {
        $this -> file = $file;
    } else {
        return false;
    }
}

private function write($pairs = array()) {
    $str = '';
    foreach ($pairs as $username => $password) {
        $str .= "$username:{SHA}$password\n";
    }
    file_put_contents($this -> file, $str);
}

private function read() {
    $pairs = array();
    $fh = fopen($this -> file, 'r');
    while (!feof($fh)) {
        $pair_str = str_replace("\n", '', fgets($fh));
        $pair_array = explode(':{SHA}', $pair_str);
        if (count($pair_array) == 2) {
            $pairs[$pair_array[0]] = $pair_array[1];
        }
    }
    return $pairs;
}

public function addUser($username = '', $clear_password = '') {
    if (!empty($username) && !empty($clear_password)) {
        $all = $this -> read();
      //  if (!array_key_exists($username, $all)) {
            $all[$username] = $this -> getHash($clear_password);
            $this -> write($all);
     //   }
    } else {
        return false;
    }
}

public function deleteUser($username = '') {
    $all = $this -> read();
    if (array_key_exists($username, $all)) {
        unset($all[$username]);
        $this -> write($all);
    } else {
        return false;
    }
}

public function doesUserExist($username = '') {
    $all = $this -> read();
    if (array_key_exists($username, $all)) {
        return true;
    } else {
        return false;
    }
}

private function getHash($clear_password = '') {
    if (!empty($clear_password)) {
        return base64_encode(sha1($clear_password, true));
    } else {
        return false;
    }
}

}  

You can use this script like:

$htp = new Htpasswd('.htpasswd');
$htp -> addUser('username1', 'clearpassword1');  // this will add or edit the user
$htp -> deleteUser('username1');  
// check if a certain username exists
if ($htp -> doesUserExist('username1')) {
 // user exists
}  
Qullbrune
  • 1,925
  • 2
  • 20
  • 20