public function generateHash($iGroupId, $sPassword)
    {
        if (empty($sPassword)) {
            $this->setError('No password to hash');
            return false;
        }

        // --------------------------------------------------------------------------

        //  Check password satisfies password rules
        $aPwRules = $this->getRules($iGroupId);

        //  Long enough?
        if (!empty($aPwRules['min']) && strlen($sPassword) < $aPwRules['min']) {
            $this->setError('Password is too short.');
            return false;
        }

        //  Too long?
        if (!empty($aPwRules['max']) && strlen($sPassword) > $aPwRules['max']) {
            $this->setError('Password is too long.');
            return false;
        }

        //  Satisfies all the requirements
        $aFailedRequirements = [];
        if (!empty($aPwRules['requirements'])) {
            foreach ($aPwRules['requirements'] as $sRequirement => $bValue) {
                switch ($sRequirement) {
                    case 'symbol':
                        if (!$this->strContainsFromCharset($sPassword, 'symbol')) {
                            $aFailedRequirements[] = 'a symbol';
                        }
                        break;

                    case 'number':
                        if (!$this->strContainsFromCharset($sPassword, 'number')) {
                            $aFailedRequirements[] = 'a number';
                        }
                        break;

                    case 'lower_alpha':
                        if (!$this->strContainsFromCharset($sPassword, 'lower_alpha')) {
                            $aFailedRequirements[] = 'a lowercase letter';
                        }
                        break;

                    case 'upper_alpha':
                        if (!$this->strContainsFromCharset($sPassword, 'upper_alpha')) {
                            $aFailedRequirements[] = 'an uppercase letter';
                        }
                        break;
                }
            }
        }

        if (!empty($aFailedRequirements)) {
            $sError = 'Password must contain ' . implode(', ', $aFailedRequirements) . '.';
            $sError = str_lreplace(', ', ' and ', $sError);
            $this->setError($sError);
            return false;
        }

        //  Not be a banned password?
        if (!empty($aPwRules['banned'])) {
            foreach ($aPwRules['banned'] as $sStr) {
                if (trim(strtolower($sPassword)) == strtolower($sStr)) {
                    $this->setError('Password cannot be "' . $sStr . '"');
                    return false;
                }
            }
        }

        // --------------------------------------------------------------------------

        //  Password is valid, generate hash object
        return $this->generateHashObject($sPassword);
    }