Authenticate user against PhPbb database
Asked Answered
I

1

8

Recently I have started implementing a solution which will use a PhPbb database for forms authorization, I have used the class from this below thread:

PhPbb C# Authentication Port

So i wrote a membership provider using this class in the 'ValidateUser' function:

public override bool ValidateUser(string username, string password)
    {
        ForumsDataContext db = Root.ForumsDataContext;
        PhPbbCryptoServiceProvider phpbbCrypt = new PhPbbCryptoServiceProvider();
        string remoteHash = db.Users.Where(u => u.UserName == username).FirstOrDefault().UserPassword;
        if (String.IsNullOrEmpty(remoteHash))
            return false;
        return phpbbCrypt.phpbbCheckHash(password, remoteHash);
    }

However this always returns false as the 'phpbbCrypt.phpbbCheckHash' returns false and I do not know enough about PhPbb to determine why the hashes are not matching up.

Any sugestions?

Invaginate answered 22/2, 2011 at 7:25 Comment(2)
Can you make it work with the demo hash and password on that page, i.e. hash = "$H$9uAiKWrdcDomn7FEqujoPLYuBXvkzV0", password = "q1w2e3"? Are your hashes 34 characters long? It says the code only supports new hashes, not the old ones - how recent is your phpbb?Schoenberg
Yeah it works with the demo hash, but I can't get it to work with the users entered password and and their hashed password from the database... They are 34 characters long, but the generated hash stills does not match the database value.... I downloaded the latest PhPbb not more then a month ago.Duwe
M
3

If you upgraded your phpbb install from 2.0 the password hashing function is different. I took this snippet from functions.php in phpbb (See: GitHub) this is the entire password checking and hashing functions with a little bit at the end to output a phpbb hashed password.

<?php
  function _hash_encode64($input, $count, &$itoa64)
  {
      $output = '';
      $i = 0;

      do {
          $value = ord($input[$i++]);
          $output .= $itoa64[$value & 0x3f];

          if ($i < $count) {
              $value |= ord($input[$i]) << 8;
          }

          $output .= $itoa64[($value >> 6) & 0x3f];

          if ($i++ >= $count) {
              break;
          }

          if ($i < $count) {
              $value |= ord($input[$i]) << 16;
          }

          $output .= $itoa64[($value >> 12) & 0x3f];

          if ($i++ >= $count) {
              break;
          }

          $output .= $itoa64[($value >> 18) & 0x3f];
      } while ($i < $count);

      return $output;
  }
  function _hash_crypt_private($password, $setting, &$itoa64)
  {
      $output = '*';

      // Check for correct hash
      if (substr($setting, 0, 3) != '$H$' && substr($setting, 0, 3) != '$P$') {
          return $output;
      }

      $count_log2 = strpos($itoa64, $setting[3]);

      if ($count_log2 < 7 || $count_log2 > 30) {
          return $output;
      }

      $count = 1 << $count_log2;
      $salt = substr($setting, 4, 8);

      if (strlen($salt) != 8) {
          return $output;
      }

      /**
       * We're kind of forced to use MD5 here since it's the only
       * cryptographic primitive available in all versions of PHP
       * currently in use.  To implement our own low-level crypto
       * in PHP would result in much worse performance and
       * consequently in lower iteration counts and hashes that are
       * quicker to crack (by non-PHP code).
       */
      $hash = md5($salt . $password, true);
      do {
          $hash = md5($hash . $password, true);
      } while (--$count);

      $output = substr($setting, 0, 12);
      $output .= _hash_encode64($hash, 16, $itoa64);

      return $output;
  }

  function phpbb_hash($password)
  {
      $itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';

      $random_state = unique_id();
      $random = '';
      $count = 6;

      if (($fh = @fopen('/dev/urandom', 'rb'))) {
          $random = fread($fh, $count);
          fclose($fh);
      }

      if (strlen($random) < $count) {
          $random = '';

          for ($i = 0; $i < $count; $i += 16) {
              $random_state = md5(unique_id() . $random_state);
              $random .= pack('H*', md5($random_state));
          }
          $random = substr($random, 0, $count);
      }

      $hash = _hash_crypt_private($password, _hash_gensalt_private($random, $itoa64), $itoa64);

      if (strlen($hash) == 34) {
          return $hash;
      }

      return md5($password);
  }
  /**
   * * Generate salt for hash generation
   * */
  function _hash_gensalt_private($input, &$itoa64, $iteration_count_log2 = 6)
  {
      if ($iteration_count_log2 < 4 || $iteration_count_log2 > 31) {
          $iteration_count_log2 = 8;
      }

      $output = '$H$';
      $output .= $itoa64[min($iteration_count_log2 + 5, 30)];
      $output .= _hash_encode64($input, 6, $itoa64);

      return $output;
  }
  /**
   * * Return unique id
   * * @param string $extra additional entropy
   * */
  function unique_id($extra = 'c')
  {
      static $dss_seeded = false;
      global $config;

      $val = $config['rand_seed'] . microtime();
      $val = md5($val);
      $config['rand_seed'] = md5($config['rand_seed'] . $val . $extra);
      return substr($val, 4, 16);
  }
  function phpbb_check_hash($password, $hash)
  {
      $itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
      if (strlen($hash) == 34) {
          return(_hash_crypt_private($password, $hash, $itoa64) === $hash) ? true : false;
      }

      return(md5($password) === $hash) ? true : false;
  }
  $toHash = "";
  if (!empty($argc) && $argc >= 2) {
      $toHash = $argv[1];
  }
  $hashed = phpbb_hash("a");
  //To Check: $checked2 = phpbb_check_hash("q1w2e3", '$H$9uAiKWrdcDomn7FEqujoPLYuBXvkzV0');
  echo "Hashing password '" . $toHash . "'\n";
  echo "Hash: " . $hashed . "\n";
  echo "Hash (MD5): " . md5($toHash) . "\n";
?>

Important part here is that it isn't a straight MD5. I took the C# class from the link the OP provided and made this test class.

[TestFixture]
public class phpBBHashTestFixture
{
    [Test]
    public void TestCanVerifyPhpBBPassword()
    {
        var cryptoService = new phpBBCryptoServiceProvider();
        Assert.That(cryptoService.phpbbCheckHash("a", "$H$9AE1X.4z5hqGj/RVdvzuYjxsJdMeFs."), Is.True);
        Assert.That(cryptoService.phpbbCheckHash("q1w2e3", "$H$9uAiKWrdcDomn7FEqujoPLYuBXvkzV0"), Is.True);
    }
}

This is a modified copy of the class in the OP question. This will check older passwords which were just an MD5 hash of the plaintext password without a salt and i also added in the prefix "$P$" to be allowed.

/// <summary>
///   Computes the phpBB/SubMD5 hash value for the input data using the implementation provided by http://openwall.com/phpass/ modified by http://www.phpbb.com/.
/// </summary>
/// <remarks>
///   Ported by Ryan Irecki
///   Website: http://www.digilitepc.net/
///   E-mail: [email protected]
/// </remarks>
public class phpBBCryptoServiceProvider
{
    /// <summary>
    ///   The encryption string base.
    /// </summary>
    private string itoa64 = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";

    /// <summary>
    ///   Compares the password string given with the hash retrieved from your database.
    /// </summary>
    /// <param name = "password">Plaintext password.</param>
    /// <param name = "hash">Hash from a SQL database</param>
    /// <returns>True if the password is correct, False otherwise.</returns>
    public bool phpbbCheckHash(string password, string hash)
    {
        if (hash.Length == 34)
        {
            return (this.hashCryptPrivate(Encoding.ASCII.GetBytes(password), hash, this.itoa64) == hash);
        }

        return this.sMD5(password, false) == hash.ToUpper();
    }

    /// <summary>
    ///   This function will return the resulting hash from the password string you specify.
    /// </summary>
    /// <param name = "password">String to hash.</param>
    /// <returns>Encrypted hash.</returns>
    /// <remarks>
    ///   Although this will return the md5 for an older password, I have not added
    ///   support for older passwords, so they will not work with this class unless
    ///   I or someone else updates it.
    /// </remarks>
    public string phpbb_hash(string password)
    {
        // Generate a random string from a random number with the length of 6.
        // You could use a static string instead, doesn't matter. E.g.
        // byte[] random = ASCIIEncoding.ASCII.GetBytes("abc123");
        var random = Encoding.ASCII.GetBytes(new Random().Next(100000, 999999).ToString());

        var hash = this.hashCryptPrivate(Encoding.ASCII.GetBytes(password), this.hashGensaltPrivate(random, this.itoa64), this.itoa64);

        if (hash.Length == 34)
        {
            return hash;
        }

        return this.sMD5(password);
    }

    /// <summary>
    ///   The workhorse that encrypts your hash.
    /// </summary>
    /// <param name = "password">String to be encrypted. Use: ASCIIEncoding.ASCII.GetBytes();</param>
    /// <param name = "genSalt">Generated salt.</param>
    /// <param name = "itoa64">The itoa64 string.</param>
    /// <returns>The encrypted hash ready to be compared.</returns>
    /// <remarks>
    ///   password:  Saves conversion inside the function, lazy coding really.
    ///   genSalt:   Returns from hashGensaltPrivate(random, itoa64);
    ///   return:    Compare with phpbbCheckHash(password, hash)
    /// </remarks>
    private string hashCryptPrivate(byte[] password, string genSalt, string itoa64)
    {
        var output = "*";
        var md5 = new MD5CryptoServiceProvider();
        if (!(genSalt.StartsWith("$H$") || genSalt.StartsWith("$P$")))
        {
            return output;
        }
        //   $count_log2 = strpos($itoa64, $setting[3]);
        var count_log2 = itoa64.IndexOf(genSalt[3]);
        if (count_log2 < 7 || count_log2 > 30)
        {
            return output;
        }

        var count = 1 << count_log2;
        var salt = Encoding.ASCII.GetBytes(genSalt.Substring(4, 8));

        if (salt.Length != 8)
        {
            return output;
        }

        var hash = md5.ComputeHash(this.Combine(salt, password));

        do
        {
            hash = md5.ComputeHash(this.Combine(hash, password));
        } while (count-- > 1);

        output = genSalt.Substring(0, 12);
        output += this.hashEncode64(hash, 16, itoa64);

        return output;
    }

    /// <summary>
    ///   Private function to concat byte arrays.
    /// </summary>
    /// <param name = "b1">Source array.</param>
    /// <param name = "b2">Array to add to the source array.</param>
    /// <returns>Combined byte array.</returns>
    private byte[] Combine(byte[] b1, byte[] b2)
    {
        var retVal = new byte[b1.Length + b2.Length];
        Array.Copy(b1, 0, retVal, 0, b1.Length);
        Array.Copy(b2, 0, retVal, b1.Length, b2.Length);
        return retVal;
    }

    /// <summary>
    ///   Encode the hash.
    /// </summary>
    /// <param name = "input">The hash to encode.</param>
    /// <param name = "count">[This parameter needs documentation].</param>
    /// <param name = "itoa64">The itoa64 string.</param>
    /// <returns>Encoded hash.</returns>
    private string hashEncode64(byte[] input, int count, string itoa64)
    {
        var output = "";
        var i = 0;
        var value = 0;

        do
        {
            value = input[i++];
            output += itoa64[value & 0x3f];

            if (i < count)
            {
                value |= input[i] << 8;
            }
            output += itoa64[(value >> 6) & 0x3f];
            if (i++ >= count)
            {
                break;
            }

            if (i < count)
            {
                value |= input[i] << 16;
            }
            output += itoa64[(value >> 12) & 0x3f];
            if (i++ >= count)
            {
                break;
            }

            output += itoa64[(value >> 18) & 0x3f];
        } while (i < count);

        return output;
    }

    /// <summary>
    ///   Generate salt for hash generation.
    /// </summary>
    /// <param name = "input">Any random information.</param>
    /// <param name = "itoa64">The itoa64 string.</param>
    /// <returns>Generated salt string</returns>
    private string hashGensaltPrivate(byte[] input, string itoa64)
    {
        var iteration_count_log2 = 6;

        var output = "$H$";
        output += itoa64[Math.Min(iteration_count_log2 + 5, 30)];
        output += this.hashEncode64(input, 6, itoa64);

        return output;
    }

    /// <summary>
    ///   Returns a hexadecimal string representation for the encrypted MD5 parameter.
    /// </summary>
    /// <param name = "password">String to be encrypted.</param>
    /// <returns>String</returns>
    private string sMD5(string password)
    {
        return this.sMD5(password, false);
    }

    /// <summary>
    ///   Returns a hexadecimal string representation for the encrypted MD5 parameter.
    /// </summary>
    /// <param name = "password">String to be encrypted.</param>
    /// <param name = "raw">Whether or not to produce a raw string.</param>
    /// <returns>String</returns>
    private string sMD5(string password, bool raw)
    {
        var md5 = new MD5CryptoServiceProvider();
        if (raw)
        {
            return Encoding.ASCII.GetString(md5.ComputeHash(Encoding.ASCII.GetBytes(password)));
        }
        else
        {
            return BitConverter.ToString(md5.ComputeHash(Encoding.ASCII.GetBytes(password))).Replace("-", "");
        }
    }
}
Mercuric answered 8/7, 2011 at 2:7 Comment(1)
This should work. Please try this code and let me know if it works for you, or if it doesn't an example password and hash that works with the phpbb check hash function and not with the above C# code.Mercuric

© 2022 - 2024 — McMap. All rights reserved.