Аутентификация простого текстового пароля против хеша md5 - PullRequest
1 голос
/ 09 февраля 2012

Здравствуйте и спасибо за чтение,

У меня есть задача аутентифицировать пару un / pw по паролю, хранящемуся в базе данных MySQL, в которой joomla служит CMS / frontend.

Веб-приложение joomla поддерживает сохранение имен пользователей и паролей в указанной базе данных, и, по-видимому, при сохранении нового пользователя оно выполняет следующие шаги -

$salt  = JUserHelper::genRandomPassword(32);
$crypt = JUserHelper::getCryptedPassword($array['password'], $salt);
$array['password'] = $crypt.':'.$salt;

genRandomPassword выглядит как -

/**
 * Generate a random password
 *
 * @static
 * @param   int     $length Length of the password to generate
 * @return  string          Random Password
 * @since   1.5
 */
public static function genRandomPassword($length = 8)
{
    $salt = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
    $len = strlen($salt);
    $makepass = '';

    $stat = @stat(__FILE__);
    if (empty($stat) || !is_array($stat)) $stat = array(php_uname());

    mt_srand(crc32(microtime() . implode('|', $stat)));

    for ($i = 0; $i < $length; $i ++) {
        $makepass .= $salt[mt_rand(0, $len -1)];
    }

    return $makepass;
}

Наконец, getCryptedPassword и getSalt выглядят как -

/**
 * Formats a password using the current encryption.
 *
 * @access  public
 * @param   string  $plaintext  The plaintext password to encrypt.
 * @param   string  $salt       The salt to use to encrypt the password. []
 *                              If not present, a new salt will be
 *                              generated.
 * @param   string  $encryption The kind of pasword encryption to use.
 *                              Defaults to md5-hex.
 * @param   boolean $show_encrypt  Some password systems prepend the kind of
 *                              encryption to the crypted password ({SHA},
 *                              etc). Defaults to false.
 *
 * @return string  The encrypted password.
 */
public static function getCryptedPassword($plaintext, $salt = '', $encryption = 'md5-hex', $show_encrypt = false)
{
    // Get the salt to use.
    $salt = JUserHelper::getSalt($encryption, $salt, $plaintext);

    // Encrypt the password.
    switch ($encryption)
    {
        case 'plain' :
            return $plaintext;

        case 'sha' :
            $encrypted = base64_encode(mhash(MHASH_SHA1, $plaintext));
            return ($show_encrypt) ? '{SHA}'.$encrypted : $encrypted;

        case 'crypt' :
        case 'crypt-des' :
        case 'crypt-md5' :
        case 'crypt-blowfish' :
            return ($show_encrypt ? '{crypt}' : '').crypt($plaintext, $salt);

        case 'md5-base64' :
            $encrypted = base64_encode(mhash(MHASH_MD5, $plaintext));
            return ($show_encrypt) ? '{MD5}'.$encrypted : $encrypted;

        case 'ssha' :
            $encrypted = base64_encode(mhash(MHASH_SHA1, $plaintext.$salt).$salt);
            return ($show_encrypt) ? '{SSHA}'.$encrypted : $encrypted;

        case 'smd5' :
            $encrypted = base64_encode(mhash(MHASH_MD5, $plaintext.$salt).$salt);
            return ($show_encrypt) ? '{SMD5}'.$encrypted : $encrypted;

        case 'aprmd5' :
            $length = strlen($plaintext);
            $context = $plaintext.'$apr1$'.$salt;
            $binary = JUserHelper::_bin(md5($plaintext.$salt.$plaintext));

            for ($i = $length; $i > 0; $i -= 16) {
                $context .= substr($binary, 0, ($i > 16 ? 16 : $i));
            }
            for ($i = $length; $i > 0; $i >>= 1) {
                $context .= ($i & 1) ? chr(0) : $plaintext[0];
            }

            $binary = JUserHelper::_bin(md5($context));

            for ($i = 0; $i < 1000; $i ++) {
                $new = ($i & 1) ? $plaintext : substr($binary, 0, 16);
                if ($i % 3) {
                    $new .= $salt;
                }
                if ($i % 7) {
                    $new .= $plaintext;
                }
                $new .= ($i & 1) ? substr($binary, 0, 16) : $plaintext;
                $binary = JUserHelper::_bin(md5($new));
            }

            $p = array ();
            for ($i = 0; $i < 5; $i ++) {
                $k = $i +6;
                $j = $i +12;
                if ($j == 16) {
                    $j = 5;
                }
                $p[] = JUserHelper::_toAPRMD5((ord($binary[$i]) << 16) | (ord($binary[$k]) << 8) | (ord($binary[$j])), 5);
            }

            return '$apr1$'.$salt.'$'.implode('', $p).JUserHelper::_toAPRMD5(ord($binary[11]), 3);

        case 'md5-hex' :
        default :
            $encrypted = ($salt) ? md5($plaintext.$salt) : md5($plaintext);
            return ($show_encrypt) ? '{MD5}'.$encrypted : $encrypted;
    }
}

/**
 * Returns a salt for the appropriate kind of password encryption.
 * Optionally takes a seed and a plaintext password, to extract the seed
 * of an existing password, or for encryption types that use the plaintext
 * in the generation of the salt.
 *
 * @access public
 * @param string $encryption  The kind of pasword encryption to use.
 *                          Defaults to md5-hex.
 * @param string $seed      The seed to get the salt from (probably a
 *                          previously generated password). Defaults to
 *                          generating a new seed.
 * @param string $plaintext The plaintext password that we're generating
 *                          a salt for. Defaults to none.
 *
 * @return string  The generated or extracted salt.
 */
public static function getSalt($encryption = 'md5-hex', $seed = '', $plaintext = '')
{
    // Encrypt the password.
    switch ($encryption)
    {
        case 'crypt' :
        case 'crypt-des' :
            if ($seed) {
                return substr(preg_replace('|^{crypt}|i', '', $seed), 0, 2);
            } else {
                return substr(md5(mt_rand()), 0, 2);
            }
            break;

        case 'crypt-md5' :
            if ($seed) {
                return substr(preg_replace('|^{crypt}|i', '', $seed), 0, 12);
            } else {
                return '$1$'.substr(md5(mt_rand()), 0, 8).'$';
            }
            break;

        case 'crypt-blowfish' :
            if ($seed) {
                return substr(preg_replace('|^{crypt}|i', '', $seed), 0, 16);
            } else {
                return '$2$'.substr(md5(mt_rand()), 0, 12).'$';
            }
            break;

        case 'ssha' :
            if ($seed) {
                return substr(preg_replace('|^{SSHA}|', '', $seed), -20);
            } else {
                return mhash_keygen_s2k(MHASH_SHA1, $plaintext, substr(pack('h*', md5(mt_rand())), 0, 8), 4);
            }
            break;

        case 'smd5' :
            if ($seed) {
                return substr(preg_replace('|^{SMD5}|', '', $seed), -16);
            } else {
                return mhash_keygen_s2k(MHASH_MD5, $plaintext, substr(pack('h*', md5(mt_rand())), 0, 8), 4);
            }
            break;

        case 'aprmd5' :
            /* 64 characters that are valid for APRMD5 passwords. */
            $APRMD5 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';

            if ($seed) {
                return substr(preg_replace('/^\$apr1\$(.{8}).*/', '\\1', $seed), 0, 8);
            } else {
                $salt = '';
                for ($i = 0; $i < 8; $i ++) {
                    $salt .= $APRMD5 {
                        rand(0, 63)
                        };
                }
                return $salt;
            }
            break;

        default :
            $salt = '';
            if ($seed) {
                $salt = $seed;
            }
            return $salt;
            break;
    }
}

Я не эксперт по PHP или Joomla, но я в определенной степени понимаю, что происходит. Я считаю, что алгоритм шифрования использует md5.

Мой вопрос -

Что мне нужно сделать, чтобы аутентифицировать комбо un / pw по паролю, хранящемуся вот так? В настоящее время соль не хранится вместе с PW, так что мне нужно здесь делать? Мне не нужен код или псевдокод, мне просто нужен четкий список шагов, которые необходимо предпринять. Если вам не хватает кода, я пишу свое приложение на Java.

РЕДАКТИРОВАТЬ -

Хорошо, я получил дальнейшую передачу соли / криптографического пароля в библиотеку аутентификации, которую я использую, однако он говорит, что они не совпадают даже после прохождения всего хэширования / расшифровки. Думаю, мне придется немного поиграть с этим.

Используя этот пример PW, который я предоставил в комментарии ниже, вот как выглядит мой java-код:

SimpleAuthenticationInfo info = new SimpleAuthenticationInfo("TestUser",
            "564c6d2c10a7135fe0ddf0b21d1a1226", getName());
    info.setCredentialsSalt(new SimpleByteSource("B9YEkhvnV8pZ8BU7fvVlIVTbEux5N17J"));


    return info;

И это ответ, который я получаю -

Submitted credentials for token [org.apache.shiro.authc.UsernamePasswordToken - TestUser, rememberMe=false] did not match the expected credentials.

Я думаю, что я близко, но я все еще не там. Поскольку мы не передаем имя алгоритма в PHP-функцию getCryptedPassword, я предполагаю, что он использует значение по умолчанию, которое выглядит как MD5. Интересно, почему это не работает?

Спасибо,

-Захари Картер

Ответы [ 2 ]

1 голос
/ 09 февраля 2012

Joomla создавал хеш как простой текст pw + соль, но когда Широ аутентифицируется, он создает хеш как соль + простой текст pw.Решением было создание подклассов SimpleCredentialsMatcher и AbstractHash.Я не мог переопределить методы в существующих подклассах, потому что они все были защищены.

1 голос
/ 09 февраля 2012

Попробуйте это.

Дано имя пользователя $un и простой текстовый пароль $pw:

jimport( 'joomla.user.helper' );
$userId = JUserHelper::getUserId( $un );
$user = JUser::getInstance( $userId );

$existingPasswordParts = explode( ':', $user->password );
$salt = $existingPasswordParts[1];
$crypt = JUserHelper::getCryptedPassword( $pw, $salt );
$password = $crypt . ':' . $salt;

if ( $user->password == $password )
{
  /* match */
}

Пользователь извлечен, а использованная соль используется повторнозашифровать простой текстовый пароль.После этого оба зашифрованных пароля можно сравнить друг с другом.Это должно работать на J1.6, J1.7 и J2.5.

...