Compare commits

...

12 Commits

Author SHA1 Message Date
RobThree e837caa9ce * Force VerifyCode() to return bool 2015-07-16 15:43:45 +02:00
Rob Janssen 0332a61b67 Update README.md 2015-05-07 12:43:42 +02:00
Rob Janssen cff5dadb79 * Typo 2015-05-06 11:21:53 +02:00
Rob Janssen 1647e46ee3 * Removed incorrect statement 2015-05-06 11:19:28 +02:00
Rob Janssen 52cbcf7579 * Removed stupid typo 2015-04-30 14:39:06 +02:00
Rob Janssen 1f9008c217 Update composer.json 2015-04-30 12:58:15 +02:00
RobThree 955e9b0b03 * Fix for possible timing-attack in verifyCode() method 2015-04-30 12:53:21 +02:00
Rob Janssen 1d0a9432e6 Update README.md 2015-04-06 20:52:43 +02:00
RobThree 3b3e723bea * More variable names improved 2015-04-06 20:35:23 +02:00
RobThree f1a729c9ed * Better variable naming 2015-04-06 20:29:16 +02:00
Rob Janssen 98d3f2a21b Update .travis.yml 2015-03-25 23:59:07 +01:00
Rob Janssen 8976cf138e Update README.md 2015-03-23 09:30:46 +01:00
6 changed files with 53 additions and 38 deletions
+1 -1
View File
@@ -8,4 +8,4 @@ php:
- 7
- hhvm
script: phpunit tests
script: phpunit --coverage-text tests
+7 -10
View File
@@ -1,6 +1,6 @@
# ![Logo](https://raw.githubusercontent.com/RobThree/TwoFactorAuth/master/logo.png) TwoFactorAuth class for PHP
[![Build status](https://img.shields.io/travis/RobThree/TwoFactorAuth.svg?style=flat-square)](https://travis-ci.org/RobThree/TwoFactorAuth/) [![Latest Stable Version](https://img.shields.io/packagist/v/robthree/twofactorauth.svg?style=flat-square)](https://packagist.org/packages/robthree/twofactorauth) [![License](https://img.shields.io/packagist/l/robthree/twofactorauth.svg?style=flat-square)](LICENSE) [![Downloads](https://img.shields.io/packagist/dt/robthree/twofactorauth.svg?style=flat-square)](https://packagist.org/packages/robthree/twofactorauth) [![HHVM Status](https://img.shields.io/hhvm/RobThree/TwoFactorAuth.svg?style=flat-square)](http://hhvm.h4cc.de/package/robthree/twofactorauth) [![PayPal donate button](http://img.shields.io/badge/paypal-donate-orange.svg?style=flat-square)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=6MB5M2SQLP636 "Keep me off the streets")
[![Build status](https://img.shields.io/travis/RobThree/TwoFactorAuth.svg?style=flat-square)](https://travis-ci.org/RobThree/TwoFactorAuth/) [![Latest Stable Version](https://img.shields.io/packagist/v/robthree/twofactorauth.svg?style=flat-square)](https://packagist.org/packages/robthree/twofactorauth) [![License](https://img.shields.io/packagist/l/robthree/twofactorauth.svg?style=flat-square)](LICENSE) [![Downloads](https://img.shields.io/packagist/dt/robthree/twofactorauth.svg?style=flat-square)](https://packagist.org/packages/robthree/twofactorauth) [![HHVM Status](https://img.shields.io/hhvm/RobThree/TwoFactorAuth.svg?style=flat-square)](http://hhvm.h4cc.de/package/robthree/twofactorauth) [![Code Climate](https://img.shields.io/codeclimate/github/RobThree/TwoFactorAuth.svg?style=flat-square)](https://codeclimate.com/github/RobThree/TwoFactorAuth) [![PayPal donate button](http://img.shields.io/badge/paypal-donate-orange.svg?style=flat-square)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=6MB5M2SQLP636 "Keep me off the streets")
PHP class for [two-factor (or multi-factor) authentication](http://en.wikipedia.org/wiki/Multi-factor_authentication) using [TOTP](http://en.wikipedia.org/wiki/Time-based_One-time_Password_Algorithm) and [QR-codes](http://en.wikipedia.org/wiki/QR_code). Inspired by, based on but most importantly an *improvement* on '[PHPGangsta/GoogleAuthenticator](https://github.com/PHPGangsta/GoogleAuthenticator)'.
@@ -10,18 +10,15 @@ PHP class for [two-factor (or multi-factor) authentication](http://en.wikipedia.
## Requirements
* Tested on PHP 5.3, 5.4, 5.5 and 5.6
* Tested on PHP 5.3, 5.4, 5.5 and 5.6, 7 and HHVM
* [cURL](http://php.net/manual/en/book.curl.php) when using the provided `GoogleQRCodeProvider` (default), `QRServerProvider` or `QRicketProvider` but you can also provide your own QR-code provider.
* [MCrypt](http://php.net/manual/en/book.mcrypt.php), [OpenSSL](http://php.net/manual/en/book.openssl.php) or [Hash](http://php.net/manual/en/book.hash.php) depending on which built-in RNG you use (TwoFactorAuth will try to 'autodetect' and use the best available); however: feel free to provide your own (CS)RNG.
## Installation
````json
"require": {
"robthree/twofactorauth": "1.0"
}
````
And run `php composer update`
Run the following command:
`php composer.phar require robthree/twofactorauth`
## Quick start
@@ -51,7 +48,7 @@ These parameters are all '`write once`'; the class will, for it's lifetime, use
### Step 1: Set up secret shared key
When a user wants to setup two-factor auth (or, more correctly, multi-factor auth) you need to create a secret. This will be your **shared** (this will be the `one-time` in [TOTP](http://en.wikipedia.org/wiki/Time-based_One-time_Password_Algorithm)) **secret**. This secret will need to be entered by the user in their app. This can be done manually, in which case you simply display the secret and have the user type it in the app:
When a user wants to setup two-factor auth (or, more correctly, multi-factor auth) you need to create a secret. This will be your **shared secret**. This secret will need to be entered by the user in their app. This can be done manually, in which case you simply display the secret and have the user type it in the app:
````php
$secret = $tfa->createSecret();
@@ -169,7 +166,7 @@ Voilà. Couldn't make it any simpler.
### RNG providers
This class also comes with three 'built-in' RNG providers ([Random Number Generator](https://en.wikipedia.org/wiki/Random_number_generation)). The RNG provider generates a number of random bytes and returns these bytes as a string. These values are then used to create the secret. By default (no RNG provider specified) TwoFactorAuth will try to determine the best available RNG provider to use. It will, be default, try to use the [`MCryptRNGProvider`](lib/Providers/Rng/MCryptRNGProvider.php), if this is not available/supported for any reason it will try to use the [`OpenSSLRNGProvider`](lib/Providers/Rng/OpenSSLRNGProvider.php) and if that is also not available/supported it will try to use the final RNG provider: [`HashRNGProvider`](lib/Providers/Rng/HashRNGProvider.php). Each of these providers use their own method of generating a random sequence of bytes. The first two (`OpenSSLRNGProvider` and `MCryptRNGProvider`) return a [cryptographically secure](https://en.wikipedia.org/wiki/Cryptographically_secure_pseudorandom_number_generator) sequence of random bytes whereas the `HashRNGProvider` returns a **non-cryptographically secure** sequence.
This class also comes with three 'built-in' RNG providers ([Random Number Generator](https://en.wikipedia.org/wiki/Random_number_generation)). The RNG provider generates a number of random bytes and returns these bytes as a string. These values are then used to create the secret. By default (no RNG provider specified) TwoFactorAuth will try to determine the best available RNG provider to use. It will, by default, try to use the [`MCryptRNGProvider`](lib/Providers/Rng/MCryptRNGProvider.php), if this is not available/supported for any reason it will try to use the [`OpenSSLRNGProvider`](lib/Providers/Rng/OpenSSLRNGProvider.php) and if that is also not available/supported it will try to use the final RNG provider: [`HashRNGProvider`](lib/Providers/Rng/HashRNGProvider.php). Each of these providers use their own method of generating a random sequence of bytes. The first two (`OpenSSLRNGProvider` and `MCryptRNGProvider`) return a [cryptographically secure](https://en.wikipedia.org/wiki/Cryptographically_secure_pseudorandom_number_generator) sequence of random bytes whereas the `HashRNGProvider` returns a **non-cryptographically secure** sequence.
You can easily implement your own `RNGProvider` by simply implementing the `IRNGProvider` interface. Each of the 'built-in' RNG providers have some constructor parameters that allow you to 'tweak' some of the settings to use when creating the random bytes such as which source to use (`MCryptRNGProvider`) or which hashing algorithm (`HashRNGProvider`). I encourage you to have a look at some of the ['built-in' RNG providers](lib/Providers/Rng) for details and the [`IRNGProvider` interface](lib/Providers/Rng/IRNGProvider.php).
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "robthree/twofactorauth",
"description": "Two Factor Authentication",
"version": "1.0",
"version": "1.1",
"type": "library",
"keywords": [ "Authentication", "Two Factor Authentication", "Multi Factor Authentication", "TFA", "MFA", "PHP", "Authenticator", "Authy" ],
"homepage": "https://github.com/RobThree/TwoFactorAuth",
-1
View File
@@ -6,7 +6,6 @@
<body>
<ol>
<?php
error_reporting(-1);
require_once 'loader.php';
Loader::register('../lib','RobThree\\Auth');
+4 -4
View File
@@ -8,9 +8,9 @@ abstract class BaseHTTPQRCodeProvider implements IQRCodeProvider
protected function getContent($url)
{
$ch = curl_init();
$curlhandle = curl_init();
curl_setopt_array($ch, array(
curl_setopt_array($curlhandle, array(
CURLOPT_URL => $url,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_MAXREDIRS => 3,
@@ -21,9 +21,9 @@ abstract class BaseHTTPQRCodeProvider implements IQRCodeProvider
CURLOPT_SSL_VERIFYPEER => $this->verifyssl,
CURLOPT_USERAGENT => 'TwoFactorAuth'
));
$data = curl_exec($ch);
$data = curl_exec($curlhandle);
curl_close($ch);
curl_close($curlhandle);
return $data;
}
}
+40 -21
View File
@@ -87,11 +87,11 @@ class TwoFactorAuth
{
$secretkey = $this->base32Decode($secret);
$ts = "\0\0\0\0" . pack('N*', $this->getTimeSlice($this->getTime($time))); // Pack time into binary string
$hm = hash_hmac($this->algorithm, $ts, $secretkey, true); // Hash it with users secret key
$hashpart = substr($hm, ord(substr($hm, -1)) & 0x0F, 4); // Use last nibble of result as index/offset and grab 4 bytes of the result
$value = unpack('N', $hashpart); // Unpack binary value
$value = $value[1] & 0x7FFFFFFF; // Drop MSB, keep only 31 bits
$timestamp = "\0\0\0\0" . pack('N*', $this->getTimeSlice($this->getTime($time))); // Pack time into binary string
$hashhmac = hash_hmac($this->algorithm, $timestamp, $secretkey, true); // Hash it with users secret key
$hashpart = substr($hashhmac, ord(substr($hashhmac, -1)) & 0x0F, 4); // Use last nibble of result as index/offset and grab 4 bytes of the result
$value = unpack('N', $hashpart); // Unpack binary value
$value = $value[1] & 0x7FFFFFFF; // Drop MSB, keep only 31 bits
return str_pad($value % pow(10, $this->digits), $this->digits, '0', STR_PAD_LEFT);
}
@@ -101,13 +101,32 @@ class TwoFactorAuth
*/
public function verifyCode($secret, $code, $discrepancy = 1, $time = null)
{
$t = $this->getTime($time);
for ($i = -$discrepancy; $i <= $discrepancy; $i++)
{
if (strcmp($this->getCode($secret, $t + ($i * $this->period)), $code) === 0)
return true;
}
$result = false;
$timetamp = $this->getTime($time);
// To keep safe from timing-attachs we iterate *all* possible codes even though we already may have verified a code is correct
for ($i = -$discrepancy; $i <= $discrepancy; $i++)
$result |= $this->codeEquals($this->getCode($secret, $timetamp + ($i * $this->period)), $code);
return (bool)$result;
}
/**
* Timing-attack safe comparison of 2 codes (see http://blog.ircmaxell.com/2014/11/its-all-about-time.html)
*/
private function codeEquals($safe, $user) {
if (function_exists('hash_equals')) {
return hash_equals($safe, $user);
} else {
// In general, it's not possible to prevent length leaks. So it's OK to leak the length. The important part is that
// we don't leak information about the difference of the two strings.
if (strlen($safe)===strlen($user)) {
$result = 0;
for ($i = 0; $i < strlen($safe); $i++)
$result |= (ord($safe[$i]) ^ ord($user[$i]));
return $result === 0;
}
}
return false;
}
@@ -155,19 +174,19 @@ class TwoFactorAuth
if (preg_match('/[^'.preg_quote(self::$_base32dict).']/', $value) !== 0)
throw new TwoFactorAuthException('Invalid base32 string');
$s = '';
foreach (str_split($value) as $c)
$buffer = '';
foreach (str_split($value) as $char)
{
if ($c !== '=')
$s .= str_pad(decbin(self::$_base32lookup[$c]), 5, 0, STR_PAD_LEFT);
if ($char !== '=')
$buffer .= str_pad(decbin(self::$_base32lookup[$char]), 5, 0, STR_PAD_LEFT);
}
$l = strlen($s);
$r = trim(chunk_split(substr($s, 0, $l - ($l % 8)), 8, ' '));
$length = strlen($buffer);
$blocks = trim(chunk_split(substr($buffer, 0, $length - ($length % 8)), 8, ' '));
$o = '';
foreach (explode(' ', $r) as $b)
$o .= chr(bindec(str_pad($b, 8, 0, STR_PAD_RIGHT)));
$output = '';
foreach (explode(' ', $blocks) as $block)
$output .= chr(bindec(str_pad($block, 8, 0, STR_PAD_RIGHT)));
return $o;
return $output;
}
}