3 class GoogleAuthenticator {
5 // first we init google authenticator by passing it a filename
6 // for its sqlite database.
7 function __construct($file) {
8 if(file_exists($file)) {
10 $this->dbConnector = new PDO("sqlite:$file");
11 } catch(PDOException $exep) {
12 $this->errorText = $exep->getMessage();
13 $this->dbConnector = false;
16 $this->setupDB($file);
19 $this->dbFile = $file;
22 // creates the database (tables);
23 function setupDB($file) {
26 $this->dbConnector = new PDO("sqlite:$file");
27 } catch(PDOException $exep) {
28 $this->errorText = $exep->getMessage();
29 $this->dbConnector = false;
32 // here we create some tables and stuff
33 $this->dbConnector->query('CREATE TABLE "users" ("user_id" INTEGER PRIMARY KEY AUTOINCREMENT,"user_name" TEXT NOT NULL,"user_tokenid" INTEGER)');
34 $this->dbConnector->query('CREATE TABLE "tokens" ("token_id" INTEGER PRIMARY KEY AUTOINCREMENT,"token_key" TEXT NOT NULL, "token_type" TEXT NOT NULL, "token_lastid" INTEGER NOT NULL)');
37 // creates "user" in the database and returns a url for
38 // the phone. If user already exists, this returns false
39 // if any error occurs, this returns false
40 function setupUser($username) {
41 $key = $this->createBase32Key();
43 // sql for inserting into db
44 $sql = "select * from users where user_name='$username'";
45 $res = $this->dbConnector->query($sql);
47 if($res->fetchCount()>0) {
48 $this->errorText = "User Already Exists, $username";
52 // and finally create 'em
53 $this->dbConnector->query("insert into tokens values (NULL, '$key', 'HOTP','0')");
54 $id = $this->dbConnector->lastInsertID();
55 $this->dbConnector->query("insert into users values (NULL, '$username', '$id')");
57 $url = $this->createURL($username, $key);
62 // set the token type the user it going to use.
63 // this defaults to HOTP - we only do 30s token
64 // so lets not be able to set that yet
65 function setupTokenType($username, $tokentype) {
66 if($tokentype!="HOTP" and $tokentype!="TOTP") {
67 $errorText = "Invalid Token Type";
71 $sql = "select * from users where user_name='$username'";
72 $res = $this->dbConnector->query($sql);
74 foreach($res as $row) {
75 $tid = $row["user_tokenid"];
79 // TODO MUST DO ERROR CHECK HERE, this line could be lethal
80 $sql = "update tokens set token_type='$tokentype' where token_id='$tid'";
86 // create "user" with insert
87 function createUser($username, $key) {
88 // sql for inserting into db
89 $sql = "select * from users where user_name='$username'";
90 $res = $this->dbConnector->query($sql);
92 //if($res) if($res->fetchCount()>0) {
93 //$this->errorText = "User Already Exists, $username";
97 // and finally create 'em
98 $this->dbConnector->query("insert into tokens values (NULL, '$key', 'HOTP', '0')");
99 $id = $this->dbConnector->lastInsertID();
100 $this->dbConnector->query("insert into users values (NULL, '$username', '$id')");
102 $url = $this->createURL($username, $key);
107 // Replcate "user" in the database... All this really
108 // does is to replace the key for the user. Returns false
109 // if the user doesnt exist of the key is poop
110 function replaceUser($username) {
111 $key = $this->createBase32Key();
113 // delete the user - TODO, clean up auth tokens
114 $sql = "delete from users where user_name='$username'";
115 $res = $this->dbConnector->query($sql);
117 // sql for inserting into db - just making sure.
118 $sql = "select * from users where user_name='$username'";
119 $res = $this->dbConnector->query($sql);
121 if($res->fetchCount()>0) {
122 $this->errorText = "User Already Exists, $username";
126 // and finally create 'em
127 $this->dbConnector->query("insert into tokens values (NULL, '$key', '0')");
128 $id = $this->dbConnector->lastInsertID();
129 $this->dbConnector->query("insert into users values (NULL, '$username', '$id')");
131 $url = $this->createURL($username, $key);
136 // sets the key for a user - this is assuming you dont want
137 // to use one created by the application. returns false
138 // if the key is invalid or the user doesn't exist.
139 function setUserKey($username, $key) {
140 $sql = "select * from users where user_name='$username'";
141 $res = $this->dbConnector->query($sql);
143 foreach($res as $row) {
144 $tid = $row["user_tokenid"];
148 // TODO MUST DO ERROR CHECK HERE, this line could be lethal
149 $sql = "update tokens set token_key='$key' where token_id='$tid'";
155 function deleteUser($username) {
156 $sql = "select * from users where user_name='$username'";
157 $res = $this->dbConnector->query($sql);
159 foreach($res as $row) {
160 $tid = $row["user_tokenid"];
164 // TODO MUST DO ERROR CHECK HERE, this line could be lethal
165 $sql = "delete from tokens where token_id='$tid'";
166 $this->dbConnector->query($sql);
168 $sql = "delete from users where user_name='$username'";
169 $this->dbConnector->query($sql);
172 // user has input their user name and some code, authenticate
174 function authenticateUser($username, $code) {
175 $sql = "select * from users where user_name='$username'";
176 $res = $this->dbConnector->query($sql);
179 foreach($res as $row) {
180 $tid = $row["user_tokenid"];
183 // for HOTP tokens we start at x and go to x+20
185 // for TOTP we go +/-1min TODO = remember that +/- 1min should
186 // be changed based on stepping if we change the expiration time
189 // $this->dbConnector->query('CREATE TABLE "tokens" ("token_id" INTEGER PRIMARY KEY AUTOINCREMENT,"token_key" TEXT NOT NULL, "token_type" TEXT NOT NULL, "token_lastid" INTEGER NOT NULL)');
191 $sql = "select * from tokens where token_id='$tid'";
192 $res = $this->dbConnector->query($sql);
197 foreach($res as $row) {
198 $tkey = $row["token_key"];
199 $ttype = $row["token_type"];
200 $tlid = $row["token_lastid"];
207 for($i=$st; $i<$en; $i++) {
208 $stest = $this->oath_hotp($tkey, $i);
209 //echo "code: $code, $stest, $tkey\n";
210 if($code == $stest) {
211 $sql = "update tokens set token_lastid='$i' where token_id='$tid'";
212 $this->dbConnector->query($sql);
221 echo "how the frig did i end up here?";
228 // this function allows a user to resync their key. If too
229 // many codes are called, we only check up to 20 codes in the future
230 // so if the user is at 21, they'll always fail.
231 function resyncCode($username, $code1, $code2) {
235 // gets the error text associated with the last error
236 function getErrorText() {
237 return $this->errorText;
240 // create a url compatibile with google authenticator.
241 function createURL($user, $key) {
242 $url = "otpauth://totp/$user?secret=$key";
247 // creeates a base 32 key (random)
248 function createBase32Key() {
249 $alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
251 for($i=0; $i<16; $i++) {
252 $offset = rand(0,strlen($alphabet)-1);
253 //echo "$i off is $offset\n";
254 $key .= $alphabet[$offset];
261 function helperb322hex($b32) {
262 $alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
267 for($i = 0; $i < strlen($b32); $i++) {
268 $in = strrpos($alphabet, $b32[$i]);
269 $b = str_pad(base_convert($in, 10, 2), 5, "0", STR_PAD_LEFT);
274 $ar = str_split($out,20);
276 //echo "$dous, $b\n";
280 foreach($ar as $val) {
281 $rv = str_pad(base_convert($val, 2, 16), 5, "0", STR_PAD_LEFT);
282 //echo "rv: $rv from $val\n";
291 function helperhex2b32($hex) {
292 $alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
294 $ar = str_split($hex, 5);
297 foreach($ar as $var) {
298 $bc = base_convert($var, 16, 2);
299 $bin = str_pad($bc, 20, "0", STR_PAD_LEFT);
301 //echo "$bc was, $var is, $bin are\n";
305 $ar2 = str_split($out, 5);
306 foreach($ar2 as $var2) {
307 $bc = base_convert($var2, 2, 10);
308 $out2 .= $alphabet[$bc];
314 function oath_hotp($key, $counter)
316 $key = pack("H*", $key);
317 $cur_counter = array(0,0,0,0,0,0,0,0);
320 $cur_counter[$i] = pack ('C*', $counter);
321 $counter = $counter >> 8;
323 $bin_counter = implode($cur_counter);
325 if (strlen ($bin_counter) < 8)
327 $bin_counter = str_repeat (chr(0), 8 - strlen ($bin_counter)) . $bin_counter;
331 $hash = hash_hmac ('sha1', $bin_counter, $key);
332 return $this->oath_truncate($hash);
335 function oath_truncate($hash, $length = 6)
338 foreach(str_split($hash,2) as $hex)
340 $hmac_result[]=hexdec($hex);
344 $offset = $hmac_result[19] & 0xf;
346 // Algorithm from RFC
349 (($hmac_result[$offset+0] & 0x7f) << 24 ) |
350 (($hmac_result[$offset+1] & 0xff) << 16 ) |
351 (($hmac_result[$offset+2] & 0xff) << 8 ) |
352 ($hmac_result[$offset+3] & 0xff)
357 // some private data bits.
360 private $dbConnector;