aboutsummaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
-rw-r--r--_test/tests/inc/auth_password.test.php14
-rw-r--r--inc/PassHash.php134
2 files changed, 136 insertions, 12 deletions
diff --git a/_test/tests/inc/auth_password.test.php b/_test/tests/inc/auth_password.test.php
index 1f4e47a30..beb95c060 100644
--- a/_test/tests/inc/auth_password.test.php
+++ b/_test/tests/inc/auth_password.test.php
@@ -92,6 +92,20 @@ class auth_password_test extends DokuWikiTest {
$this->assertTrue(auth_verifyPassword('test12345', '$H$9IQRaTwmfeRo7ud9Fh4E2PdI0S3r.L0'));
}
+ function test_verifypassword_drupal_sha512() {
+ $this->assertTrue(auth_verifypassword('drupal_sha512', '$S$D7JxIm0f7QKO3zjwVS1RH4AW8sYvmLjO0.Rn4swH0JVt6OrZ4yzZ'));
+ }
+
+ function test_verifypassword_drupal_migrated_6to7() {
+ $this->assertTrue(auth_verifypassword('pouette1234', 'U$S$9c47LGZuhR6TvhRQXzymkJIQ3mXthUCc6KDEGTt4B7eOL/H9Ykuy'));
+ }
+
+ function test_verifyPassword_seafilepbkdf2() {
+ $hash='PBKDF2SHA256$10000$99227b6df52aa1394b5ca0aceee2733dd6c2670c85bbe26c751a2c65e79d4db7$d61dd1c4df6873c73813fe97f96d0e917792602a33966f3fab0eef154637cc84';
+ $pw='@STR0NGpassW0RD';
+ $this->assertTrue(auth_verifyPassword($pw, $hash));
+ }
+
function test_veryPassword_mediawiki() {
$this->assertTrue(auth_verifyPassword('password', ':B:838c83e1:e4ab7024509eef084cdabd03d8b2972c'));
}
diff --git a/inc/PassHash.php b/inc/PassHash.php
index c0e61f409..1189da0b7 100644
--- a/inc/PassHash.php
+++ b/inc/PassHash.php
@@ -9,6 +9,7 @@ namespace dokuwiki;
* This class implements various mechanisms used to hash passwords
*
* @author Andreas Gohr <andi@splitbrain.org>
+ * @author Schplurtz le Déboulonné <Schplurtz@laposte.net>
* @license LGPL2
*/
class PassHash {
@@ -20,6 +21,7 @@ class PassHash {
* match true is is returned else false
*
* @author Andreas Gohr <andi@splitbrain.org>
+ * @author Schplurtz le Déboulonné <Schplurtz@laposte.net>
*
* @param string $clear Clear-Text password
* @param string $hash Hash to compare against
@@ -31,6 +33,12 @@ class PassHash {
$magic = '';
//determine the used method and salt
+ if (substr($hash, 0, 2) == 'U$') {
+ // This may be an updated password from user_update_7000(). Such hashes
+ // have 'U' added as the first character and need an extra md5().
+ $hash = substr($hash, 1);
+ $clear = md5($clear);
+ }
$len = strlen($hash);
if(preg_match('/^\$1\$([^\$]{0,8})\$/', $hash, $m)) {
$method = 'smd5';
@@ -40,6 +48,10 @@ class PassHash {
$method = 'apr1';
$salt = $m[1];
$magic = 'apr1';
+ } elseif(preg_match('/^\$S\$(.{52})$/', $hash, $m)) {
+ $method = 'drupal_sha512';
+ $salt = $m[1];
+ $magic = 'S';
} elseif(preg_match('/^\$P\$(.{31})$/', $hash, $m)) {
$method = 'pmd5';
$salt = $m[1];
@@ -55,6 +67,13 @@ class PassHash {
'iter' => $m[2],
);
$salt = $m[3];
+ } elseif(preg_match('/^PBKDF2(SHA\d+)\$(\d+)\$([[:xdigit:]]+)\$([[:xdigit:]]+)$/', $hash, $m)) {
+ $method = 'seafilepbkdf2';
+ $magic = array(
+ 'algo' => $m[1],
+ 'iter' => $m[2],
+ );
+ $salt = $m[3];
} elseif(preg_match('/^sha1\$(.{5})\$/', $hash, $m)) {
$method = 'djangosha1';
$salt = $m[1];
@@ -348,17 +367,24 @@ class PassHash {
}
/**
- * Password hashing method 'pmd5'
+ * Password stretched hashing wrapper.
*
- * Uses salted MD5 hashs. Salt is 1+8 bytes long, 1st byte is the
- * iteration count when given, for null salts $compute is used.
+ * Initial hash is repeatedly rehashed with same password.
+ * Any salted hash algorithm supported by PHP hash() can be used. Salt
+ * is 1+8 bytes long, 1st byte is the iteration count when given. For null
+ * salts $compute is used.
*
- * The actual iteration count is the given count squared, maximum is
- * 30 (-> 1073741824). If a higher one is given, the function throws
- * an exception.
+ * The actual iteration count is 2 to the power of the given count,
+ * maximum is 30 (-> 2^30 = 1_073_741_824). If a higher one is given,
+ * the function throws an exception.
+ * This iteration count is expected to grow with increasing power of
+ * new computers.
*
- * @link http://www.openwall.com/phpass/
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @author Schplurtz le Déboulonné <Schplurtz@laposte.net>
+ * @link http://www.openwall.com/phpass/
*
+ * @param string $algo The hash algorithm to be used
* @param string $clear The clear text to hash
* @param string $salt The salt to use, null for random
* @param string $magic The hash identifier (P or H)
@@ -366,13 +392,13 @@ class PassHash {
* @throws \Exception
* @return string Hashed password
*/
- public function hash_pmd5($clear, $salt = null, $magic = 'P', $compute = 8) {
+ protected function stretched_hash($algo, $clear, $salt = null, $magic = 'P', $compute = 8) {
$itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
if(is_null($salt)) {
$this->init_salt($salt);
$salt = $itoa64[$compute].$salt; // prefix iteration count
}
- $iterc = $salt[0]; // pos 0 of salt is iteration count
+ $iterc = $salt[0]; // pos 0 of salt is log2(iteration count)
$iter = strpos($itoa64, $iterc);
if($iter > 30) {
@@ -384,14 +410,14 @@ class PassHash {
$salt = substr($salt, 1, 8);
// iterate
- $hash = md5($salt.$clear, true);
+ $hash = hash($algo, $salt . $clear, TRUE);
do {
- $hash = md5($hash.$clear, true);
+ $hash = hash($algo, $hash.$clear, true);
} while(--$iter);
// encode
$output = '';
- $count = 16;
+ $count = strlen($hash);
$i = 0;
do {
$value = ord($hash[$i++]);
@@ -413,6 +439,49 @@ class PassHash {
}
/**
+ * Password hashing method 'pmd5'
+ *
+ * Repeatedly uses salted MD5 hashs. See stretched_hash() for the
+ * details.
+ *
+ *
+ * @author Schplurtz le Déboulonné <Schplurtz@laposte.net>
+ * @link http://www.openwall.com/phpass/
+ * @see PassHash::stretched_hash() for the implementation details.
+ *
+ * @param string $clear The clear text to hash
+ * @param string $salt The salt to use, null for random
+ * @param string $magic The hash identifier (P or H)
+ * @param int $compute The iteration count for new passwords
+ * @throws Exception
+ * @return string Hashed password
+ */
+ public function hash_pmd5($clear, $salt = null, $magic = 'P', $compute = 8) {
+ return $this->stretched_hash('md5', $clear, $salt, $magic, $compute);
+ }
+
+ /**
+ * Password hashing method 'drupal_sha512'
+ *
+ * Implements Drupal salted sha512 hashs. Drupal truncates the hash at 55
+ * characters. See stretched_hash() for the details;
+ *
+ * @author Schplurtz le Déboulonné <Schplurtz@laposte.net>
+ * @link https://api.drupal.org/api/drupal/includes%21password.inc/7.x
+ * @see PassHash::stretched_hash() for the implementation details.
+ *
+ * @param string $clear The clear text to hash
+ * @param string $salt The salt to use, null for random
+ * @param string $magic The hash identifier (S)
+ * @param int $compute The iteration count for new passwords (defautl is drupal 7's)
+ * @throws Exception
+ * @return string Hashed password
+ */
+ public function hash_drupal_sha512($clear, $salt = null, $magic = 'S', $compute = 15) {
+ return substr($this->stretched_hash('sha512', $clear, $salt, $magic, $compute), 0, 55);
+ }
+
+ /**
* Alias for hash_pmd5
*
* @param string $clear
@@ -462,6 +531,47 @@ class PassHash {
}
/**
+ * Password hashing method 'seafilepbkdf2'
+ *
+ * An algorithm and iteration count should be given in the opts array.
+ *
+ * Hash algorithm is the string that is in the password string in seafile
+ * database. It has to be converted to a php algo name.
+ *
+ * @author Schplurtz le Déboulonné <Schplurtz@laposte.net>
+ * @see https://stackoverflow.com/a/23670177
+ *
+ * @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_seafilepbkdf2($clear, $salt=null, $opts=array()) {
+ $this->init_salt($salt, 64);
+ if(empty($opts['algo'])) {
+ $prefixalgo='SHA256';
+ } else {
+ $prefixalgo=$opts['algo'];
+ }
+ $algo = strtolower($prefixalgo);
+ if(empty($opts['iter'])) {
+ $iter = 10000;
+ } 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 = hash_pbkdf2($algo, $clear, hex2bin($salt), $iter, 0);
+ return "PBKDF2$prefixalgo\$$iter\$$salt\$$hash";
+ }
+
+ /**
* Password hashing method 'djangopbkdf2'
*
* An algorithm and iteration count should be given in the opts array.