From 26dc61cbe59c1cb77392ae43f0f72183fa70c273 Mon Sep 17 00:00:00 2001 From: Brandon Date: Tue, 2 Oct 2018 13:07:01 -0400 Subject: [PATCH 1/2] Create Drupal 7 hash algorithm option --- lib/Crypto/Drupal7.php | 80 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 lib/Crypto/Drupal7.php diff --git a/lib/Crypto/Drupal7.php b/lib/Crypto/Drupal7.php new file mode 100644 index 0000000..5b32a91 --- /dev/null +++ b/lib/Crypto/Drupal7.php @@ -0,0 +1,80 @@ + + * @author Marcin Łojewski + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace OCA\UserSQL\Crypto; + +use OCP\IL10N; + +require_once "Phpass.php"; + +/** + * Drupal 7 overrides of phpass hash implementation. + * + * @author BrandonKerr + */ +class Drupal7 extends Phpass +{ + + /** + * The expected (and maximum) number of characters in a hashed password. + */ + const DRUPAL_HASH_LENGTH = 55; + + /** + * @param string $password Password to encrypt. + * @param string $setting Hash settings. + * + * @return string|null Generated hash. Null on invalid settings. + */ + private function crypt($password, $setting) + { + $countLog2 = strpos(self::ITOA64, $setting[3]); + if ($countLog2 < 7 || $countLog2 > 30) { + return null; + } + + $count = 1 << $countLog2; + + $salt = substr($setting, 4, 8); + if (strlen($salt) !== 8) { + return null; + } + + $hash = hash('sha512', $salt . $password, true); + do { + $hash = hash('sha512', $hash . $password, true); + } while (--$count); + + $output = substr($setting, 0, 12); + $output .= $this->encode64($hash, strlen($hash)); + + return substr($output, 0, self::DRUPAL_HASH_LENGTH); + } + + + /** + * @inheritdoc + */ + protected function getAlgorithmName() + { + return "Drupal 7"; + } +} From 23ccb5d7b07a39f2aac958e590dc2f6451658b00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20=C5=81ojewski?= Date: Wed, 3 Oct 2018 21:44:02 +0200 Subject: [PATCH 2/2] Drupal 7 hash - fixes --- CHANGELOG.md | 1 + README.md | 1 + lib/Crypto/Drupal7.php | 50 +++++++++-------------------- lib/Crypto/Phpass.php | 20 +++++++++--- tests/Crypto/Drupal7Test.php | 61 ++++++++++++++++++++++++++++++++++++ 5 files changed, 94 insertions(+), 39 deletions(-) create mode 100644 tests/Crypto/Drupal7Test.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 2684f26..8937916 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Added - Whirlpool hash algorithm - 'Prepend salt' toggle +- Drupal 7 hash algorithm ### Fixed - Error when 'Display name' not set - Encoding of iteration for 'Extended DES (Crypt)' diff --git a/README.md b/README.md index 8bccbcf..f99d4fb 100644 --- a/README.md +++ b/README.md @@ -190,6 +190,7 @@ MD5 (Crypt) | | $1$RzaFbNcU$u9adfTY/Q6za6nu0Ogrl1/ SHA256 (Crypt) | Generates hash with 5000 rounds. | $5$rounds=5000$VIYD0iHkg7uY9SRc$v2XLS/9dvfFN84mzGvW9wxnVt9Xd/urXaaTkpW8EwD1 SHA512 (Crypt) | Generates hash with 5000 rounds. | $6$rounds=5000$yH.Q0OL4qbCOUJ3q$Xry5EVFva3wKnfo8/ktrugmBd8tcl34NK6rXInv1HhmdSUNLEm0La9JnA57rqwQ.9/Bz513MD4tvmmISLUIHs/ Standard DES (Crypt) | | yTBnb7ab/N072 +Drupal 7 | See [phpass](http://www.openwall.com/phpass/). | $S$DC7eCpJQ3SUQtW4Bp.vKb2rpeaffi4iqk9OpYwJyEoSMsezn67Sl Joomla MD5 Encryption | Generates 32 chars salt. | 14d21b49b0f13e2acba962b6b0039edd:haJK0yTvBXTNMh76xwEw5RYEVpJsN8us MD5 | No salt supported. | 5f4dcc3b5aa765d61d8327deb882cf99 Portable PHP password | See [phpass](http://www.openwall.com/phpass/). | $P$BxrwraqNTi4as0EI.IpiA/K.muk9ke/ diff --git a/lib/Crypto/Drupal7.php b/lib/Crypto/Drupal7.php index 5b32a91..567e342 100644 --- a/lib/Crypto/Drupal7.php +++ b/lib/Crypto/Drupal7.php @@ -21,54 +21,34 @@ namespace OCA\UserSQL\Crypto; -use OCP\IL10N; - -require_once "Phpass.php"; - /** * Drupal 7 overrides of phpass hash implementation. * * @author BrandonKerr + * @author Marcin Łojewski */ class Drupal7 extends Phpass { - - /** - * The expected (and maximum) number of characters in a hashed password. - */ - const DRUPAL_HASH_LENGTH = 55; + /** + * The expected (and maximum) number of characters in a hashed password. + */ + const DRUPAL_HASH_LENGTH = 55; /** - * @param string $password Password to encrypt. - * @param string $setting Hash settings. - * - * @return string|null Generated hash. Null on invalid settings. + * @inheritdoc */ - private function crypt($password, $setting) + protected function crypt($password, $setting) { - $countLog2 = strpos(self::ITOA64, $setting[3]); - if ($countLog2 < 7 || $countLog2 > 30) { - return null; - } - - $count = 1 << $countLog2; - - $salt = substr($setting, 4, 8); - if (strlen($salt) !== 8) { - return null; - } - - $hash = hash('sha512', $salt . $password, true); - do { - $hash = hash('sha512', $hash . $password, true); - } while (--$count); - - $output = substr($setting, 0, 12); - $output .= $this->encode64($hash, strlen($hash)); - - return substr($output, 0, self::DRUPAL_HASH_LENGTH); + return substr(parent::crypt($password, $setting), 0, self::DRUPAL_HASH_LENGTH); } + /** + * @inheritdoc + */ + protected function hash($input) + { + return hash('sha512', $input, true); + } /** * @inheritdoc diff --git a/lib/Crypto/Phpass.php b/lib/Crypto/Phpass.php index d193917..a430ae3 100644 --- a/lib/Crypto/Phpass.php +++ b/lib/Crypto/Phpass.php @@ -61,7 +61,7 @@ class Phpass extends AbstractAlgorithm * * @return string|null Generated hash. Null on invalid settings. */ - private function crypt($password, $setting) + protected function crypt($password, $setting) { $countLog2 = strpos(self::ITOA64, $setting[3]); if ($countLog2 < 7 || $countLog2 > 30) { @@ -75,17 +75,29 @@ class Phpass extends AbstractAlgorithm return null; } - $hash = md5($salt . $password, true); + $hash = $this->hash($salt . $password); do { - $hash = md5($hash . $password, true); + $hash = $this->hash($hash . $password); } while (--$count); $output = substr($setting, 0, 12); - $output .= $this->encode64($hash, 16); + $output .= $this->encode64($hash, strlen($hash)); return $output; } + /** + * Apply hash function to input. + * + * @param string $input Input string. + * + * @return string Hashed input. + */ + protected function hash($input) + { + return md5($input, true); + } + /** * Encode binary input to base64 string. * diff --git a/tests/Crypto/Drupal7Test.php b/tests/Crypto/Drupal7Test.php new file mode 100644 index 0000000..834fcff --- /dev/null +++ b/tests/Crypto/Drupal7Test.php @@ -0,0 +1,61 @@ + + * @author Marcin Łojewski + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Tests\UserSQL\Crypto; + +use OCA\UserSQL\Crypto\Drupal7; +use OCA\UserSQL\Crypto\IPasswordAlgorithm; +use OCP\IL10N; +use Test\TestCase; + +/** + * Unit tests for class Drupal7. + * + * @author Marcin Łojewski + */ +class Drupal7Test extends TestCase +{ + /** + * @var IPasswordAlgorithm + */ + private $crypto; + + public function testCheckPassword() + { + $this->assertTrue( + $this->crypto->checkPassword( + "password", "\$S\$DC7eCpJQ3SUQtW4Bp.vKb2rpeaffi4iqk9OpYwJyEoSMsezn67Sl" + ) + ); + } + + public function testPasswordHash() + { + $hash = $this->crypto->getPasswordHash("password"); + $this->assertTrue($this->crypto->checkPassword("password", $hash)); + } + + protected function setUp() + { + parent::setUp(); + $this->crypto = new Drupal7($this->createMock(IL10N::class)); + } +}