To generate one-time passwords, each class needs at least the following parameters:
- A secret encoded in Base32
- A digest algorithm
- A number of digits
Depending on the type of OTP, you will need the following additional parameters:
- For TOTP: a period (and optionally an epoch)
- For HOTP: a counter
Since v11.4, OTPHP provides two ways to modify OTP objects:
These methods modify the OTP object in place and are useful for building OTP configurations step by step:
<?php
use OTPHP\TOTP;
use OTPHP\InternalClock;
$otp = TOTP::generate(new InternalClock());
$otp->setPeriod(60); // Modifies the object
$otp->setDigest('sha256'); // Modifies the object
$otp->setDigits(8); // Modifies the object
$otp->setLabel('alice@foo.bar'); // Modifies the object
$otp->setIssuer('My Service'); // Modifies the object
// $otp is now configured with all the above settingsThese methods return a new OTP object with the modified parameter, leaving the original unchanged. This is useful for readonly classes and functional programming styles:
<?php
use OTPHP\TOTP;
use OTPHP\InternalClock;
$baseOtp = TOTP::generate(new InternalClock());
$customOtp = $baseOtp
->withPeriod(60)
->withDigest('sha256')
->withDigits(8)
->withLabel('alice@foo.bar')
->withIssuer('My Service');
// $baseOtp remains unchanged with default settings
// $customOtp is a new object with the custom settingsAvailable immutable methods:
withSecret(string $secret): selfwithLabel(string $label): selfwithIssuer(string $issuer): selfwithDigits(int $digits): selfwithDigest(string $digest): selfwithParameter(string $parameter, mixed $value): selfwithIssuerIncludedAsParameter(bool $issuerIncludedAsParameter): self- TOTP specific:
withPeriod(int $period): self,withEpoch(int $epoch): self - HOTP specific:
withCounter(int $counter): self
By default, a 512 bits secret is generated. If you need, you can use your own secret:
<?php
use OTPHP\TOTP;
use ParagonIE\ConstantTime\Base32;
$mySecret = trim(Base32::encodeUpper(random_bytes(128)), '='); // We generate our own 1024 bits secret
$otp = TOTP::createFromSecret($mySecret);You can also specify a custom secret size when generating a new OTP:
<?php
use OTPHP\TOTP;
use OTPHP\InternalClock;
// Generate with custom secret size (in bytes)
// 16 bytes = 26 base32 characters, 32 bytes = 52 base32 characters, etc.
$otp = TOTP::generate(new InternalClock(), 16);
echo "Secret: " . $otp->getSecret(); // Will be 26 characters longPlease note that the trailing = are automatically removed by the library.
By default, the period for a TOTP is 30 seconds and the counter for a HOTP is 0.
<?php
use OTPHP\TOTP;
use OTPHP\HOTP;
$otp = TOTP::generate();
$otp = $otp->withPeriod(10); // The period is now 10 seconds
$otp = HOTP::generate();
$otp = $otp->withCounter(1000); // The counter is now 1000. We recommend you start at `0`, but you can set any value (at least 0)By default the digest algorithm is sha1.
You can use any algorithm listed by hash_algos().
Note that most applications only support md5, sha1, sha256 and sha512.
You must verify that the algorithm you want to use is supported by the application your clients might be using.
SHA-2 algorithms are recommended.
<?php
use OTPHP\TOTP;
$totp = TOTP::generate();
$totp = $totp->withPeriod(30) // The period (30 seconds)
->withDigest('ripemd160'); // The digest algorithmBy default the number of digits is 6. You can decide to use more (or less) digits. More than 10 may be difficult to use by the owner.
<?php
use OTPHP\TOTP;
$totp = TOTP::generate();
$totp = $totp->withPeriod(30) // The period (30 seconds)
->withDigest('sha1') // The digest algorithm
->withDigits(8); // The output will generate 8 digitsBy default, the epoch for a TOTP is 0.
The epoch is equivalent to the T0 parameter in RFC 6238.
This parameter basically determines at which timestamp (epoch) to start counting. It is useful in scenarios where
you need an exact period to verify passwords in. The epoch can be shared by client and server to specify the exact
timestamp at which the password was created so that you can reuse it for exact verification.
CAUTION: If you follow this approach and share the epoch as password creation timestamp, you should use dynamic secrets that are different each time, otherwise you will most likely always produce the same passwords. You could for example encode the timestamp in the secret to make it different each time.
<?php
use OTPHP\TOTP;
// Without epoch
$otp = TOTP::generate();
$otp = $otp->withPeriod(5) // The period (5 seconds)
->withDigest('sha1') // The digest algorithm
->withDigits(6); // The output will generate 6 digits
$password = $otp->at(1519401289); // Current period is: 1519401285 - 1519401289
$otp->verify($password, 1519401289); // Second 1: true
$otp->verify($password, 1519401290); // Second 2: false
// With epoch
$otp = TOTP::generate();
$otp = $otp->withPeriod(5) // The period (30 seconds)
->withDigest('sha1') // The digest algorithm
->withDigits(6) // The output will generate 8 digits
->withEpoch(1519401289); // The epoch is now 02/23/2018 @ 3:54:49pm (UTC)
$password = $otp->at(1519401289); // Current period is: 1519401289 - 1519401293
$otp->verify($password, 1519401289); // Second 1: true
$otp->verify($password, 1519401290); // Second 2: true
$otp->verify($password, 1519401291); // Second 3: true
$otp->verify($password, 1519401292); // Second 4: true
$otp->verify($password, 1519401293); // Second 5: true
$otp->verify($password, 1519401294); // Second 6: falseOTP objects are able to support custom parameters.
These parameters are available in the provisioning URI or from the method getParameter.
<?php
use OTPHP\TOTP;
$totp = TOTP::createFromSecret('JBSWY3DPEHPK3PXP'); // New TOTP
$totp = $totp->withLabel('alice@google.com') // The label
->withParameter('foo', 'bar');
$totp->getProvisioningUri(); // Will return otpauth://totp/alice%40google.com?secret=JBSWY3DPEHPK3PXP&foo=barAs a user may have multiple OTP using the same label (e.g. the user email), it is useful to set the issuer parameter to identify the service that provided the OTP.
<?php
use OTPHP\TOTP;
$totp = TOTP::createFromSecret('JBSWY3DPEHPK3PXP'); // New TOTP with custom secret
$totp = $totp->withLabel('alice@google.com') // The label (string)
->withIssuer('My Service');By default and to be compatible with Google Authenticator, the issuer is set in the query parameters and as the label prefix.
<?php
echo $totp->getProvisioningUri(); // Will return otpauth://totp/My%20Service%3Aalice%40google.com?issuer=My%20Service&secret=JBSWY3DPEHPK3PXPIf you do not want to get the issuer as a query parameter, you can remove it by using the method withIssuerIncludedAsParameter(bool).
<?php
$totp = $totp->withIssuerIncludedAsParameter(false);
echo $totp->getProvisioningUri(); // Will return otpauth://totp/My%20Service%3Aalice%40google.com?secret=JBSWY3DPEHPK3PXPSome applications such as FreeOTP can load images from an URI (image parameter).
Please note that at the moment, we cannot list applications that support this parameter.
<?php
use OTPHP\TOTP;
$totp = TOTP::createFromSecret('JBSWY3DPEHPK3PXP'); // New TOTP with custom secret
$totp = $totp->withLabel('alice@google.com') // The label (string)
->withParameter('image', 'https://foo.bar/otp.png');
$totp->getProvisioningUri(); // Will return otpauth://totp/alice%40google.com?secret=JBSWY3DPEHPK3PXP&image=https%3A%2F%2Ffoo.bar%2Fotp.pngWhen you load a QR Code using this input data, a compatible application will try to load the image at https://foo.bar/otp.png.