add support for new Django hashing methods

New Python Django application default to PBKDF2 with SHA256 as a
password mechanism. This adds support for that mechanism in our
password hasher class. This will be needed in the tests for the new
PDO auth plugin.
This commit is contained in:
Andreas Gohr 2016-03-11 13:55:08 +01:00
parent 2a83ac60f3
commit 924cc11c61
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

@ -131,7 +131,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');