根据md5哈希验证纯文本密码

时间:2012-02-08 21:15:21

标签: authentication encryption joomla md5 salt

您好,感谢您阅读,

我有一项任务是根据存储在MySQL数据库中的密码验证un / pw对,该数据库有joomla作为CMS /前端。

joomla Web应用程序支持在所述数据库中存储用户名和密码,并且在存储新用户时它似乎会经历以下步骤 -

$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编写我的应用程序。

编辑 -

好的,我已经进一步向我正在使用的身份验证库提供salt / crypto密码,但它表示即使经过所有的哈希/解密后它们也不匹配。我想我将不得不更多地玩这个。

使用下面评论中提供的示例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.

我想我很接近,但我仍然不在那里。由于我们没有将算法名称传递给getCryptedPassword PHP函数,我猜它是使用默认值,看起来是MD5。我想知道为什么这不起作用。

谢谢,

-Zachary Carter

2 个答案:

答案 0 :(得分:1)

试试这个。

指定用户名$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。

答案 1 :(得分:1)

Joomla正在将哈希构建为纯文本pw + salt,但是当Shiro进行身份验证时,它将哈希构建为salt + plain-text pw。解决方案是继承SimpleCredentialsMatcher和AbstractHash。我无法覆盖现有子类中的方法,因为它们都受到保护。