You are here

ga_login_test.module in Google Authenticator login 7

ga_login_test module.

File

tests/ga_login_test/ga_login_test.module
View source
<?php

/**
 * @file
 * ga_login_test module.
 */

/**
 * Implements hook_boot().
 */
function ga_login_test_boot() {
  global $drupal_hash_salt, $databases;
  if (empty($drupal_hash_salt)) {
    $clone = $databases;
    $clone['default']['default']['prefix'] = "";
    $drupal_hash_salt = hash('sha256', serialize($clone));
  }
}

/**
 * Generate the code for a given secret.
 *
 * @param string $secret
 *   Secret as generated by the ga4php class.
 * @param int $timestamp_offset
 *   Timestamp offset.
 *
 * @returns int
 *   Returns 6 digit code.
 */
function ga_login_test_generate_code($secret, $timestamp_offset = 0) {
  $timestamp = Google2FA::getTimestamp();
  $timestamp += $timestamp_offset;
  $secretkey = Google2FA::base32Decode($secret);
  return Google2FA::oathHotp($secretkey, $timestamp);
}

/**
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * PHP Google two-factor authentication module.
 *
 * @see http://www.idontplaydarts.com/2011/07/google-totp-two-factor-authentication-for-php/
 *
 * @author Phil
 **/
class Google2FA {

  // Interval between key regeneration.
  const KEYREGENERATION = 30;

  // Length of the Token generated.
  const OTPLENGTH = 6;

  // Lookup needed for Base32 encoding.
  private static $lut = array(
    "A" => 0,
    "B" => 1,
    "C" => 2,
    "D" => 3,
    "E" => 4,
    "F" => 5,
    "G" => 6,
    "H" => 7,
    "I" => 8,
    "J" => 9,
    "K" => 10,
    "L" => 11,
    "M" => 12,
    "N" => 13,
    "O" => 14,
    "P" => 15,
    "Q" => 16,
    "R" => 17,
    "S" => 18,
    "T" => 19,
    "U" => 20,
    "V" => 21,
    "W" => 22,
    "X" => 23,
    "Y" => 24,
    "Z" => 25,
    "2" => 26,
    "3" => 27,
    "4" => 28,
    "5" => 29,
    "6" => 30,
    "7" => 31,
  );

  /**
   * Generates a 16 digit secret key in base32 format.
   *
   * @param int $length
   *   Length of the key
   *
   * @return string
   *   Secret key
   */
  public static function generateSecretKey($length = 16) {
    $b32 = "234567QWERTYUIOPASDFGHJKLZXCVBNM";
    $s = "";
    for ($i = 0; $i < $length; $i++) {
      $s .= $b32[rand(0, 31)];
    }
    return $s;
  }

  /**
   * Returns the current ts devided by the KEYREGENERATION period.
   *
   * @return int
   *   Timestamp
   */
  public static function getTimestamp() {
    return floor(microtime(TRUE) / self::KEYREGENERATION);
  }

  /**
   * Decodes a base32 string into a binary string.
   */
  public static function base32Decode($b32) {
    $b32 = strtoupper($b32);
    if (!preg_match('/^[ABCDEFGHIJKLMNOPQRSTUVWXYZ234567]+$/', $b32, $match)) {
      throw new Exception('Invalid characters in the base32 string.');
    }
    $l = strlen($b32);
    $n = 0;
    $j = 0;
    $binary = "";
    for ($i = 0; $i < $l; $i++) {

      // Move buffer left by 5 to make room.
      $n = $n << 5;

      // Add value into buffer.
      $n = $n + self::$lut[$b32[$i]];

      // Keep track of number of bits in buffer.
      $j = $j + 5;
      if ($j >= 8) {
        $j = $j - 8;
        $binary .= chr(($n & 0xff << $j) >> $j);
      }
    }
    return $binary;
  }

  /**
   * Takes the secret key and the timestamp and returns the OTP.
   *
   * @param binary $key
   *   Secret key in binary form.
   * @param int $counter
   *   Timestamp as returned by getTimestamp.
   *
   * @return string
   *   The OTP
   */
  public static function oathHotp($key, $counter) {
    if (strlen($key) < 8) {
      throw new Exception('Secret key is too short. Must be at least 16 base 32 characters');
    }

    // Counter must be 64-bit int.
    $bin_counter = pack('N*', 0) . pack('N*', $counter);
    $hash = hash_hmac('sha1', $bin_counter, $key, TRUE);
    return str_pad(self::oathTruncate($hash), self::OTPLENGTH, '0', STR_PAD_LEFT);
  }

  /**
   * Verifys a key against the current timestamp.
   *
   * @param string $b32seed
   *   Seed to use
   * @param string $key
   *   User specified key
   * @param int $window
   *   How many windows to use.
   * @param bool $use_timestamp
   *   Use the timestamp.
   *
   * @return bool
   *   TRUE is key is valid.
   */
  public static function verifyKey($b32seed, $key, $window = 4, $use_timestamp = TRUE) {
    $timestamp = self::getTimestamp();
    if ($use_timestamp !== TRUE) {
      $timestamp = (int) $use_timestamp;
    }
    $binary_seed = self::base32Decode($b32seed);
    for ($ts = $timestamp - $window; $ts <= $timestamp + $window; $ts++) {
      if (self::oathHotp($binary_seed, $ts) == $key) {
        return TRUE;
      }
    }
    return FALSE;
  }

  /**
   * Extracts the OTP from the SHA1 hash.
   *
   * @param binary $hash
   *   Hash to extract OTP from.
   *
   * @return int
   *   OTP.
   */
  public static function oathTruncate($hash) {
    $offset = ord($hash[19]) & 0xf;
    return ((ord($hash[$offset + 0]) & 0x7f) << 24 | (ord($hash[$offset + 1]) & 0xff) << 16 | (ord($hash[$offset + 2]) & 0xff) << 8 | ord($hash[$offset + 3]) & 0xff) % pow(10, self::OTPLENGTH);
  }

}

Functions

Namesort descending Description
ga_login_test_boot Implements hook_boot().
ga_login_test_generate_code Generate the code for a given secret.

Classes

Namesort descending Description
Google2FA This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.