Spomky-Labs / otphp

:closed_lock_with_key: A PHP library for generating one time passwords according to RFC 4226 (HOTP) and the RFC 6238 (TOTP)
MIT License
1.3k stars 148 forks source link

TOTP verification returning always false #230

Open programarivm opened 2 weeks ago

programarivm commented 2 weeks ago

Description

Hi there,

I'm using the following sample script to simulate a user sign up.

<?php

// sign_up.php

require_once __DIR__ . '/../vendor/autoload.php';

use OTPHP\InternalClock;
use OTPHP\TOTP;

$secret = 'JDDK4U6G3BJLEZ7Y';

$otp = TOTP::createFromSecret($secret, new InternalClock());
$otp->setPeriod(20);
$otp->setDigest('sha512');
$otp->setDigits(8);
$otp->setEpoch(100);
$otp->setLabel('alice@foo.bar');
$otp->setIssuer('Example');

$grCodeUri = $otp->getQrCodeUri(
    'https://api.qrserver.com/v1/create-qr-code/?data=[DATA]&size=300x300&ecc=M',
    '[DATA]'
);

echo $grCodeUri . PHP_EOL;

So far so good. The generated $grCodeUri is then scanned with Authy which seems to be working okay in that Authy is generating the TOTP codes. However, I can't manage to verify any of the codes by Authy if using the script below.

<?php

// sign_in.php

require_once __DIR__ . '/../vendor/autoload.php';

use OTPHP\InternalClock;
use OTPHP\TOTP;

$secret = 'JDDK4U6G3BJLEZ7Y';

$otp = TOTP::createFromSecret($secret, new InternalClock());

$verified = $otp->verify($argv[1], null, 5);

echo (int) $verified . PHP_EOL;

Could you please elaborate on this example?

Thanks for the help,

Spomky commented 2 weeks ago

Hi,

The verification script creates a TOTP object, but other parameters are not set. It should be as follows:

<?php

// sign_in.php

require_once __DIR__ . '/../vendor/autoload.php';

use OTPHP\InternalClock;
use OTPHP\TOTP;

$secret = 'JDDK4U6G3BJLEZ7Y';

$otp = TOTP::createFromSecret($secret, new InternalClock());
$otp->setPeriod(20);
$otp->setDigest('sha512');
$otp->setDigits(8);
$otp->setEpoch(100);

$verified = $otp->verify($argv[1], null, 5);

echo (int) $verified . PHP_EOL;

But it can also come from a lack of support for custom parameters. You need to make sure that Authy supports period, digest, digits and epoch with values ​​other than the default ones.

programarivm commented 2 weeks ago

Here is the elaboration of the example. At the moment I'm using FreeOTP to generate 9 digits passwords, and described below is a safe token with strong cryptographic parameters by Base32-encoding a 128 characters length password.

<?php

// sign_up.php

require_once __DIR__ . '/../vendor/autoload.php';

use OTPHP\InternalClock;
use OTPHP\TOTP;

$secret = 'IZ2COUCZFZMGYVRPKURGGSL3GVPTGZLNGZFCE3BZGAWWWZJHH5WUGMSNNJEC2QROKNTVQYZVK57HEKKQFZEDQZB7FUYCQPZ5L44GAXTPOFTWSILLJJHFAYD6N5ESK7LYKJIGUL3HIR2HW5DFMVGWGRTGFB3CUUBJJR3E4QRXJ45VYYJVONGVAOK6KUZTYUK6IAVXGOT4JF7C2===';

$otp = TOTP::createFromSecret($secret, new InternalClock());
$otp->setDigits(9);
$otp->setLabel('preciseKoala');
$otp->setIssuer('ChesslaBlab');
$otp->setParameter('image', 'https://chesslablab.org/logo.png');

$grCodeUri = $otp->getQrCodeUri(
    'https://api.qrserver.com/v1/create-qr-code/?data=[DATA]&size=300x300&ecc=M',
    '[DATA]'
);

echo $grCodeUri . PHP_EOL;
<?php

// sign_in.php

require_once __DIR__ . '/../vendor/autoload.php';

use OTPHP\InternalClock;
use OTPHP\TOTP;

$secret = 'IZ2COUCZFZMGYVRPKURGGSL3GVPTGZLNGZFCE3BZGAWWWZJHH5WUGMSNNJEC2QROKNTVQYZVK57HEKKQFZEDQZB7FUYCQPZ5L44GAXTPOFTWSILLJJHFAYD6N5ESK7LYKJIGUL3HIR2HW5DFMVGWGRTGFB3CUUBJJR3E4QRXJ45VYYJVONGVAOK6KUZTYUK6IAVXGOT4JF7C2===';

$otp = TOTP::createFromSecret($secret, new InternalClock());
$otp->setDigits(9);

$verified = $otp->verify($argv[1], null, 5);

echo (int) $verified . PHP_EOL;

At the moment this is in my humble opinion easier to implement than WebAuthn.

The added value is:

Maybe this basic passwordless authentication is just fine for some apps. Now preciseKoala can play chess with other users and see their results in the ranking.