Merge pull request #1495 from splitbrain/django_pbkdf2

add support for new Django hashing methods
This commit is contained in:
Andreas Gohr 2016-04-07 10:09:18 +02:00
commit 7ca2e2cf7c
4 changed files with 111 additions and 9 deletions

View File

@ -17,6 +17,24 @@ class PassHash_test extends DokuWikiTest {
$this->assertEquals('fbdb1d1b18aa6c08324b7d64b71fb76370690e1d', PassHash::hmac('sha1','',''));
$this->assertEquals('80070713463e7749b90c2dc24911e275', PassHash::hmac('md5','The quick brown fox jumps over the lazy dog','key'));
$this->assertEquals('de7c9b85b8b78aa6bc8a7a36f70a90701c9db4d9', PassHash::hmac('sha1','The quick brown fox jumps over the lazy dog','key'));
}
}
function test_djangopbkdf2() {
if(!function_exists('hash_pbkdf2') || !in_array('sha256', hash_algos())){
$this->markTestSkipped('missing hash functions for djangopbkdf2 password tests');
return;
}
$ph = new PassHash();
$knownpasses = array (
'pbkdf2_sha256$24000$LakQQ2OOTO1v$dmUgz8V7zcpaoBSA3MV76J5a4rzrszF0NpxGx6HRBbE=',
'pbkdf2_sha256$24000$PXogIZpE4gaK$F/P/L5SRrbb6taOGEr4w6DhxjMzNAj1jEWTPyAUn8WU=',
'pbkdf2_sha256$24000$vtn5APnhirmB$/jzJXYvm78X8/FCOMhGUmcCy0iWhtk0L1hcBWN1AYZc=',
'pbkdf2_sha256$24000$meyCtGKrS5Ai$vkMfMzB/yGFKplmXujgtfl3OGR27AwOQmP+YeRP6lbw=',
'pbkdf2_sha256$24000$M8ecC8zfqLmJ$l6cIa/Od+m56VMm9hJbdPNhTXZykPVbUGGTPx7/VRE4=',
);
foreach($knownpasses as $known) {
$this->assertTrue($ph->verify_hash('P4zzW0rd!', $known));
}
}
}

View File

@ -2,7 +2,7 @@
class auth_password_test extends DokuWikiTest {
// hashes for the password foo$method, using abcdefgh as salt
// hashes for the password foo$method, using abcdefgh12345678912345678912345678 as salt
var $passes = array(
'smd5' => '$1$abcdefgh$SYbjm2AEvSoHG7Xapi8so.',
'apr1' => '$apr1$abcdefgh$C/GzYTF4kOVByYLEoD5X4.',
@ -24,14 +24,24 @@ class auth_password_test extends DokuWikiTest {
// Check SHA512 only if available in this PHP
$this->passes['sha512'] = '$6$abcdefgh12345678$J9.zOcgx0lotwZdcz0uulA3IVQMinZvFZVjA5vapRLVAAqtay23XD4xeeUxQ3B4JvDWYFBIxVWW1tOYlHX13k1';
}
if(function_exists('hash_pbkdf2')) {
if(in_array('sha256', hash_algos())) {
$this->passes['djangopbkdf2_sha256'] = 'pbkdf2_sha256$24000$abcdefgh1234$R23OyZJ0nGHLG6MvPNfEkV5AOz3jUY5zthByPXs2gn0=';
}
if(in_array('sha1', hash_algos())) {
$this->passes['djangopbkdf2_sha1'] = 'pbkdf2_sha1$24000$abcdefgh1234$pOliX4vV1hgOv7lFNURIHHx41HI=';
}
}
}
function test_cryptPassword(){
foreach($this->passes as $method => $hash){
$info = "testing method $method";
$this->assertEquals(auth_cryptPassword('foo'.$method, $method,'abcdefgh12345678912345678912345678'),
$hash, $info);
$this->assertEquals(
$hash,
auth_cryptPassword('foo'.$method, $method,'abcdefgh12345678912345678912345678'),
$info);
}
}

View File

@ -42,8 +42,15 @@ class PassHash {
$magic = 'P';
} elseif(preg_match('/^\$H\$(.{31})$/', $hash, $m)) {
$method = 'pmd5';
$salt = $m[1];
$magic = 'H';
$salt = $m[1];
$magic = 'H';
} elseif(preg_match('/^pbkdf2_(\w+?)\$(\d+)\$(.{12})\$/', $hash, $m)) {
$method = 'djangopbkdf2';
$magic = array(
'algo' => $m[1],
'iter' => $m[2],
);
$salt = $m[3];
} elseif(preg_match('/^sha1\$(.{5})\$/', $hash, $m)) {
$method = 'djangosha1';
$salt = $m[1];
@ -83,7 +90,8 @@ class PassHash {
//crypt and compare
$call = 'hash_'.$method;
if($this->$call($clear, $salt, $magic) === $hash) {
$newhash = $this->$call($clear, $salt, $magic);
if($newhash === $hash) {
return true;
}
return false;
@ -435,6 +443,69 @@ class PassHash {
return 'md5$'.$salt.'$'.md5($salt.$clear);
}
/**
* Password hashing method 'djangopbkdf2'
*
* An algorithm and iteration count should be given in the opts array.
* Defaults to sha256 and 24000 iterations
*
* @param string $clear The clear text to hash
* @param string $salt The salt to use, null for random
* @param array $opts ('algo' => hash algorithm, 'iter' => iterations)
* @return string Hashed password
* @throws Exception when PHP is missing support for the method/algo
*/
public function hash_djangopbkdf2($clear, $salt=null, $opts=array()) {
$this->init_salt($salt, 12);
if(empty($opts['algo'])) {
$algo = 'sha256';
} else {
$algo = $opts['algo'];
}
if(empty($opts['iter'])) {
$iter = 24000;
} else {
$iter = (int) $opts['iter'];
}
if(!function_exists('hash_pbkdf2')) {
throw new Exception('This PHP installation has no PBKDF2 support');
}
if(!in_array($algo, hash_algos())) {
throw new Exception("This PHP installation has no $algo support");
}
$hash = base64_encode(hash_pbkdf2($algo, $clear, $salt, $iter, 0, true));
return "pbkdf2_$algo\$$iter\$$salt\$$hash";
}
/**
* Alias for djangopbkdf2 defaulting to sha256 as hash algorithm
*
* @param string $clear The clear text to hash
* @param string $salt The salt to use, null for random
* @param array $opts ('iter' => iterations)
* @return string Hashed password
* @throws Exception when PHP is missing support for the method/algo
*/
public function hash_djangopbkdf2_sha256($clear, $salt=null, $opts=array()) {
$opts['algo'] = 'sha256';
return $this->hash_djangopbkdf2($clear, $salt, $opts);
}
/**
* Alias for djangopbkdf2 defaulting to sha1 as hash algorithm
*
* @param string $clear The clear text to hash
* @param string $salt The salt to use, null for random
* @param array $opts ('iter' => iterations)
* @return string Hashed password
* @throws Exception when PHP is missing support for the method/algo
*/
public function hash_djangopbkdf2_sha1($clear, $salt=null, $opts=array()) {
$opts['algo'] = 'sha1';
return $this->hash_djangopbkdf2($clear, $salt, $opts);
}
/**
* Passwordhashing method 'bcrypt'
*

View File

@ -137,7 +137,10 @@ $meta['_authentication'] = array('fieldset');
$meta['useacl'] = array('onoff','_caution' => 'danger');
$meta['autopasswd'] = array('onoff');
$meta['authtype'] = array('authtype','_caution' => 'danger');
$meta['passcrypt'] = array('multichoice','_choices' => array('smd5','md5','apr1','sha1','ssha','lsmd5','crypt','mysql','my411','kmd5','pmd5','hmd5','mediawiki','bcrypt','djangomd5','djangosha1','sha512'));
$meta['passcrypt'] = array('multichoice','_choices' => array(
'smd5','md5','apr1','sha1','ssha','lsmd5','crypt','mysql','my411','kmd5','pmd5','hmd5',
'mediawiki','bcrypt','djangomd5','djangosha1','djangopbkdf2_sha1','djangopbkdf2_sha256','sha512'
));
$meta['defaultgroup']= array('string');
$meta['superuser'] = array('string','_caution' => 'danger');
$meta['manager'] = array('string');