先来张图
-------------- NodeJS 实现 TOTP算法 ----------
NodeJS下,先安装notp库
npm install notp
然后脚本测试如下:
totp.js
index.html
<! DOCTYPE html >
< html manifest ="hotp.appcache" >
< head >
< meta charset ="UTF-8" />
< title > HOTP/TOTP Demonstration </ title >
< script src ="sjcl.js" ></ script >
< script src ="totp.js" ></ script >
< style >
label
{
display : inline-block ;
min-width : 12em ;
}
input
{
min-width : 30em !important ;
}
.hotpresult:before
{
content : "HOTP: " ;
}
.totpresult:before
{
content : "TOTP: " ;
}
</ style >
</ head >
< body >
< form id ="otpinputs" >
< label for ="secret" > Secret (Base32) </ label >
< input id ="secret" type ="text" value ="LFLFMU2SGVCUIUCZKBMEKRKLIQ" />
< br />
< label for ="hotpcounter" > HOTP Counter (next value) </ label >
< input id ="hotpcounter" type ="text" value ="0" />
< br />
< label for ="totpcounter" > TOTP Counter </ label >
< input id ="totpcounter" type ="text" value ="" />
</ form >
< button id ="generateotp" > Generate HOTP </ button >
< div id ="hotpresult" >
</ div >
< div id ="totpresult" >
</ div >
</ body >
</ html >
---------- php 实现 TOTP算法 ----------------
-------------- NodeJS 实现 TOTP算法 ----------
NodeJS下,先安装notp库
npm install notp
然后脚本测试如下:
var
notp
=
require('notp');
var token = 'LFLFMU2SGVCUIUCZKBMEKRKLIQ';
var key = notp.totp.gen(token, {});
console.log(key);
var token = 'LFLFMU2SGVCUIUCZKBMEKRKLIQ';
var key = notp.totp.gen(token, {});
console.log(key);
------------- JavaScript 实现 TOTP算法 --------------
sjcl.js
"
use strict
"
;
var
sjcl
=
{cipher:{},hash:{},keyexchange:{},mode:{},misc:{},codec:{},exception:{corrupt:
function
(a){
this
.toString
=
function
(){
return
"
CORRUPT:
"
+
this
.message};
this
.message
=
a},invalid:
function
(a){
this
.toString
=
function
(){
return
"
INVALID:
"
+
this
.message};
this
.message
=
a},bug:
function
(a){
this
.toString
=
function
(){
return
"
BUG:
"
+
this
.message};
this
.message
=
a},notReady:
function
(a){
this
.toString
=
function
(){
return
"
NOT READY:
"
+
this
.message};
this
.message
=
a}}};
" undefined " != typeof module && module.exports && (module.exports = sjcl);
sjcl.bitArray = {bitSlice: function (a,b,c){a = sjcl.bitArray.g(a.slice(b / 32 ), 32 - (b & 31 )).slice( 1 ); return void 0 === c ? a:sjcl.bitArray.clamp(a,c - b)},extract: function (a,b,c){ var d = Math.floor( - b - c & 31 ); return ((b + c - 1 ^ b) &- 32 ? a[b / 32 | 0 ] << 32 - d ^ a[b / 32 + 1 | 0 ] >>> d:a[b / 32 | 0 ] >>> d) & ( 1 << c) - 1 },concat: function (a,b){ if ( 0 === a.length || 0 === b.length) return a.concat(b); var c = a[a.length - 1 ],d = sjcl.bitArray.getPartial(c); return 32 === d ? a.concat(b):sjcl.bitArray.g(b,d,c | 0 ,a.slice( 0 ,a.length - 1 ))},bitLength: function (a){ var b = a.length; return 0 ===
b ? 0 : 32 * (b - 1 ) + sjcl.bitArray.getPartial(a[b - 1 ])},clamp: function (a,b){ if ( 32 * a.length < b) return a;a = a.slice( 0 ,Math.ceil(b / 32 )); var c = a.length;b &= 31 ; 0 < c && b && (a[c - 1 ] = sjcl.bitArray.partial(b,a[c - 1 ] & 2147483648 >> b - 1 , 1 )); return a},partial: function (a,b,c){ return 32 === a ? b:(c ? b | 0 :b << 32 - a) + 0x10000000000 * a},getPartial: function (a){ return Math.round(a / 0x10000000000 ) || 32 },equal: function (a,b){ if (sjcl.bitArray.bitLength(a) !== sjcl.bitArray.bitLength(b)) return ! 1 ; var c = 0 ,d; for (d = 0 ;d < a.length;d ++ )c |= a[d] ^ b[d]; return 0 ===
c},g: function (a,b,c,d){ var e;e = 0 ; for ( void 0 === d && (d = []); 32 <= b;b -= 32 )d.push(c),c = 0 ; if ( 0 === b) return d.concat(a); for (e = 0 ;e < a.length;e ++ )d.push(c | a[e] >>> b),c = a[e] << 32 - b;e = a.length ? a[a.length - 1 ]: 0 ;a = sjcl.bitArray.getPartial(e);d.push(sjcl.bitArray.partial(b + a & 31 , 32 < b + a ? c:d.pop(), 1 )); return d},j: function (a,b){ return [a[ 0 ] ^ b[ 0 ],a[ 1 ] ^ b[ 1 ],a[ 2 ] ^ b[ 2 ],a[ 3 ] ^ b[ 3 ]]}};
sjcl.codec.base32 = {e: " ABCDEFGHIJKLMNOPQRSTUVWXYZ234567 " ,fromBits: function (a,b){ var c = "" ,d,e = 0 ,g = sjcl.codec.base32.e,f = 0 ,k = sjcl.bitArray.bitLength(a); for (d = 0 ; 5 * c.length < k;)c += g.charAt((f ^ a[d] >>> e) >>> 27 ), 5 > e ? (f = a[d] << 5 - e,e += 27 ,d ++ ):(f <<= 5 ,e -= 5 ); for (;c.length & 5 &&! b;)c += " = " ; return c},toBits: function (a){a = a.replace( / \s |=/ g, "" ).toUpperCase(); var b = [],c,d = 0 ,e = sjcl.codec.base32.e,g = 0 ,f; for (c = 0 ;c < a.length;c ++ ){f = e.indexOf(a.charAt(c)); if ( 0 > f) throw new sjcl.exception.invalid( " this isn't base32! " ); 27 < d ? (d -=
27 ,b.push(g ^ f >>> d),g = f << 32 - d):(d += 5 ,g ^= f << 32 - d)}d & 56 && b.push(sjcl.bitArray.partial(d & 56 ,g, 1 )); return b}};sjcl.hash.sha1 = function (a){a ? ( this .d = a.d.slice( 0 ), this .b = a.b.slice( 0 ), this .a = a.a): this .reset()};sjcl.hash.sha1.hash = function (a){ return ( new sjcl.hash.sha1).update(a).finalize()};
sjcl.hash.sha1.prototype = {blockSize: 512 ,reset: function (){ this .d = this .h.slice( 0 ); this .b = []; this .a = 0 ; return this },update: function (a){ " string " === typeof a && (a = sjcl.codec.utf8String.toBits(a)); var b,c = this .b = sjcl.bitArray.concat( this .b,a);b = this .a;a = this .a = b + sjcl.bitArray.bitLength(a); for (b = this .blockSize + b &- this .blockSize;b <= a;b += this .blockSize)n( this ,c.splice( 0 , 16 )); return this },finalize: function (){ var a,b = this .b,c = this .d,b = sjcl.bitArray.concat(b,[sjcl.bitArray.partial( 1 , 1 )]); for (a = b.length + 2 ;a & 15 ;a ++ )b.push( 0 );
b.push(Math.floor( this .a / 0x100000000 )); for (b.push( this .a | 0 );b.length;)n( this ,b.splice( 0 , 16 )); this .reset(); return c},h:[ 1732584193 , 4023233417 , 2562383102 , 271733878 , 3285377520 ],i:[ 1518500249 , 1859775393 , 2400959708 , 3395469782 ]};
function n(a,b){ var c,d,e,g,f,k,m,l = b.slice( 0 ),h = a.d;e = h[ 0 ];g = h[ 1 ];f = h[ 2 ];k = h[ 3 ];m = h[ 4 ]; for (c = 0 ; 79 >= c;c ++ ) 16 <= c && (l[c] = (l[c - 3 ] ^ l[c - 8 ] ^ l[c - 14 ] ^ l[c - 16 ]) << 1 | (l[c - 3 ] ^ l[c - 8 ] ^ l[c - 14 ] ^ l[c - 16 ]) >>> 31 ),d = 19 >= c ? g & f |~ g & k: 39 >= c ? g ^ f ^ k: 59 >= c ? g & f | g & k | f & k: 79 >= c ? g ^ f ^ k: void 0 ,d = (e << 5 | e >>> 27 ) + d + m + l[c] + a.i[Math.floor(c / 20 )] | 0 ,m = k,k = f,f = g << 30 | g >>> 2 ,g = e,e = d;h[ 0 ] = h[ 0 ] + e | 0 ;h[ 1 ] = h[ 1 ] + g | 0 ;h[ 2 ] = h[ 2 ] + f | 0 ;h[ 3 ] = h[ 3 ] + k | 0 ;h[ 4 ] = h[ 4 ] + m | 0 }
sjcl.misc.hmac = function (a,b){ this .f = b = b || sjcl.hash.sha256; var c = [[],[]],d,e = b.prototype.blockSize / 32 ; this .c = [ new b, new b];a.length > e && (a = b.hash(a)); for (d = 0 ;d < e;d ++ )c[ 0 ][d] = a[d] ^ 909522486 ,c[ 1 ][d] = a[d] ^ 1549556828 ; this .c[ 0 ].update(c[ 0 ]); this .c[ 1 ].update(c[ 1 ])};sjcl.misc.hmac.prototype.encrypt = sjcl.misc.hmac.prototype.mac = function (a){a = ( new this .f( this .c[ 0 ])).update(a).finalize(); return ( new this .f( this .c[ 1 ])).update(a).finalize()};
" undefined " != typeof module && module.exports && (module.exports = sjcl);
sjcl.bitArray = {bitSlice: function (a,b,c){a = sjcl.bitArray.g(a.slice(b / 32 ), 32 - (b & 31 )).slice( 1 ); return void 0 === c ? a:sjcl.bitArray.clamp(a,c - b)},extract: function (a,b,c){ var d = Math.floor( - b - c & 31 ); return ((b + c - 1 ^ b) &- 32 ? a[b / 32 | 0 ] << 32 - d ^ a[b / 32 + 1 | 0 ] >>> d:a[b / 32 | 0 ] >>> d) & ( 1 << c) - 1 },concat: function (a,b){ if ( 0 === a.length || 0 === b.length) return a.concat(b); var c = a[a.length - 1 ],d = sjcl.bitArray.getPartial(c); return 32 === d ? a.concat(b):sjcl.bitArray.g(b,d,c | 0 ,a.slice( 0 ,a.length - 1 ))},bitLength: function (a){ var b = a.length; return 0 ===
b ? 0 : 32 * (b - 1 ) + sjcl.bitArray.getPartial(a[b - 1 ])},clamp: function (a,b){ if ( 32 * a.length < b) return a;a = a.slice( 0 ,Math.ceil(b / 32 )); var c = a.length;b &= 31 ; 0 < c && b && (a[c - 1 ] = sjcl.bitArray.partial(b,a[c - 1 ] & 2147483648 >> b - 1 , 1 )); return a},partial: function (a,b,c){ return 32 === a ? b:(c ? b | 0 :b << 32 - a) + 0x10000000000 * a},getPartial: function (a){ return Math.round(a / 0x10000000000 ) || 32 },equal: function (a,b){ if (sjcl.bitArray.bitLength(a) !== sjcl.bitArray.bitLength(b)) return ! 1 ; var c = 0 ,d; for (d = 0 ;d < a.length;d ++ )c |= a[d] ^ b[d]; return 0 ===
c},g: function (a,b,c,d){ var e;e = 0 ; for ( void 0 === d && (d = []); 32 <= b;b -= 32 )d.push(c),c = 0 ; if ( 0 === b) return d.concat(a); for (e = 0 ;e < a.length;e ++ )d.push(c | a[e] >>> b),c = a[e] << 32 - b;e = a.length ? a[a.length - 1 ]: 0 ;a = sjcl.bitArray.getPartial(e);d.push(sjcl.bitArray.partial(b + a & 31 , 32 < b + a ? c:d.pop(), 1 )); return d},j: function (a,b){ return [a[ 0 ] ^ b[ 0 ],a[ 1 ] ^ b[ 1 ],a[ 2 ] ^ b[ 2 ],a[ 3 ] ^ b[ 3 ]]}};
sjcl.codec.base32 = {e: " ABCDEFGHIJKLMNOPQRSTUVWXYZ234567 " ,fromBits: function (a,b){ var c = "" ,d,e = 0 ,g = sjcl.codec.base32.e,f = 0 ,k = sjcl.bitArray.bitLength(a); for (d = 0 ; 5 * c.length < k;)c += g.charAt((f ^ a[d] >>> e) >>> 27 ), 5 > e ? (f = a[d] << 5 - e,e += 27 ,d ++ ):(f <<= 5 ,e -= 5 ); for (;c.length & 5 &&! b;)c += " = " ; return c},toBits: function (a){a = a.replace( / \s |=/ g, "" ).toUpperCase(); var b = [],c,d = 0 ,e = sjcl.codec.base32.e,g = 0 ,f; for (c = 0 ;c < a.length;c ++ ){f = e.indexOf(a.charAt(c)); if ( 0 > f) throw new sjcl.exception.invalid( " this isn't base32! " ); 27 < d ? (d -=
27 ,b.push(g ^ f >>> d),g = f << 32 - d):(d += 5 ,g ^= f << 32 - d)}d & 56 && b.push(sjcl.bitArray.partial(d & 56 ,g, 1 )); return b}};sjcl.hash.sha1 = function (a){a ? ( this .d = a.d.slice( 0 ), this .b = a.b.slice( 0 ), this .a = a.a): this .reset()};sjcl.hash.sha1.hash = function (a){ return ( new sjcl.hash.sha1).update(a).finalize()};
sjcl.hash.sha1.prototype = {blockSize: 512 ,reset: function (){ this .d = this .h.slice( 0 ); this .b = []; this .a = 0 ; return this },update: function (a){ " string " === typeof a && (a = sjcl.codec.utf8String.toBits(a)); var b,c = this .b = sjcl.bitArray.concat( this .b,a);b = this .a;a = this .a = b + sjcl.bitArray.bitLength(a); for (b = this .blockSize + b &- this .blockSize;b <= a;b += this .blockSize)n( this ,c.splice( 0 , 16 )); return this },finalize: function (){ var a,b = this .b,c = this .d,b = sjcl.bitArray.concat(b,[sjcl.bitArray.partial( 1 , 1 )]); for (a = b.length + 2 ;a & 15 ;a ++ )b.push( 0 );
b.push(Math.floor( this .a / 0x100000000 )); for (b.push( this .a | 0 );b.length;)n( this ,b.splice( 0 , 16 )); this .reset(); return c},h:[ 1732584193 , 4023233417 , 2562383102 , 271733878 , 3285377520 ],i:[ 1518500249 , 1859775393 , 2400959708 , 3395469782 ]};
function n(a,b){ var c,d,e,g,f,k,m,l = b.slice( 0 ),h = a.d;e = h[ 0 ];g = h[ 1 ];f = h[ 2 ];k = h[ 3 ];m = h[ 4 ]; for (c = 0 ; 79 >= c;c ++ ) 16 <= c && (l[c] = (l[c - 3 ] ^ l[c - 8 ] ^ l[c - 14 ] ^ l[c - 16 ]) << 1 | (l[c - 3 ] ^ l[c - 8 ] ^ l[c - 14 ] ^ l[c - 16 ]) >>> 31 ),d = 19 >= c ? g & f |~ g & k: 39 >= c ? g ^ f ^ k: 59 >= c ? g & f | g & k | f & k: 79 >= c ? g ^ f ^ k: void 0 ,d = (e << 5 | e >>> 27 ) + d + m + l[c] + a.i[Math.floor(c / 20 )] | 0 ,m = k,k = f,f = g << 30 | g >>> 2 ,g = e,e = d;h[ 0 ] = h[ 0 ] + e | 0 ;h[ 1 ] = h[ 1 ] + g | 0 ;h[ 2 ] = h[ 2 ] + f | 0 ;h[ 3 ] = h[ 3 ] + k | 0 ;h[ 4 ] = h[ 4 ] + m | 0 }
sjcl.misc.hmac = function (a,b){ this .f = b = b || sjcl.hash.sha256; var c = [[],[]],d,e = b.prototype.blockSize / 32 ; this .c = [ new b, new b];a.length > e && (a = b.hash(a)); for (d = 0 ;d < e;d ++ )c[ 0 ][d] = a[d] ^ 909522486 ,c[ 1 ][d] = a[d] ^ 1549556828 ; this .c[ 0 ].update(c[ 0 ]); this .c[ 1 ].update(c[ 1 ])};sjcl.misc.hmac.prototype.encrypt = sjcl.misc.hmac.prototype.mac = function (a){a = ( new this .f( this .c[ 0 ])).update(a).finalize(); return ( new this .f( this .c[ 1 ])).update(a).finalize()};
totp.js
(
function
()
{
" use strict " ;
/* global document, sjcl */
function HOTP(K, C)
{
var key = sjcl.codec.base32.toBits(K);
// Count is 64 bits long. Note that JavaScript bitwise operations make
// the MSB effectively 0 in this case.
var count = [((C & 0xffffffff00000000 ) >> 32 ), C & 0xffffffff ];
var otplength = 6 ;
var hmacsha1 = new sjcl.misc.hmac(key, sjcl.hash.sha1);
var code = hmacsha1.encrypt(count);
var offset = sjcl.bitArray.extract(code, 152 , 8 ) & 0x0f ;
var startBits = offset * 8 ;
var endBits = startBits + 4 * 8 ;
var slice = sjcl.bitArray.bitSlice(code, startBits, endBits);
var dbc1 = slice[ 0 ];
var dbc2 = dbc1 & 0x7fffffff ;
var otp = dbc2 % Math.pow( 10 , otplength);
var result = otp.toString();
while (result.length < otplength)
{
result = ' 0 ' + result;
}
return result;
}
//
// UI Functions
//
function GenerateHOTP()
{
var secret = document.getElementById('secret').value;
var counterEl = document.getElementById('hotpcounter');
var counter = parseInt(counterEl.value, 10 );
var otp = HOTP(secret, counter);
var passwordEl = document.getElementById('hotpresult');
while (passwordEl.hasChildNodes())
{
passwordEl.removeChild(passwordEl.firstChild);
}
passwordEl.textContent = " HOTP: " + otp;
counterEl.value = counter + 1 ;
}
function GenerateTOTP()
{
var secret = document.getElementById('secret').value;
var ctime = Math.floor(( new Date() - 0 ) / 30000 );
var counterEl = document.getElementById('totpcounter');
counterEl.value = ctime;
var otp = HOTP(secret, ctime);
var passwordEl = document.getElementById('totpresult');
while (passwordEl.hasChildNodes())
{
passwordEl.removeChild(passwordEl.firstChild);
}
passwordEl.textContent = " TOTP: " + otp;
}
function ConfigureHandlers()
{
var el = document.getElementById('generateotp');
el.addEventListener('click', GenerateHOTP, false );
setInterval(GenerateTOTP, 1000 );
GenerateHOTP();
GenerateTOTP();
}
document.addEventListener('DOMContentLoaded', ConfigureHandlers, false );
}
)();
{
" use strict " ;
/* global document, sjcl */
function HOTP(K, C)
{
var key = sjcl.codec.base32.toBits(K);
// Count is 64 bits long. Note that JavaScript bitwise operations make
// the MSB effectively 0 in this case.
var count = [((C & 0xffffffff00000000 ) >> 32 ), C & 0xffffffff ];
var otplength = 6 ;
var hmacsha1 = new sjcl.misc.hmac(key, sjcl.hash.sha1);
var code = hmacsha1.encrypt(count);
var offset = sjcl.bitArray.extract(code, 152 , 8 ) & 0x0f ;
var startBits = offset * 8 ;
var endBits = startBits + 4 * 8 ;
var slice = sjcl.bitArray.bitSlice(code, startBits, endBits);
var dbc1 = slice[ 0 ];
var dbc2 = dbc1 & 0x7fffffff ;
var otp = dbc2 % Math.pow( 10 , otplength);
var result = otp.toString();
while (result.length < otplength)
{
result = ' 0 ' + result;
}
return result;
}
//
// UI Functions
//
function GenerateHOTP()
{
var secret = document.getElementById('secret').value;
var counterEl = document.getElementById('hotpcounter');
var counter = parseInt(counterEl.value, 10 );
var otp = HOTP(secret, counter);
var passwordEl = document.getElementById('hotpresult');
while (passwordEl.hasChildNodes())
{
passwordEl.removeChild(passwordEl.firstChild);
}
passwordEl.textContent = " HOTP: " + otp;
counterEl.value = counter + 1 ;
}
function GenerateTOTP()
{
var secret = document.getElementById('secret').value;
var ctime = Math.floor(( new Date() - 0 ) / 30000 );
var counterEl = document.getElementById('totpcounter');
counterEl.value = ctime;
var otp = HOTP(secret, ctime);
var passwordEl = document.getElementById('totpresult');
while (passwordEl.hasChildNodes())
{
passwordEl.removeChild(passwordEl.firstChild);
}
passwordEl.textContent = " TOTP: " + otp;
}
function ConfigureHandlers()
{
var el = document.getElementById('generateotp');
el.addEventListener('click', GenerateHOTP, false );
setInterval(GenerateTOTP, 1000 );
GenerateHOTP();
GenerateTOTP();
}
document.addEventListener('DOMContentLoaded', ConfigureHandlers, false );
}
)();
index.html
<! DOCTYPE html >
< html manifest ="hotp.appcache" >
< head >
< meta charset ="UTF-8" />
< title > HOTP/TOTP Demonstration </ title >
< script src ="sjcl.js" ></ script >
< script src ="totp.js" ></ script >
< style >
label
{
display : inline-block ;
min-width : 12em ;
}
input
{
min-width : 30em !important ;
}
.hotpresult:before
{
content : "HOTP: " ;
}
.totpresult:before
{
content : "TOTP: " ;
}
</ style >
</ head >
< body >
< form id ="otpinputs" >
< label for ="secret" > Secret (Base32) </ label >
< input id ="secret" type ="text" value ="LFLFMU2SGVCUIUCZKBMEKRKLIQ" />
< br />
< label for ="hotpcounter" > HOTP Counter (next value) </ label >
< input id ="hotpcounter" type ="text" value ="0" />
< br />
< label for ="totpcounter" > TOTP Counter </ label >
< input id ="totpcounter" type ="text" value ="" />
</ form >
< button id ="generateotp" > Generate HOTP </ button >
< div id ="hotpresult" >
</ div >
< div id ="totpresult" >
</ div >
</ body >
</ html >
---------- php 实现 TOTP算法 ----------------
<?
/* *
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* PHP Google two-factor authentication module.
*
* See http://www.idontplaydarts.com/2011/07/google-totp-two-factor-authentication-for-php/
* for more details
*
* @author Phil
* */
class Google2FA {
const keyRegeneration = 30 ; // Interval between key regeneration
const otpLength = 6 ; // Length of the Token generated
private static $lut = array ( // Lookup needed for Base32 encoding
" A " => 0 , " B " => 1 ,
" C " => 2 , " D " => 3 ,
" E " => 4 , " F " => 5 ,
" G " => 6 , " H " => 7 ,
" I " => 8 , " J " => 9 ,
" K " => 10 , " L " => 11 ,
" M " => 12 , " N " => 13 ,
" O " => 14 , " P " => 15 ,
" Q " => 16 , " R " => 17 ,
" S " => 18 , " T " => 19 ,
" U " => 20 , " V " => 21 ,
" W " => 22 , " X " => 23 ,
" Y " => 24 , " Z " => 25 ,
" 2 " => 26 , " 3 " => 27 ,
" 4 " => 28 , " 5 " => 29 ,
" 6 " => 30 , " 7 " => 31
);
/* *
* Generates a 16 digit secret key in base32 format
* @return string
* */
public static function generate_secret_key( $length = 16 ) {
$b32 = " 234567QWERTYUIOPASDFGHJKLZXCVBNM " ;
$s = "" ;
for ( $i = 0 ; $i < $length ; $i ++ )
$s .= $b32 [ rand ( 0 , 31 )];
return $s ;
}
/* *
* Returns the current Unix Timestamp devided by the keyRegeneration
* period.
* @return integer
* */
public static function get_timestamp() {
return floor ( microtime ( true ) / self :: keyRegeneration);
}
/* *
* Decodes a base32 string into a binary string.
* */
public static function base32_decode( $b32 ) {
$b32 = strtoupper ( $b32 );
if ( ! preg_match ( ' /^[ABCDEFGHIJKLMNOPQRSTUVWXYZ234567]+$/ ' , $b32 , $match ))
throw new Exception ( ' Invalid characters in the base32 string. ' );
$l = strlen ( $b32 );
$n = 0 ;
$j = 0 ;
$binary = "" ;
for ( $i = 0 ; $i < $l ; $i ++ ) {
$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
if ( $j >= 8 ) {
$j = $j - 8 ;
$binary .= chr (( $n & ( 0xFF << $j )) >> $j );
}
}
return $binary ;
}
/* *
* Takes the secret key and the timestamp and returns the one time
* password.
*
* @param binary $key - Secret key in binary form.
* @param integer $counter - Timestamp as returned by get_timestamp.
* @return string
* */
public static function oath_hotp( $key , $counter )
{
if ( strlen ( $key ) < 8 )
throw new Exception ( ' Secret key is too short. Must be at least 16 base 32 characters ' );
$bin_counter = pack ( ' N* ' , 0 ) . pack ( ' N* ' , $counter ); // Counter must be 64-bit int
$hash = hash_hmac ( ' sha1 ' , $bin_counter , $key , true );
return str_pad (self :: oath_truncate( $hash ) , self :: otpLength , ' 0 ' , STR_PAD_LEFT);
}
/* *
* Verifys a user inputted key against the current timestamp. Checks $window
* keys either side of the timestamp.
*
* @param string $b32seed
* @param string $key - User specified key
* @param integer $window
* @param boolean $useTimeStamp
* @return boolean
* */
public static function verify_key( $b32seed , $key , $window = 4 , $useTimeStamp = true ) {
$timeStamp = self :: get_timestamp();
if ( $useTimeStamp !== true ) $timeStamp = (int) $useTimeStamp ;
$binarySeed = self :: base32_decode( $b32seed );
for ( $ts = $timeStamp - $window ; $ts <= $timeStamp + $window ; $ts ++ )
if (self :: oath_hotp( $binarySeed , $ts ) == $key )
return true ;
return false ;
}
/* *
* Extracts the OTP from the SHA1 hash.
* @param binary $hash
* @return integer
* */
public static function oath_truncate( $hash )
{
$offset = ord ( $hash [ 19 ]) & 0xf ;
return (
(( ord ( $hash [ $offset + 0 ]) & 0x7f ) << 24 ) |
(( ord ( $hash [ $offset + 1 ]) & 0xff ) << 16 ) |
(( ord ( $hash [ $offset + 2 ]) & 0xff ) << 8 ) |
( ord ( $hash [ $offset + 3 ]) & 0xff )
) % pow ( 10 , self :: otpLength);
}
}
$InitalizationKey = " LFLFMU2SGVCUIUCZKBMEKRKLIQ " ; // Set the inital key
$TimeStamp = Google2FA :: get_timestamp();
$secretkey = Google2FA :: base32_decode( $InitalizationKey ); // Decode it into binary
$otp = Google2FA :: oath_hotp( $secretkey , $TimeStamp ); // Get current token
echo ( " Init key: $InitalizationKey\n " );
echo ( " Timestamp: $TimeStamp\n " );
echo ( " One time password: $otp\n " );
// Use this to verify a key as it allows for some time drift.
$result = Google2FA :: verify_key( $InitalizationKey , " 123456 " );
var_dump ( $result );
?>
/* *
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* PHP Google two-factor authentication module.
*
* See http://www.idontplaydarts.com/2011/07/google-totp-two-factor-authentication-for-php/
* for more details
*
* @author Phil
* */
class Google2FA {
const keyRegeneration = 30 ; // Interval between key regeneration
const otpLength = 6 ; // Length of the Token generated
private static $lut = array ( // Lookup needed for Base32 encoding
" A " => 0 , " B " => 1 ,
" C " => 2 , " D " => 3 ,
" E " => 4 , " F " => 5 ,
" G " => 6 , " H " => 7 ,
" I " => 8 , " J " => 9 ,
" K " => 10 , " L " => 11 ,
" M " => 12 , " N " => 13 ,
" O " => 14 , " P " => 15 ,
" Q " => 16 , " R " => 17 ,
" S " => 18 , " T " => 19 ,
" U " => 20 , " V " => 21 ,
" W " => 22 , " X " => 23 ,
" Y " => 24 , " Z " => 25 ,
" 2 " => 26 , " 3 " => 27 ,
" 4 " => 28 , " 5 " => 29 ,
" 6 " => 30 , " 7 " => 31
);
/* *
* Generates a 16 digit secret key in base32 format
* @return string
* */
public static function generate_secret_key( $length = 16 ) {
$b32 = " 234567QWERTYUIOPASDFGHJKLZXCVBNM " ;
$s = "" ;
for ( $i = 0 ; $i < $length ; $i ++ )
$s .= $b32 [ rand ( 0 , 31 )];
return $s ;
}
/* *
* Returns the current Unix Timestamp devided by the keyRegeneration
* period.
* @return integer
* */
public static function get_timestamp() {
return floor ( microtime ( true ) / self :: keyRegeneration);
}
/* *
* Decodes a base32 string into a binary string.
* */
public static function base32_decode( $b32 ) {
$b32 = strtoupper ( $b32 );
if ( ! preg_match ( ' /^[ABCDEFGHIJKLMNOPQRSTUVWXYZ234567]+$/ ' , $b32 , $match ))
throw new Exception ( ' Invalid characters in the base32 string. ' );
$l = strlen ( $b32 );
$n = 0 ;
$j = 0 ;
$binary = "" ;
for ( $i = 0 ; $i < $l ; $i ++ ) {
$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
if ( $j >= 8 ) {
$j = $j - 8 ;
$binary .= chr (( $n & ( 0xFF << $j )) >> $j );
}
}
return $binary ;
}
/* *
* Takes the secret key and the timestamp and returns the one time
* password.
*
* @param binary $key - Secret key in binary form.
* @param integer $counter - Timestamp as returned by get_timestamp.
* @return string
* */
public static function oath_hotp( $key , $counter )
{
if ( strlen ( $key ) < 8 )
throw new Exception ( ' Secret key is too short. Must be at least 16 base 32 characters ' );
$bin_counter = pack ( ' N* ' , 0 ) . pack ( ' N* ' , $counter ); // Counter must be 64-bit int
$hash = hash_hmac ( ' sha1 ' , $bin_counter , $key , true );
return str_pad (self :: oath_truncate( $hash ) , self :: otpLength , ' 0 ' , STR_PAD_LEFT);
}
/* *
* Verifys a user inputted key against the current timestamp. Checks $window
* keys either side of the timestamp.
*
* @param string $b32seed
* @param string $key - User specified key
* @param integer $window
* @param boolean $useTimeStamp
* @return boolean
* */
public static function verify_key( $b32seed , $key , $window = 4 , $useTimeStamp = true ) {
$timeStamp = self :: get_timestamp();
if ( $useTimeStamp !== true ) $timeStamp = (int) $useTimeStamp ;
$binarySeed = self :: base32_decode( $b32seed );
for ( $ts = $timeStamp - $window ; $ts <= $timeStamp + $window ; $ts ++ )
if (self :: oath_hotp( $binarySeed , $ts ) == $key )
return true ;
return false ;
}
/* *
* Extracts the OTP from the SHA1 hash.
* @param binary $hash
* @return integer
* */
public static function oath_truncate( $hash )
{
$offset = ord ( $hash [ 19 ]) & 0xf ;
return (
(( ord ( $hash [ $offset + 0 ]) & 0x7f ) << 24 ) |
(( ord ( $hash [ $offset + 1 ]) & 0xff ) << 16 ) |
(( ord ( $hash [ $offset + 2 ]) & 0xff ) << 8 ) |
( ord ( $hash [ $offset + 3 ]) & 0xff )
) % pow ( 10 , self :: otpLength);
}
}
$InitalizationKey = " LFLFMU2SGVCUIUCZKBMEKRKLIQ " ; // Set the inital key
$TimeStamp = Google2FA :: get_timestamp();
$secretkey = Google2FA :: base32_decode( $InitalizationKey ); // Decode it into binary
$otp = Google2FA :: oath_hotp( $secretkey , $TimeStamp ); // Get current token
echo ( " Init key: $InitalizationKey\n " );
echo ( " Timestamp: $TimeStamp\n " );
echo ( " One time password: $otp\n " );
// Use this to verify a key as it allows for some time drift.
$result = Google2FA :: verify_key( $InitalizationKey , " 123456 " );
var_dump ( $result );
?>