openrat-cms

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

OTP.class.php (3123B)


      1 <?php
      2 namespace security;
      3 
      4 
      5 
      6 use cms\base\Startup;
      7 
      8 /**
      9  * One time passwords (OTP).
     10  * Both TOTP (time based OTP) and HOTP are supported.
     11  * 
     12  * @author Jan Dankert
     13  *
     14  */
     15 class OTP
     16 {
     17 	/**
     18 	 * Supporting only SHA1.
     19 	 * This is the default as specified for the google authenticator.
     20 	 */
     21 	const SUPPORTED_ALGO = 'SHA1';
     22 
     23 	/**
     24 	 * Default is 6 digits for the OTP.
     25 	 */
     26 	const OTP_LENGTH = 6;
     27 
     28 	/**
     29 	 * Duration of a TOTP timeslot in seconds.
     30 	 */
     31 	const TOTP_DURATION = 30;
     32 
     33 	/**
     34 	 * Allow the last n timeslots for TOTP.
     35 	 */
     36 	const TOTP_ALLOW_LAST = 1;
     37 
     38 	/**
     39 	 * Allow the next n counts for HOTP.
     40 	 */
     41 	const HOTP_ALLOW_NEXT = 10;
     42 
     43 
     44 	/**
     45 	 * Calculate valids TOTPs for a secret.
     46 	 *
     47 	 * @param string $secret
     48 	 * @return string valid OTP
     49 	 */
     50 	public static function getTOTPCode($secret)
     51 	{
     52 		// return the OTP for the actual timeslice and for the last one.
     53 		return self::getOTP( $secret, self::getTimeSlice() );
     54 	}
     55 
     56 
     57 	/**
     58 	 * Calculate valids TOTPs for a secret.
     59 	 *
     60 	 * @param string $secret
     61 	 * @return array valid OTPs
     62 	 */
     63 	public static function getValidTOTPCodes($secret)
     64 	{
     65 		$actualTimeSlice = self::getTimeSlice();
     66 
     67 		// return the OTP for the actual timeslice and for the last one.
     68 		return self::getOTPs( $secret, range(
     69 			$actualTimeSlice - self::TOTP_ALLOW_LAST,
     70 			$actualTimeSlice
     71 		) );
     72 	}
     73 
     74 
     75 	/**
     76 	 * Calculate the HOTP code, with given secret and counter.
     77 	 *
     78 	 * @param string $secret
     79 	 * @param int $count
     80 	 * @return array
     81 	 */
     82 	public static function getValidHOTPCodes($secret, $count)
     83 	{
     84 		return self::getOTPs($secret, range($count,$count+ self::HOTP_ALLOW_NEXT));
     85 	}
     86 
     87 
     88 	/**
     89 	 * Calculate OTP, with given secret and slot range.
     90 	 * This calculates HOTP and TOTP values.
     91 	 *
     92 	 * @param string $secret
     93 	 * @param array $slots
     94 	 *
     95 	 * @return array
     96 	 */
     97 	protected static function getOTPs($secret, $slotRange )
     98 	{
     99 		// return the OTPs for the given range
    100 		return array_map(function ($slot) use ($secret) {
    101 			return OTP::getOTP($secret, $slot);
    102 		}, array_combine( $slotRange,$slotRange ) );
    103 	}
    104 
    105 	/**
    106 	 * Calculate the code, with given secret and slot.
    107 	 * This is valid for HOTP and TOTP.
    108 	 *
    109 	 * @param string   $secret
    110 	 * @param int|null $slot
    111 	 *
    112 	 * @return string
    113 	 */
    114 	protected static function getOTP( $secret, $slot )
    115 	{
    116 		$secretKey = @hex2bin($secret);
    117 		// Pack time into binary string
    118 		$time = chr(0).chr(0).chr(0).chr(0).pack('N*', $slot);
    119 		// Hash it with users secret key
    120 		$hm = hash_hmac(self::SUPPORTED_ALGO, $time, $secretKey, true);
    121 		// Use last nipple of result as index/offset
    122 		$offset = ord(substr($hm, -1)) & 0x0F;
    123 		// grab 4 bytes of the result
    124 		$hashPart = substr($hm, $offset, 4);
    125 		// Unpak binary value
    126 		$value = unpack('N', $hashPart);
    127 		$value = $value[1];
    128 		// Only 32 bits
    129 		$value = $value & 0x7FFFFFFF;
    130 		$modulo = pow(10, self::OTP_LENGTH);
    131 		return str_pad($value % $modulo, self::OTP_LENGTH, '0', STR_PAD_LEFT);
    132 	}
    133 
    134 
    135 	/**
    136 	 * Actual timeslot.
    137 	 * The number of timeslots since the beginning of unixtime.
    138 	 *
    139 	 * @return int timeslot
    140 	 */
    141 	public static function getTimeSlice()
    142 	{
    143 		return intval(Startup::getStartTime() / self::TOTP_DURATION );
    144 	}
    145 
    146 
    147 }