· 8 years ago · Dec 20, 2017, 12:34 AM
1<?php
2 class Google2FA {
3 const keyRegeneration = 30; // Interval between key regeneration
4 const otpLength = 6; // Length of the Token generated
5
6 private static $lut = array( // Lookup needed for Base32 encoding
7 "A" => 0, "B" => 1,
8 "C" => 2, "D" => 3,
9 "E" => 4, "F" => 5,
10 "G" => 6, "H" => 7,
11 "I" => 8, "J" => 9,
12 "K" => 10, "L" => 11,
13 "M" => 12, "N" => 13,
14 "O" => 14, "P" => 15,
15 "Q" => 16, "R" => 17,
16 "S" => 18, "T" => 19,
17 "U" => 20, "V" => 21,
18 "W" => 22, "X" => 23,
19 "Y" => 24, "Z" => 25,
20 "2" => 26, "3" => 27,
21 "4" => 28, "5" => 29,
22 "6" => 30, "7" => 31
23 );
24
25 public static function generate_secret_key($length = 16) {
26 $b32 = "234567QWERTYUIOPASDFGHJKLZXCVBNM";
27 $s = "";
28
29 for ($i = 0; $i < $length; $i++){
30 $s .= $b32[rand(0,31)];
31 return $s;
32 }
33 }
34
35 public static function get_timestamp() {
36 return floor(microtime(true)/self::keyRegeneration);
37 }
38
39 public static function base32_decode($b32) {
40 $b32 = strtoupper($b32);
41
42 if (!preg_match('/^[ABCDEFGHIJKLMNOPQRSTUVWXYZ234567]+$/', $b32, $match))
43 throw new Exception('Invalid characters in the base32 string.');
44 $l = strlen($b32);
45 $n = 0;
46 $j = 0;
47 $binary = "";
48
49 for ($i = 0; $i < $l; $i++) {
50
51 $n = $n << 5; // Move buffer left by 5 to make room $n = $n + self::$lut[$b32[$i]]; // Add value into buffer $j = $j + 5; // Keep track of number of bits in buffer
52 if ($j >= 8) {
53 $j = $j - 8;
54 $binary .= chr(($n & (0xFF << $j)) >> $j);
55 }
56 }
57
58 return $binary;
59 }
60
61 public static function oath_hotp($key, $counter){
62 if (strlen($key) < 8)
63 throw new Exception('Secret key is too short. Must be at least 16 base 32 characters');
64
65 $bin_counter = pack('N*', 0) . pack('N*', $counter); // Counter must be 64-bit int
66 $hash = hash_hmac ('sha1', $bin_counter, $key, true);
67
68 return str_pad(self::oath_truncate($hash), self::otpLength, '0', STR_PAD_LEFT);
69 }
70
71 public static function verify_key($b32seed, $key, $window = 4, $useTimeStamp = true) {
72 $timeStamp = self::get_timestamp();
73
74 if ($useTimeStamp !== true) $timeStamp = (int)$useTimeStamp;
75
76 $binarySeed = self::base32_decode($b32seed);
77
78 for ($ts = $timeStamp - $window; $ts <= $timeStamp + $window; $ts++){
79 if (self::oath_hotp($binarySeed, $ts) == $key)
80 return true;
81 }
82 return false;
83 }
84
85 public static function oath_truncate($hash){
86 $offset = ord($hash[19]) & 0xf;
87 return (
88 ((ord($hash[$offset+0]) & 0x7f) << 24 ) |
89 ((ord($hash[$offset+1]) & 0xff) << 16 ) |
90 ((ord($hash[$offset+2]) & 0xff) << 8 ) |
91 (ord($hash[$offset+3]) & 0xff)
92 ) % pow(10, self::otpLength);
93 }
94 }
95
96 $InitalizationKey = "coralwebdesigns"; // Set the inital key
97
98 $TimeStamp = Google2FA::get_timestamp();
99 $secretkey = Google2FA::base32_decode($InitalizationKey); // Decode it into binary
100 $otp = Google2FA::oath_hotp($secretkey, $TimeStamp); // Get current token
101
102 echo("Init key: $InitalizationKey\n");
103 echo("Timestamp: $TimeStamp\n");
104 echo("One time password: $otp\n");
105
106 // Use this to verify a key as it allows for some time drift.
107
108 $result = Google2FA::verify_key($InitalizationKey, "123456");
109
110 var_dump($result);
111?>