mirror of
https://github.com/RobThree/TwoFactorAuth.git
synced 2026-08-20 20:52:54 +00:00
Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f546c73537 | |||
| c94fcd83d8 | |||
| 856167c950 | |||
| 60c4ec7fb4 | |||
| c8ec3f624f | |||
| 80f00338a2 | |||
| 53a184f85c | |||
| c3c762a8f3 | |||
| 69c1597c71 | |||
| d1878f2e5c | |||
| e837caa9ce | |||
| 0332a61b67 | |||
| cff5dadb79 | |||
| 1647e46ee3 |
@@ -1,6 +1,6 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Rob Janssen
|
||||
Copyright (c) 2014-2015 Rob Janssen
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#  TwoFactorAuth class for PHP
|
||||
#  PHP library for Two Factor Authentication
|
||||
|
||||
[](https://travis-ci.org/RobThree/TwoFactorAuth/) [](https://packagist.org/packages/robthree/twofactorauth) [](LICENSE) [](https://packagist.org/packages/robthree/twofactorauth) [](http://hhvm.h4cc.de/package/robthree/twofactorauth) [](https://codeclimate.com/github/RobThree/TwoFactorAuth) [](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)'.
|
||||
PHP library 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)'. There's a [.Net implementation](https://github.com/RobThree/TwoFactorAuth.Net) of this library as well.
|
||||
|
||||
<p align="center">
|
||||
<img src="https://raw.githubusercontent.com/RobThree/TwoFactorAuth/master/multifactorauthforeveryone.png">
|
||||
@@ -10,7 +10,7 @@ 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.
|
||||
|
||||
@@ -48,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();
|
||||
@@ -81,7 +81,7 @@ When outputting a QR-code you can choose a `$label` for the user (which, when en
|
||||
|
||||
### Step 2: Verify secret shared key
|
||||
|
||||
When the code is added to the app, the app will be ready to start generating codes which 'expire' each '`$period`' number of seconds. To make sure the code was entered, or scanned, correctly you need to verify this by having the user enter a generated code. To check if the generated code is valid you call the `verifyCode()` method:
|
||||
When the shared secret is added to the app, the app will be ready to start generating codes which 'expire' each '`$period`' number of seconds. To make sure the secret was entered, or scanned, correctly you need to verify this by having the user enter a generated code. To check if the generated code is valid you call the `verifyCode()` method:
|
||||
|
||||
````php
|
||||
// Verify code
|
||||
@@ -109,9 +109,9 @@ verifyCode($secret, $code, $discrepancy = 1, $time = null)
|
||||
|
||||
### QR-code providers
|
||||
|
||||
As mentioned before, this class comes with three 'built-in' QR-code providers. This chapter will touch the subject a bit but most of it should be self-explanatory. The `TwoFactorAuth`-class accepts a `$qrcodeprovider` parameter which lets you specify a built-in or custom QR-code provider. All three built-in providers do a simple HTTP request to retrieve an image using cURL and implement the [`IQRCodeProvider`](lib/Providers/Qr/IQRCodeProvider.php) interface which is all you need to implement to write your own QR-code provider.
|
||||
As mentioned before, this library comes with three 'built-in' QR-code providers. This chapter will touch the subject a bit but most of it should be self-explanatory. The `TwoFactorAuth`-class accepts a `$qrcodeprovider` parameter which lets you specify a built-in or custom QR-code provider. All three built-in providers do a simple HTTP request to retrieve an image using cURL and implement the [`IQRCodeProvider`](lib/Providers/Qr/IQRCodeProvider.php) interface which is all you need to implement to write your own QR-code provider.
|
||||
|
||||
The default provider is the [`GoogleQRCodeProvider`](lib/Providers/Qr/GoogleQRCodeProvider.php) which uses the [Google Chart Tools](https://developers.google.com/chart/infographics/docs/qr_codes) to render QR-codes. Then we have the [`QRServerProvider`](lib/Providers/Qr/QRServerProvider.php) which uses the [goqr.me API](http://goqr.me/api/doc/create-qr-code/) and finally we have the [`QRicketProvider`](lib/Providers/Qr/QRicketProvider.php) which uses the [QRickit API](http://qrickit.com/qrickit_apps/qrickit_api.php). All three inherit from a common (abstract) base-class named [`BaseHTTPQRCodeProvider`](lib/Providers/Qr/BaseHTTPQRCodeProvider.php) because all three share the same functionality: retrieve an image from a 3rd party over HTTP. All three classes have constructors that allow you to tweak some settings and most, if not all, arguments should speak for themselves. If you're not sure which values are supported, click the links in this paragraph for documentation on the API's that are utilized by these classes.
|
||||
The default provider is the [`GoogleQRCodeProvider`](lib/Providers/Qr/GoogleQRCodeProvider.php) which uses the [Google Chart Tools](https://developers.google.com/chart/infographics/docs/qr_codes) to render QR-codes. Then we have the [`QRServerProvider`](lib/Providers/Qr/QRServerProvider.php) which uses the [goqr.me API](http://goqr.me/api/doc/create-qr-code/) and finally we have the [`QRicketProvider`](lib/Providers/Qr/QRicketProvider.php) which uses the [QRickit API](http://qrickit.com/qrickit_apps/qrickit_api.php). All three inherit from a common (abstract) baseclass named [`BaseHTTPQRCodeProvider`](lib/Providers/Qr/BaseHTTPQRCodeProvider.php) because all three share the same functionality: retrieve an image from a 3rd party over HTTP. All three classes have constructors that allow you to tweak some settings and most, if not all, arguments should speak for themselves. If you're not sure which values are supported, click the links in this paragraph for documentation on the API's that are utilized by these classes.
|
||||
|
||||
If you don't like any of the built-in classes because you don't want to rely on external resources for example or because you're paranoid about sending the TOTP secret to these 3rd parties (which is useless to them since they miss *at least one* other factor in the [MFA process](http://en.wikipedia.org/wiki/Multi-factor_authentication)), feel tree to implement your own. The `IQRCodeProvider` interface couldn't be any simpler. All you need to do is implement 2 methods:
|
||||
|
||||
@@ -166,12 +166,12 @@ 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 library 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).
|
||||
|
||||
## License
|
||||
|
||||
Licensed under MIT license. See LICENSE file for details.
|
||||
Licensed under MIT license. See [LICENSE](https://raw.githubusercontent.com/RobThree/TwoFactorAuth/master/LICENSE) for details.
|
||||
|
||||
[Logo / icon](http://www.iconmay.com/Simple/Travel_and_Tourism_Part_2/luggage_lock_safety_baggage_keys_cylinder_lock_hotel_travel_tourism_luggage_lock_icon_465) under CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
|
||||
[Logo / icon](http://www.iconmay.com/Simple/Travel_and_Tourism_Part_2/luggage_lock_safety_baggage_keys_cylinder_lock_hotel_travel_tourism_luggage_lock_icon_465) under CC0 1.0 Universal (CC0 1.0) Public Domain Dedication ([Archived page](http://riii.nl/tm7ap))
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
<Compile Include="lib\Providers\Qr\QRException.php" />
|
||||
<Compile Include="lib\Providers\Qr\QRicketProvider.php" />
|
||||
<Compile Include="lib\Providers\Qr\QRServerProvider.php" />
|
||||
<Compile Include="lib\Providers\Rng\CSRNGProvider.php" />
|
||||
<Compile Include="lib\Providers\Rng\IRNGProvider.php" />
|
||||
<Compile Include="lib\Providers\Rng\MCryptRNGProvider.php" />
|
||||
<Compile Include="lib\Providers\Rng\OpenSSLRNGProvider.php" />
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "robthree/twofactorauth",
|
||||
"description": "Two Factor Authentication",
|
||||
"version": "1.1",
|
||||
"version": "1.4",
|
||||
"type": "library",
|
||||
"keywords": [ "Authentication", "Two Factor Authentication", "Multi Factor Authentication", "TFA", "MFA", "PHP", "Authenticator", "Authy" ],
|
||||
"homepage": "https://github.com/RobThree/TwoFactorAuth",
|
||||
|
||||
@@ -43,6 +43,7 @@ class QRServerProvider extends BaseHTTPQRCodeProvider
|
||||
case 'eps':
|
||||
return 'application/postscript';
|
||||
}
|
||||
throw new QRException(sprintf('Unknown MIME-type: %s', $this->format));
|
||||
}
|
||||
|
||||
public function getQRCodeImage($qrtext, $size)
|
||||
|
||||
@@ -33,6 +33,7 @@ class QRicketProvider extends BaseHTTPQRCodeProvider
|
||||
case 'j':
|
||||
return 'image/jpeg';
|
||||
}
|
||||
throw new QRException(sprintf('Unknown MIME-type: %s', $this->format));
|
||||
}
|
||||
|
||||
public function getQRCodeImage($qrtext, $size)
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace RobThree\Auth\Providers\Rng;
|
||||
|
||||
class CSRNGProvider implements IRNGProvider
|
||||
{
|
||||
public function getRandomBytes($bytecount) {
|
||||
return random_bytes($bytecount); // PHP7+
|
||||
}
|
||||
|
||||
public function isCryptographicallySecure() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -45,7 +45,9 @@ class TwoFactorAuth
|
||||
|
||||
// Try to find best available RNG provider if none was specified
|
||||
if ($rngprovider==null) {
|
||||
if (function_exists('mcrypt_create_iv')) {
|
||||
if (function_exists('random_bytes')) {
|
||||
$rngprovider = new Providers\Rng\CSRNGProvider();
|
||||
} elseif (function_exists('mcrypt_create_iv')) {
|
||||
$rngprovider = new Providers\Rng\MCryptRNGProvider();
|
||||
} elseif (function_exists('openssl_random_pseudo_bytes')) {
|
||||
$rngprovider = new Providers\Rng\OpenSSLRNGProvider();
|
||||
@@ -108,7 +110,7 @@ class TwoFactorAuth
|
||||
for ($i = -$discrepancy; $i <= $discrepancy; $i++)
|
||||
$result |= $this->codeEquals($this->getCode($secret, $timetamp + ($i * $this->period)), $code);
|
||||
|
||||
return $result;
|
||||
return (bool)$result;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -8,6 +8,7 @@ require_once 'lib/Providers/Qr/GoogleQRCodeProvider.php';
|
||||
|
||||
require_once 'lib/Providers/Rng/IRNGProvider.php';
|
||||
require_once 'lib/Providers/Rng/RNGException.php';
|
||||
require_once 'lib/Providers/Rng/CSRNGProvider.php';
|
||||
require_once 'lib/Providers/Rng/MCryptRNGProvider.php';
|
||||
require_once 'lib/Providers/Rng/OpenSSLRNGProvider.php';
|
||||
require_once 'lib/Providers/Rng/HashRNGProvider.php';
|
||||
@@ -24,7 +25,7 @@ class TwoFactorAuthTest extends PHPUnit_Framework_TestCase
|
||||
*/
|
||||
public function testConstructorThrowsOnInvalidDigits() {
|
||||
|
||||
$tfa = new TwoFactorAuth('Test', 0);
|
||||
new TwoFactorAuth('Test', 0);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -32,7 +33,7 @@ class TwoFactorAuthTest extends PHPUnit_Framework_TestCase
|
||||
*/
|
||||
public function testConstructorThrowsOnInvalidPeriod() {
|
||||
|
||||
$tfa = new TwoFactorAuth('Test', 6, 0);
|
||||
new TwoFactorAuth('Test', 6, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -40,7 +41,7 @@ class TwoFactorAuthTest extends PHPUnit_Framework_TestCase
|
||||
*/
|
||||
public function testConstructorThrowsOnInvalidAlgorithm() {
|
||||
|
||||
$tfa = new TwoFactorAuth('Test', 6, 30, 'xxx');
|
||||
new TwoFactorAuth('Test', 6, 30, 'xxx');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -48,7 +49,7 @@ class TwoFactorAuthTest extends PHPUnit_Framework_TestCase
|
||||
*/
|
||||
public function testConstructorThrowsOnQrProviderNotImplementingInterface() {
|
||||
|
||||
$tfa = new TwoFactorAuth('Test', 6, 30, 'sha1', new stdClass());
|
||||
new TwoFactorAuth('Test', 6, 30, 'sha1', new stdClass());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -56,7 +57,7 @@ class TwoFactorAuthTest extends PHPUnit_Framework_TestCase
|
||||
*/
|
||||
public function testConstructorThrowsOnRngProviderNotImplementingInterface() {
|
||||
|
||||
$tfa = new TwoFactorAuth('Test', 6, 30, 'sha1', null, new stdClass());
|
||||
new TwoFactorAuth('Test', 6, 30, 'sha1', null, new stdClass());
|
||||
}
|
||||
|
||||
public function testGetCodeReturnsCorrectResults() {
|
||||
@@ -147,7 +148,7 @@ class TwoFactorAuthTest extends PHPUnit_Framework_TestCase
|
||||
*/
|
||||
public function testGetCodeThrowsOnInvalidBase32String1() {
|
||||
$tfa = new TwoFactorAuth('Test');
|
||||
$result = $tfa->getCode('FOO1BAR8BAZ9'); //1, 8 & 9 are invalid chars
|
||||
$tfa->getCode('FOO1BAR8BAZ9'); //1, 8 & 9 are invalid chars
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -155,7 +156,7 @@ class TwoFactorAuthTest extends PHPUnit_Framework_TestCase
|
||||
*/
|
||||
public function testGetCodeThrowsOnInvalidBase32String2() {
|
||||
$tfa = new TwoFactorAuth('Test');
|
||||
$result = $tfa->getCode('mzxw6==='); //Lowercase
|
||||
$tfa->getCode('mzxw6==='); //Lowercase
|
||||
}
|
||||
|
||||
public function testKnownBase32DecodeTestVectors() {
|
||||
@@ -242,7 +243,62 @@ class TwoFactorAuthTest extends PHPUnit_Framework_TestCase
|
||||
$this->assertEquals('47863826', $tfa->getCode($secret, 20000000000));
|
||||
}
|
||||
|
||||
/**
|
||||
* @requires function random_bytes
|
||||
*/
|
||||
public function testCSRNGProvidersReturnExpectedNumberOfBytes() {
|
||||
$rng = new \RobThree\Auth\Providers\Rng\CSRNGProvider();
|
||||
foreach ($this->getRngTestLengths() as $l)
|
||||
$this->assertEquals($l, strlen($rng->getRandomBytes($l)));
|
||||
$this->assertEquals(true, $rng->isCryptographicallySecure());
|
||||
}
|
||||
|
||||
/**
|
||||
* @requires function hash_algos
|
||||
* @requires function hash
|
||||
*/
|
||||
public function testHashRNGProvidersReturnExpectedNumberOfBytes() {
|
||||
$rng = new \RobThree\Auth\Providers\Rng\HashRNGProvider();
|
||||
foreach ($this->getRngTestLengths() as $l)
|
||||
$this->assertEquals($l, strlen($rng->getRandomBytes($l)));
|
||||
$this->assertEquals(false, $rng->isCryptographicallySecure());
|
||||
}
|
||||
|
||||
/**
|
||||
* @requires function mcrypt_create_iv
|
||||
*/
|
||||
public function testMCryptRNGProvidersReturnExpectedNumberOfBytes() {
|
||||
$rng = new \RobThree\Auth\Providers\Rng\MCryptRNGProvider();
|
||||
foreach ($this->getRngTestLengths() as $l)
|
||||
$this->assertEquals($l, strlen($rng->getRandomBytes($l)));
|
||||
$this->assertEquals(true, $rng->isCryptographicallySecure());
|
||||
}
|
||||
|
||||
/**
|
||||
* @requires function openssl_random_pseudo_bytes
|
||||
*/
|
||||
public function testStrongOpenSSLRNGProvidersReturnExpectedNumberOfBytes() {
|
||||
$rng = new \RobThree\Auth\Providers\Rng\OpenSSLRNGProvider(true);
|
||||
foreach ($this->getRngTestLengths() as $l)
|
||||
$this->assertEquals($l, strlen($rng->getRandomBytes($l)));
|
||||
$this->assertEquals(true, $rng->isCryptographicallySecure());
|
||||
}
|
||||
|
||||
/**
|
||||
* @requires function openssl_random_pseudo_bytes
|
||||
*/
|
||||
public function testNonStrongOpenSSLRNGProvidersReturnExpectedNumberOfBytes() {
|
||||
$rng = new \RobThree\Auth\Providers\Rng\OpenSSLRNGProvider(false);
|
||||
foreach ($this->getRngTestLengths() as $l)
|
||||
$this->assertEquals($l, strlen($rng->getRandomBytes($l)));
|
||||
$this->assertEquals(false, $rng->isCryptographicallySecure());
|
||||
}
|
||||
|
||||
|
||||
private function getRngTestLengths() {
|
||||
return array(1, 16, 32, 256);
|
||||
}
|
||||
|
||||
private function DecodeDataUri($datauri) {
|
||||
if (preg_match('/data:(?P<mimetype>[\w\.\-\/]+);(?P<encoding>\w+),(?P<data>.*)/', $datauri, $m) === 1) {
|
||||
return array(
|
||||
|
||||
Reference in New Issue
Block a user