PHP Classes

File: src/OTP/TOTP.php

Recommend this page to a friend!
  Classes of Scott Arciszewski   PHP Multi Factor Authentication   src/OTP/TOTP.php   Download  
File: src/OTP/TOTP.php
Role: Class source
Content type: text/plain
Description: Class source
Class: PHP Multi Factor Authentication
2 factor authentication independent of the vendor
Author: By
Last change:
Date: 5 years ago
Size: 3,520 bytes
 

Contents

Class file image Download
<?php
declare(strict_types=1);
namespace
ParagonIE\MultiFactor\OTP;

use
ParagonIE\ConstantTime\{
   
Binary,
   
Hex
};
use
ParagonIE\HiddenString\HiddenString;

/**
 * Class TOTP
 * @package ParagonIE\MultiFactor\OTP
 */
class TOTP implements OTPInterface
{
   
/**
     * @var string
     */
   
protected $algo;

   
/**
     * @var int
     */
   
protected $length;

   
/**
     * @var int
     */
   
protected $timeStep;

   
/**
     * @var int
     */
   
protected $timeZero;

   
/**
     * TOTP constructor.
     *
     * @param int $timeZero The start time for calculating the TOTP
     * @param int $timeStep How many seconds should each TOTP live?
     * @param int $length How many digits should each TOTP be?
     * @param string $algo Hash function to use
     */
   
public function __construct(
       
int $timeZero = 0,
       
int $timeStep = 30,
       
int $length = 6,
       
string $algo = 'sha1'
   
) {
       
$this->timeZero = $timeZero;
       
$this->timeStep = $timeStep;
       
$this->length = $length;
       
$this->algo = $algo;
    }

   
/**
     * Generate a TOTP secret in accordance with RFC 6238
     *
     * @ref https://tools.ietf.org/html/rfc6238
     * @param string|HiddenString $sharedSecret The key to use for determining the TOTP
     * @param int $counterValue Current time or HOTP counter
     * @return string
     * @throws \OutOfRangeException
     */
   
public function getCode(
       
$sharedSecret,
       
int $counterValue
   
): string {
        if (
$this->length < 1 || $this->length > 10) {
            throw new \
OutOfRangeException(
               
'Length must be between 1 and 10, as a consequence of RFC 6238.'
           
);
        }
       
$counterValue > 0 ? $counterValue: time();
       
$msg = $this->getTValue($counterValue, true);
       
$bytes = \hash_hmac($this->algo, $msg, is_string($sharedSecret) ? $sharedSecret : $sharedSecret->getString(), true);

       
$byteLen = Binary::safeStrlen($bytes);

       
// Per the RFC
       
$offset = \unpack('C', $bytes[$byteLen - 1])[1];
       
$offset &= 0x0f;

       
$unpacked = \array_values(
            \
unpack('C*', Binary::safeSubstr($bytes, $offset, 4))
        );

       
$intValue = (
            ((
$unpacked[0] & 0x7f) << 24)
            | ((
$unpacked[1] & 0xff) << 16)
            | ((
$unpacked[2] & 0xff) << 8)
            | ((
$unpacked[3] & 0xff) )
        );

       
$intValue %= 10 ** $this->length;

        return \
str_pad(
            (string)
$intValue,
           
$this->length,
           
'0',
            \
STR_PAD_LEFT
       
);
    }

   
/**
     * @return int
     */
   
public function getLength(): int
   
{
        return
$this->length;
    }

   
/**
     * @return int
     */
   
public function getTimeStep(): int
   
{
        return
$this->timeStep;
    }

   
/**
     * Get the binary T value
     *
     * @param int $unixTimestamp
     * @param bool $rawOutput
     * @return string
     */
   
protected function getTValue(
       
int $unixTimestamp,
       
bool $rawOutput = false
   
): string {
       
$value = \intdiv(
           
$unixTimestamp - $this->timeZero,
           
$this->timeStep !== 0
               
? $this->timeStep
               
: 1
       
);
       
$hex = \str_pad(
            \
dechex($value),
           
16,
           
'0',
           
STR_PAD_LEFT
       
);
        if (
$rawOutput) {
            return
Hex::decode($hex);
        }
        return
$hex;
    }
}