File modules/security/OTP.class.php

Last commit: Wed Feb 23 00:38:24 2022 +0100	dankert	New: Enable HOTP with counter synchronization; New: TOTP of the last period are valid too.
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 }
Download modules/security/OTP.class.php
History Wed, 23 Feb 2022 00:38:24 +0100 dankert New: Enable HOTP with counter synchronization; New: TOTP of the last period are valid too.