How to programmatically build an APR1-MD5 using PHP
Asked Answered
F

5

7

Much like this question I want to generate an htpasswd file entry from PHP. However it needs to be the APR1 (Apache) style, as mentioned in the original answer (The answer did not show how to implement the APR1 style), to work with mod_dav_svn.

I can't seem to find a working implementation that will create the password.

I found this (I forget where now):

function crypt_apr1_md5($plainpasswd) {
    $salt = substr(str_shuffle("abcdefghijklmnopqrstuvwxyz0123456789"), 0, 8);
    $len = strlen($plainpasswd);
    $text = $plainpasswd.'$apr1$'.$salt;
    $bin = pack("H32", md5($plainpasswd.$salt.$plainpasswd));
    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) : $plainpasswd{0}; }
    $bin = pack("H32", md5($text));
    for($i = 0; $i < 1000; $i++) {
        $new = ($i & 1) ? $plainpasswd : $bin;
        if ($i % 3) $new .= $salt;
        if ($i % 7) $new .= $plainpasswd;
        $new .= ($i & 1) ? $bin : $plainpasswd;
        $bin = pack("H32", md5($new));
    }
    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;
}

But it doesn't generate a working password. I think this might be something to do with the version of apache but am not sure. (I am running on CENTOS 5)

Furor answered 24/6, 2009 at 14:37 Comment(2)
This code is from the comments section of the PHP 'crypt' documentation: php.net/manual/en/function.crypt.phpSmock
I was getting an error where it would complain that $tmp was undefined, I simply added $tmp="" to the beginning of the funciton and it works correctly now, but I'm not sure if that is the correct way to fix it.Priestess
F
2

It turns out I made a mistake and this function does in fact create working APR1 htpasswd entries. They do look different to the ones Apache creates but they do work.

Furor answered 28/7, 2009 at 21:58 Comment(0)
C
1

Look for existing components that do it on sites like phpclasses.org. One example: http://www.phpclasses.org/browse/package/5066.html.

Carotenoid answered 24/6, 2009 at 16:22 Comment(1)
This particular example from phpclasses doesn't even support apr1/MD5 password files, so it's useless for this particular question.Staceystaci
M
1

Thanks you! It works like a charm.

Just a small comment: The salt can also contain "./" and "A..Z" besides "a..z0..9", so it is the same string as the 'translate-to' string in the last line. And sometimes you want to set the salt in addition, to create reproducable results, like:

function crypt_apr1_md5( $plainpasswd, $salt = '' ) {
$translateTo = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
if ( $salt == '' ) { $salt = substr(str_shuffle($translateTo), 0, 8); }

...

$tmp = strtr(strrev(substr(base64_encode($tmp), 2)), "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/", $translateTo);
return '$apr1$'.$salt.'$'.$tmp;
}

Merthiolate answered 15/6, 2010 at 9:33 Comment(0)
G
0

This might be a bit hacky, but have you considered using the exec() function to call the command that generates the htpasswd ?

Germanium answered 24/6, 2009 at 15:24 Comment(1)
Yes I have, I was really trying to avoid using any system calls. In fact I am doing this to replace exactly that. It will be my last resort if I can't find another way. Thanks though!Furor
Z
0

See on this:

1:   private function crypt_apr1_md5($plainpasswd) { </br>
7:   for($i = $len; $i > 0; $i >>= 1) { $text .= ($i & 1) ? chr(o) : $plainpasswd{0}; } </br>
16:  $tmp = ''; </br>
Zenobia answered 28/2, 2015 at 18:48 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.