google2fa

repository·9.x·Indexed 24 days ago

https://github.com/antonioribeiro/google2fa

A PHP implementation of the Google Two-Factor Authentication module supporting HOTP (RFC 4226) and TOTP (RFC 6238) algorithms. The library provides functionality to generate Base32 secret keys, verify one-time passwords (OTP), handle clock drift via validation windows, and prevent replay attacks using verifyKeyNewer(). It supports SHA1, SHA256, and SHA512 HMAC algorithms and provides tools to generate provisioning URLs for QR code generators.

Tokens
2.7K
Snippets
10
Records
21
Agent score
84%

What's inside google2fa

  1. Install Google2FA via Composer

    9.x

    Install the core package using Composer:

    composer require pragmarx/google2fa

    Note: This package requires PHP 7.1 or greater. If you intend to generate inline QRCodes, you should also install a QR code generator like bacon/bacon-qr-code:

    composer require bacon/bacon-qr-code
  2. Migration guide for Version 9.0.0 breaking change

    9.x

    In version 9.0.0, the default secret key length increased from 16 to 32 characters to increase entropy from 80 bits to 160 bits.

    Impact Checklist:

    • Database schemas: Ensure google2fa_secret columns can accommodate 32 characters.
    • Validation rules: Update length validations that expect exactly 16 characters.
    • Tests: Update assertions expecting 16-character secrets.
    • UI components: Ensure fields and displays accommodate longer secrets.

    Existing 16-character secrets remain fully functional.

  3. Ensure server time synchronization

    9.x

    Since TOTP is time-based, it is critical that your server time is synchronized with an NTP server. On Ubuntu, you can synchronize time using the following commands:

    sudo service ntp stop
    sudo ntpd -gq
    sudo service ntp start
  4. Generate a QR Code URL

    9.x

    Google2FA does not generate images directly. Instead, use getQRCodeUrl() to generate the provisioning URL required by TOTP apps. You can then pass this URL to a QR code generation library (like BaconQrCode, Simple QrCode, or Endroid QR Code).

    $qrCodeUrl = $google2fa->getQRCodeUrl(
        $companyName,
        $companyEmail,
        $secretKey
    );
  5. Generate secret keys with custom length and prefixes

    9.x

    You can harden security by generating larger secret keys or adding a prefix.

    Key Length: In v9.0.0+, the default length is 32 bytes. You can specify a custom length (e.g., 16 bytes for v8.x compatibility).

    Prefixes: You can prefix secret keys, but the total length must remain a power of 2. Because prefixes are converted to base 32, the maximum prefix length is 10 bytes. Valid prefix sizes are 1, 2, 5, 10, 20, 40, 80....

    // Custom length
    $secretKey = $google2fa->generateSecretKey(32); 
    $secretKey = $google2fa->generateSecretKey(16); 
    
    // Using a prefix
    $prefix = strpad($userId, 10, 'X');
    $secretKey = $google2fa->generateSecretKey(16, $prefix);
  6. Ensure Google Authenticator compatibility

    9.x

    To be compatible with Google Authenticator, the secret key (when converted to base 32) must have a length that is a power of 2 (e.g., 8, 16, 32, 64...).

    By default, the package enforces this compatibility. You can disable this enforcement if you are targeting other types of apps using setEnforceGoogleAuthenticatorCompatibility(false).

  7. Handle clock drift with the validation window

    9.x

    To account for users whose device clocks are slightly out of sync with your server, you can use a $window parameter when verifying a key. The $window defines how many 30-second intervals in the past and future should be considered valid.

    By default, the window is 1, meaning the system accepts the current key, one previous key, and one future key (effectively making the key valid for 60 seconds).

    Warning: Setting $window to 0 may cause verification to fail if the user takes time to type the code into the form after seeing it in their generator app.

    $secret = $request->input('secret');
    
    $window = 8; // 8 keys (respectively 4 minutes) past and future
    
    $valid = $google2fa->verifyKey($user->google2fa_secret, $secret, $window);
  8. Configure the OTP window and regeneration interval

    9.x

    You can adjust how long an OTP remains valid and how often the key regenerates.

    • Window: Defines how many cycles an OTP lasts. A window of 0 lasts 30 seconds; a window of 2 lasts 120 seconds. You can set this globally via setWindow() or per-call during verifyKey().
    • Key Regeneration Interval: Changes the default 30-second cycle. Note: Changing this may cause your app to become out of sync with standard apps like Google Authenticator.
  9. Generate a secret key

    9.x

    Use generateSecretKey() to create a new secret key for a user.

    Note on Version 9.0.0+: The default secret key length is now 32 characters (160 bits of entropy). If you require the legacy 16-character length (80 bits), you must pass 16 as an argument.

    // Generates a 32-character secret key (v9.0.0+ default)
    $secret = $google2fa->generateSecretKey();
    
    // Explicitly specify 16 characters for legacy compatibility
    $secret = $google2fa->generateSecretKey(16);
    $secret = $google2fa->generateSecretKey();
  10. Prevent replay attacks with verifyKeyNewer()

    9.x

    To prevent an attacker from reusing a one-time key that has already been submitted, use verifyKeyNewer(). This function checks the provided key against a stored timestamp to ensure the key hasn't been used in a previous cycle.

    If the key is valid and hasn't been used, it returns the Unix timestamp of the key divided by the regeneration period (usually 30). If the key is invalid or has already been used, it returns false.

    $secret = $request->input('secret');
    
    // $user->google2fa_ts should store the last successful timestamp
    $timestamp = $google2fa->verifyKeyNewer($user->google2fa_secret, $secret, $user->google2fa_ts);
    
    if ($timestamp !== false) {
        $user->update(['google2fa_ts' => $timestamp]);
        // successful
    } else {
        // failed
    }