【问题标题】:Authenticating plain text password against md5 hash针对 md5 哈希验证纯文本密码
【发布时间】:2012-03-01 08:56:46
【问题描述】:

您好,感谢您的阅读,

我的任务是根据存储在 MySQL 数据库中的密码对 un / pw 对进行身份验证,该数据库将 joomla 作为 CMS / 前端。

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.

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

谢谢,

-扎卡里·卡特

【问题讨论】:

  • 你应该说服决策者你需要用sha1代替md5的使用。如果他们问为什么,请告诉他们索尼因 PlayStation Network 漏洞而损失了多少钱。
  • @Jonathan Plain sha1 在密码散列方面与普通 md5 一样糟糕。他应该用 bcrypt 或 PBKDF2 替换它。
  • 盐的 31 位熵也很小。所有这些代码看起来都是由不知道自己在做什么的人编写的。该代码是 joomla 本身的一部分,还是来自另一个库?
  • @CodeInChaos:大多数 PHP 代码很不安全,而且加密通常很糟糕(看看上面使用的“RNG”,yipes):getCryptedPassword 回答了你的问题,我想。
  • @owlstead 我已经针对他们巧妙的genRandomPassword 函数提出了问题。 joomlacode.org/gf/project/joomla/tracker/… |如果某些自制加密货币不好,我可以理解。但是 AFAIK Joomla 是一个大受欢迎的 php CMS,所以 有人 应该查看他们的加密货币。

标签: authentication encryption joomla md5 salt


【解决方案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。

【讨论】:

  • 你读过我发布的任何内容吗?我说我需要在 Java 而不是 PHP 中执行此操作,并且我已经发布了解决方案,所以这仍然行不通。
  • 好的,我很抱歉。我想当我阅读“Joomla”并看到所有 PHP 代码时,我完全停止了阅读。如果 Java 等效代码不起作用,很可能是由于使用了 MD5 函数,这与 java 和 php 不同 - 不知道如何或为什么。
  • 正如我在回答中解释的那样,问题在于 Apache Shiro 的哈希顺序与 Jommla 哈希(salt+plain-textpw)与哈希(plain-text+salt)的顺序相反。
【解决方案2】:

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

【讨论】:

    猜你喜欢
    • 2015-08-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-12
    • 2022-08-03
    • 1970-01-01
    相关资源
    最近更新 更多