198 lines
		
	
	
		
			4.8 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			198 lines
		
	
	
		
			4.8 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
| <?php
 | |
| /**
 | |
|  * @package   FOF
 | |
|  * @copyright Copyright (c)2010-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
 | |
|  * @license   GNU General Public License version 2, or later
 | |
|  */
 | |
| 
 | |
| namespace FOF30\Encrypt;
 | |
| 
 | |
| defined('_JEXEC') || die;
 | |
| 
 | |
| class Totp
 | |
| {
 | |
| 	/**
 | |
| 	 * @var int The length of the resulting passcode (default: 6 digits)
 | |
| 	 */
 | |
| 	private $passCodeLength = 6;
 | |
| 
 | |
| 	/**
 | |
| 	 * @var number The PIN modulo. It is set automatically to log10(passCodeLength)
 | |
| 	 */
 | |
| 	private $pinModulo;
 | |
| 
 | |
| 	/**
 | |
| 	 * The length of the secret key, in characters (default: 10)
 | |
| 	 *
 | |
| 	 * @var int
 | |
| 	 */
 | |
| 	private $secretLength = 10;
 | |
| 
 | |
| 	/**
 | |
| 	 * The time step between successive TOTPs in seconds (default: 30 seconds)
 | |
| 	 *
 | |
| 	 * @var int
 | |
| 	 */
 | |
| 	private $timeStep = 30;
 | |
| 
 | |
| 	/**
 | |
| 	 * The Base32 encoder class
 | |
| 	 *
 | |
| 	 * @var Base32|null
 | |
| 	 */
 | |
| 	private $base32 = null;
 | |
| 
 | |
| 	/**
 | |
| 	 * Initialises an RFC6238-compatible TOTP generator. Please note that this
 | |
| 	 * class does not implement the constraint in the last paragraph of §5.2
 | |
| 	 * of RFC6238. It's up to you to ensure that the same user/device does not
 | |
| 	 * retry validation within the same Time Step.
 | |
| 	 *
 | |
| 	 * @param   int     $timeStep        The Time Step (in seconds). Use 30 to be compatible with Google Authenticator.
 | |
| 	 * @param   int     $passCodeLength  The generated passcode length. Default: 6 digits.
 | |
| 	 * @param   int     $secretLength    The length of the secret key. Default: 10 bytes (80 bits).
 | |
| 	 * @param   Base32  $base32          The base32 en/decrypter
 | |
| 	 */
 | |
| 	public function __construct($timeStep = 30, $passCodeLength = 6, $secretLength = 10, Base32 $base32 = null)
 | |
| 	{
 | |
| 		$this->timeStep       = $timeStep;
 | |
| 		$this->passCodeLength = $passCodeLength;
 | |
| 		$this->secretLength   = $secretLength;
 | |
| 		$this->pinModulo      = 10 ** $this->passCodeLength;
 | |
| 
 | |
| 		if (is_null($base32))
 | |
| 		{
 | |
| 			$this->base32 = new Base32();
 | |
| 		}
 | |
| 		else
 | |
| 		{
 | |
| 			$this->base32 = $base32;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Get the time period based on the $time timestamp and the Time Step
 | |
| 	 * defined. If $time is skipped or set to null the current timestamp will
 | |
| 	 * be used.
 | |
| 	 *
 | |
| 	 * @param   int|null  $time  Timestamp
 | |
| 	 *
 | |
| 	 * @return  int  The time period since the UNIX Epoch
 | |
| 	 */
 | |
| 	public function getPeriod($time = null)
 | |
| 	{
 | |
| 		if (is_null($time))
 | |
| 		{
 | |
| 			$time = time();
 | |
| 		}
 | |
| 
 | |
| 		$period = floor($time / $this->timeStep);
 | |
| 
 | |
| 		return $period;
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Check is the given passcode $code is a valid TOTP generated using secret
 | |
| 	 * key $secret
 | |
| 	 *
 | |
| 	 * @param   string  $secret  The Base32-encoded secret key
 | |
| 	 * @param   string  $code    The passcode to check
 | |
| 	 * @param   int     $time    The time to check it against. Leave null to check for the current server time.
 | |
| 	 *
 | |
| 	 * @return boolean True if the code is valid
 | |
| 	 */
 | |
| 	public function checkCode($secret, $code, $time = null)
 | |
| 	{
 | |
| 		$time = $this->getPeriod($time);
 | |
| 
 | |
| 		for ($i = -1; $i <= 1; $i++)
 | |
| 		{
 | |
| 			if ($this->getCode($secret, ($time + $i) * $this->timeStep) == $code)
 | |
| 			{
 | |
| 				return true;
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		return false;
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Gets the TOTP passcode for a given secret key $secret and a given UNIX
 | |
| 	 * timestamp $time
 | |
| 	 *
 | |
| 	 * @param   string  $secret  The Base32-encoded secret key
 | |
| 	 * @param   int     $time    UNIX timestamp
 | |
| 	 *
 | |
| 	 * @return string
 | |
| 	 */
 | |
| 	public function getCode($secret, $time = null)
 | |
| 	{
 | |
| 		$period = $this->getPeriod($time);
 | |
| 		$secret = $this->base32->decode($secret);
 | |
| 
 | |
| 		$time = pack("N", $period);
 | |
| 		$time = str_pad($time, 8, chr(0), STR_PAD_LEFT);
 | |
| 
 | |
| 		$hash   = hash_hmac('sha1', $time, $secret, true);
 | |
| 		$offset = ord(substr($hash, -1));
 | |
| 		$offset = $offset & 0xF;
 | |
| 
 | |
| 		$truncatedHash = $this->hashToInt($hash, $offset) & 0x7FFFFFFF;
 | |
| 		$pinValue      = str_pad($truncatedHash % $this->pinModulo, $this->passCodeLength, "0", STR_PAD_LEFT);
 | |
| 
 | |
| 		return $pinValue;
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Returns a QR code URL for easy setup of TOTP apps like Google Authenticator
 | |
| 	 *
 | |
| 	 * @param   string  $user      User
 | |
| 	 * @param   string  $hostname  Hostname
 | |
| 	 * @param   string  $secret    Secret string
 | |
| 	 *
 | |
| 	 * @return  string
 | |
| 	 */
 | |
| 	public function getUrl($user, $hostname, $secret)
 | |
| 	{
 | |
| 		$url        = sprintf("otpauth://totp/%s@%s?secret=%s", $user, $hostname, $secret);
 | |
| 		$encoder    = "https://chart.googleapis.com/chart?chs=200x200&chld=Q|2&cht=qr&chl=";
 | |
| 		$encoderURL = $encoder . urlencode($url);
 | |
| 
 | |
| 		return $encoderURL;
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Generates a (semi-)random Secret Key for TOTP generation
 | |
| 	 *
 | |
| 	 * @return  string
 | |
| 	 */
 | |
| 	public function generateSecret()
 | |
| 	{
 | |
| 		$secret = "";
 | |
| 
 | |
| 		for ($i = 1; $i <= $this->secretLength; $i++)
 | |
| 		{
 | |
| 			$c      = random_int(0, 255);
 | |
| 			$secret .= pack("c", $c);
 | |
| 		}
 | |
| 
 | |
| 		return $this->base32->encode($secret);
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * Extracts a part of a hash as an integer
 | |
| 	 *
 | |
| 	 * @param   string  $bytes  The hash
 | |
| 	 * @param   string  $start  The char to start from (0 = first char)
 | |
| 	 *
 | |
| 	 * @return  string
 | |
| 	 */
 | |
| 	protected function hashToInt($bytes, $start)
 | |
| 	{
 | |
| 		$input = substr($bytes, $start, strlen($bytes) - $start);
 | |
| 		$val2  = unpack("N", substr($input, 0, 4));
 | |
| 
 | |
| 		return $val2[1];
 | |
| 	}
 | |
| }
 |