Search tools...
security-tools

TOTP 2FA Generator — Complete Security Guide

Understand TOTP, RFC 6238, setup 2FA and protect your accounts from phishing and SIM swap

16 min readUpdated March 19, 2026security, 2FA, TOTP, authentication, cryptography, HMAC, RFC-6238

Passwords alone are dead. Data breaches expose billions of credentials every year, and credential stuffing attacks — where attackers try stolen password databases against other services — succeed because most people reuse passwords. Time-based One-Time Passwords (TOTP) solve this by generating a second factor that changes every 30 seconds, cryptographically derived from a shared secret and the current time. Even if an attacker steals your password, they can't log in without that ephemeral 6-digit code.

This guide covers everything: the mathematical foundations of HOTP and TOTP (HMAC-SHA1, base32, the 30-second time window), a practical comparison of all second-factor methods, setup instructions for major platforms, recovery planning for lost devices, real attack vectors against TOTP (phishing, real-time relay), and best practices for individuals and teams. Whether you're a developer building 2FA into your app or a professional securing your own accounts, this guide gives you the full picture.

Free Tool

Generate and Test TOTP Codes Now

Generate a secure TOTP secret, get a QR code to scan, and verify the 6-digit codes in real time. Test TOTP with SHA-1, SHA-256 or SHA-512. Free, no server storage, all processing in browser.

Open TOTP 2FA Generator

What is TOTP? How Time-Based One-Time Passwords Work

TOTP stands for Time-based One-Time Password. It is defined in RFC 6238 (2011), built as an extension of HOTP (RFC 4226). The core idea: generate a short numeric code using a shared secret key and the current time, such that both the server and the user's authenticator app independently produce the same code — without communicating.

The TOTP Algorithm Step by Step

import hmac
import hashlib
import struct
import time
import base64

def generate_totp(secret_base32: str, digits: int = 6, period: int = 30) -> str:
    """
    Generate a TOTP code per RFC 6238.
    secret_base32: The shared secret in Base32 encoding (from the QR code)
    digits: Length of OTP (default: 6)
    period: Time step in seconds (default: 30)
    """
    # Step 1: Decode the Base32 secret to raw bytes
    secret_bytes = base64.b32decode(secret_base32.upper())

    # Step 2: Calculate T — the time counter
    # T = floor(current_unix_time / time_step)
    T = int(time.time()) // period
    # Example: if time.time() = 1710835200 and period = 30
    # T = 57027840

    # Step 3: Pack T as an 8-byte big-endian unsigned integer
    T_bytes = struct.pack('>Q', T)

    # Step 4: Compute HMAC-SHA1
    # hmac_result = HMAC-SHA1(secret_bytes, T_bytes)
    hmac_result = hmac.new(secret_bytes, T_bytes, hashlib.sha1).digest()
    # Result: 20 bytes

    # Step 5: Dynamic Truncation
    # Use last nibble of hmac_result as offset
    offset = hmac_result[-1] & 0x0F
    # Extract 4 bytes at offset, mask top bit (to ensure positive int)
    code_int = struct.unpack('>I', hmac_result[offset:offset + 4])[0] & 0x7FFFFFFF

    # Step 6: Modulo reduction to get N digits
    otp = code_int % (10 ** digits)

    # Step 7: Zero-pad to required length
    return str(otp).zfill(digits)

# Usage:
secret = "JBSWY3DPEHPK3PXP"  # Example Base32 secret
print(generate_totp(secret))  # e.g., "847291"

Why SHA1 in 2026? Is It Safe?

TOTP uses HMAC-SHA1 (not raw SHA1). While SHA1 collision attacks exist, HMAC-SHA1 is not vulnerable to them — HMAC's security depends on the preimage resistance of the hash function, not collision resistance. HMAC-SHA1 with a 160-bit key remains computationally secure for TOTP's use case.

RFC 6238 also specifies TOTP-SHA256 and TOTP-SHA512 variants (used by some hardware keys). Most authenticator apps use HMAC-SHA1 for compatibility with the original RFC 4226 standard.

The Time Window and Validity

Current UNIX time: 1,710,835,246

T = floor(1,710,835,246 / 30) = 57,027,841

This T is valid from: 1,710,835,210 (57,027,841 × 30)
Until:               1,710,835,239 (57,027,842 × 30 - 1)

That's exactly 30 seconds.

Servers typically accept T-1 (previous window) and T+1 (next window)
to account for clock skew and human entry delays.
Total validity window: effectively 60-90 seconds.

The QR Code and otpauth URI

The QR code you scan contains an otpauth:// URI:

otpauth://totp/ToolsArena:alice@example.com?
  secret=JBSWY3DPEHPK3PXP&
  issuer=ToolsArena&
  algorithm=SHA1&
  digits=6&
  period=30

Fields:
  - type: totp (time-based) or hotp (counter-based)
  - label: "Issuer:Account" (shown in authenticator app)
  - secret: Base32-encoded shared secret (16–32 characters)
  - issuer: Service name (displayed in app)
  - algorithm: SHA1, SHA256 or SHA512 (default: SHA1)
  - digits: 6 or 8 (default: 6)
  - period: time step in seconds (default: 30)

TOTP vs SMS OTP vs Hardware Keys: Security Comparison

Not all second factors are equal. Understanding their differences helps you choose the right method for different threat models.

Method Mechanism Phishing Resistant SIM Swap Resistant Works Offline Recovery If Lost Security Level
SMS OTP Code sent to phone number No No (major weakness) Requires cellular signal Easy (new SIM) Weak
Email OTP Code sent to email No Yes No Via email recovery Weak-Medium
TOTP (RFC 6238) Shared secret + time Partial (real-time relay possible) Yes Yes (offline) Backup codes / re-enrollment Good
FIDO2/WebAuthn Passkey Public key cryptography + domain binding Yes (domain-bound) Yes Yes (device-local) Passkey sync (iCloud/Google) or secondary key Excellent
Hardware Key (FIDO2 — YubiKey) Physical device + public key crypto Yes (domain-bound) Yes Yes Backup hardware key Excellent
Push Notification (Duo, Okta) App approval on paired device Partial (MFA fatigue attacks) Yes Requires internet Admin recovery Good

Why SMS 2FA is Critically Weak

SIM Swap Attack Flow:
1. Attacker calls your carrier with social engineering
   ("I lost my phone, please move my number to this new SIM")
2. Carrier transfers your number to attacker's SIM
3. Attacker requests password reset → SMS goes to attacker's phone
4. Account fully compromised

High-profile SIM swap victims include:
- Jack Dorsey (Twitter/X CEO) — 2019
- Multiple crypto exchange CEOs — 2021-2023
- US FTC Chairman — 2023

SS7 attack: Even without SIM swap, flaws in the SS7 telephony
protocol allow nation-state actors and sophisticated criminals to
intercept SMS messages in transit.

TOTP vs Passkeys in 2026

Passkeys (FIDO2/WebAuthn) are the superior replacement for TOTP. They are phishing-resistant because they're cryptographically bound to the exact domain — a fake site cannot request your passkey for the real site. However, TOTP remains the most widely supported 2FA method, available on virtually every service. Use passkeys where available; use TOTP where passkeys aren't yet supported.

The HOTP/TOTP Standard: RFC 6238 Explained Without Jargon

Understanding the RFC helps you make informed decisions about authentication security and debug TOTP issues at their root.

The HOTP Foundation (RFC 4226)

HOTP (HMAC-based One-Time Password) is the precursor to TOTP. Instead of time, it uses a counter:

def generate_hotp(secret: bytes, counter: int, digits: int = 6) -> str:
    """HOTP per RFC 4226"""
    # Pack counter as 8-byte big-endian
    counter_bytes = struct.pack('>Q', counter)
    # HMAC-SHA1
    hmac_result = hmac.new(secret, counter_bytes, hashlib.sha1).digest()
    # Dynamic truncation
    offset = hmac_result[-1] & 0x0F
    code = struct.unpack('>I', hmac_result[offset:offset + 4])[0] & 0x7FFFFFFF
    return str(code % (10 ** digits)).zfill(digits)

# HOTP problem: counter must stay in sync between client and server.
# If user generates codes without using them, counter drifts.
# TOTP solves this by using time as the counter.

TOTP = HOTP with Time Counter (RFC 6238)

# TOTP is simply HOTP where counter = T
# T = floor((current_unix_time - T0) / X)
# T0 = Unix epoch (January 1, 1970 UTC) = 0
# X  = time step = 30 seconds (default)

# The key insight: both client and server know:
# 1. The shared secret (exchanged once, at setup)
# 2. The current time (synchronized via NTP)
# Therefore, both independently compute the same T → same TOTP code.

RFC 6238 Parameters

ParameterRFC DefaultCommon ValuesNotes
Hash algorithmHMAC-SHA1SHA1, SHA256, SHA512SHA1 is standard for most apps
Time step (X)30 seconds30s (universal), 60s (rare)30s balances usability and security
T0 (epoch)0 (UNIX epoch)Always 0Jan 1, 1970 00:00:00 UTC
Code digits66 (universal), 8 (higher security)8 digits used by some banks
Look-ahead window±1 time step1-2 stepsCompensates for clock skew
Secret length≥128 bits160 bits (Base32: 32 chars)Longer = more secure

Why 30 Seconds?

The 30-second window is a deliberate balance:

TOO SHORT (5-10s): User can't type the code before it expires.
  → Poor UX, high error rate, accessibility issues.

TOO LONG (120-300s): Attacker who intercepts the code via
  real-time phishing has more time to use it.
  → Larger attack window.

30 SECONDS: Comfortable for users to read and type,
  yet short enough that a relay attack requires
  near-real-time infrastructure to exploit.

Servers typically also accept T-1 (30s before) to handle:
  - Users who took a few seconds after the code refreshed
  - Minor clock skew between client and server (usually <1s with NTP)

Secret Key Generation and Storage

import secrets
import base64

# Generate a cryptographically random 160-bit secret
secret_bytes = secrets.token_bytes(20)  # 20 bytes = 160 bits

# Encode as Base32 for QR code / otpauth:// URI
secret_b32 = base64.b32encode(secret_bytes).decode('utf-8')
# Example: "JBSWY3DPEHPK3PXPJBSWY3DPEHPK3PXP"

# NEVER store secrets in plaintext in your database.
# Encrypt with AES-256-GCM using a key stored in:
#   - AWS KMS / Google Cloud KMS / Azure Key Vault
#   - HashiCorp Vault
#   - Hardware Security Module (HSM)

# Example: envelope encryption
import os
from cryptography.hazmat.primitives.ciphers.aead import AESGCM

def encrypt_totp_secret(secret_bytes: bytes, kek: bytes) -> bytes:
    """Encrypt TOTP secret with a Key Encryption Key"""
    aesgcm = AESGCM(kek)
    nonce = os.urandom(12)  # 96-bit nonce
    encrypted = aesgcm.encrypt(nonce, secret_bytes, None)
    return nonce + encrypted

Setting Up 2FA with TOTP: Step-by-Step for Common Platforms

Here is a practical setup guide for the most common platforms developers use, plus best practices for implementing TOTP in your own application.

Setting Up TOTP on GitHub

1. Go to Settings → Password and authentication → Two-factor authentication
2. Click "Enable two-factor authentication"
3. Select "Authenticator app"
4. Scan the QR code with your authenticator app:
   - Recommended: Aegis (Android, open source)
   - Recommended: Raivo OTP (iOS, open source)
   - Also common: Google Authenticator, Microsoft Authenticator, 1Password
5. Enter the 6-digit code from your app to verify
6. CRITICAL: Save your recovery codes in a password manager or secure offline location
   - GitHub shows 16 single-use recovery codes
   - Each code can only be used once
   - If you lose your authenticator AND all recovery codes, you lose account access

Setting Up TOTP on AWS IAM

1. IAM Console → Users → [Your User] → Security credentials
2. Multi-factor authentication → Assign MFA device
3. Select "Authenticator app"
4. AWS shows TWO consecutive QR codes — scan both into your app
   (This is unusual — AWS requires two sequential codes to verify time sync)
5. Enter Code 1 from the app, wait 30 seconds, enter Code 2
6. Note: AWS root account MUST have MFA — this is a non-negotiable security requirement

Implementing TOTP in Your Application (Node.js)

import * as speakeasy from 'speakeasy';
import * as QRCode from 'qrcode';

// 1. Generate secret for new user
async function setupTOTP(userId: string, userEmail: string) {
  const secret = speakeasy.generateSecret({
    name: `MyApp (${userEmail})`,
    issuer: 'MyApp',
    length: 20,  // 160 bits
  });

  // Generate QR code for scanning
  const qrCodeDataUrl = await QRCode.toDataURL(secret.otpauth_url);

  // Store the encrypted secret in your database
  // NEVER store secret.base32 in plaintext
  await db.users.update(userId, {
    totpSecretEncrypted: await encryptSecret(secret.base32),
    totpEnabled: false,  // Not enabled until verified
    totpSetupAt: null,
  });

  return {
    secret: secret.base32,  // Show to user once for manual entry
    qrCode: qrCodeDataUrl,  // User scans this
  };
}

// 2. Verify the code and enable TOTP
async function verifyAndEnableTOTP(userId: string, userCode: string): Promise {
  const user = await db.users.findById(userId);
  const secret = await decryptSecret(user.totpSecretEncrypted);

  const isValid = speakeasy.totp.verify({
    secret,
    encoding: 'base32',
    token: userCode,
    window: 1,  // Accept T-1 and T+1 for clock skew
  });

  if (isValid) {
    // Generate backup codes
    const backupCodes = generateBackupCodes(8);
    const hashedCodes = await Promise.all(backupCodes.map(hashCode));

    await db.users.update(userId, {
      totpEnabled: true,
      totpSetupAt: new Date(),
      backupCodesHashed: hashedCodes,
    });

    return { success: true, backupCodes };  // Show backup codes to user ONCE
  }

  return { success: false };
}

// 3. Validate TOTP at login
async function validateTOTP(userId: string, token: string): Promise {
  const user = await db.users.findById(userId);

  // Check if it's a backup code
  if (token.length === 8 && /^[A-Z0-9]+$/.test(token)) {
    return useBackupCode(userId, token);
  }

  const secret = await decryptSecret(user.totpSecretEncrypted);

  return speakeasy.totp.verify({
    secret,
    encoding: 'base32',
    token,
    window: 1,
  });
}

// 4. Generate cryptographically random backup codes
function generateBackupCodes(count: number): string[] {
  return Array.from({ length: count }, () =>
    crypto.randomBytes(4).toString('hex').toUpperCase()  // 8-char hex codes
  );
}

TOTP Apps Comparison

AppPlatformOpen SourceCloud BackupExportRecommended For
AegisAndroid onlyYes (GPL-3)Self-managed encryptedYesPrivacy-conscious users
Raivo OTPiOS onlyYes (MIT)iCloud (encrypted)YesiOS privacy users
BitwardenAll platformsYes (AGPL)Bitwarden cloud (encrypted)YesIntegrated password + 2FA management
1PasswordAll platformsNo1Password cloudYesTeams, business users
Google AuthenticatoriOS, AndroidNoGoogle account syncLimitedSimplicity (but lacks export)
Microsoft AuthenticatoriOS, AndroidNoMicrosoft accountLimitedMicrosoft/Azure environments
AuthyAll platformsNoAuthy cloud (encrypted)No (lock-in)Desktop users (has desktop app)

What Happens If You Lose Your TOTP Device: Recovery Planning

Device loss is the most common TOTP support ticket. Planning for it before it happens is critical — panicked recovery attempts are how mistakes happen.

Tier 1: Backup Codes (Must Have)

Every TOTP-supporting service provides one-time backup codes at setup. These are single-use codes that bypass TOTP entirely. They are your primary recovery mechanism.

Best practices for backup code storage:
✓ Print and store in a physical safe or safe deposit box
✓ Store in a password manager (Bitwarden, 1Password) as a secure note
✓ Keep multiple copies in geographically different locations
✓ Treat them with the same security as your password

Never:
✗ Screenshot and store on the same device as your authenticator
✗ Email to yourself
✗ Store in a cloud note (Notion, Google Docs) without encryption
✗ Save in plaintext on your computer

When using a backup code for recovery:
1. Use the code to log in
2. Immediately disable TOTP
3. Re-enroll TOTP on your new device
4. Generate and save new backup codes

Tier 2: Authenticator App with Cloud Backup

Authenticator apps with encrypted cloud backup:

Aegis (Android):
  - Enable backup: Settings → Backups → Enable automatic backups
  - Encrypted with password you set
  - Backup file stored on device / can sync to Google Drive, Nextcloud, etc.
  - Restore: Install Aegis → Restore from backup → Enter password

Raivo (iOS):
  - Backup to iCloud (end-to-end encrypted with Apple ID)
  - Restore: Install Raivo on new iOS device → iCloud auto-restores

Bitwarden:
  - TOTP secrets stored in Bitwarden vault (encrypted)
  - Accessible from any device with Bitwarden installed
  - If you lose your Bitwarden master password: emergency access contacts

WARNING: Google Authenticator's cloud sync (added 2023) does NOT
end-to-end encrypt your secrets when synced to Google account.
Google can technically access them. Use Aegis or Raivo for E2E encryption.

Tier 3: Multi-Device Enrollment

For critical accounts: enroll TOTP on 2 devices simultaneously.
At setup, scan the QR code with BOTH devices.

Device 1: Primary phone (Aegis/Raivo)
Device 2: Tablet, spare phone, or password manager (Bitwarden 2FA)
Device 3 (optional): Desktop app (Bitwarden, 1Password, Authy)

Both devices will generate the same code (same secret + same time).
If Device 1 is lost, Device 2 still works.

Account Recovery When All Else Fails

If you have no backup codes, no app backup, and no second device:

GitHub:
  - Contact GitHub Support with proof of identity
  - Process: email from registered address + repository contributions
  - Time: 1-7 business days

AWS:
  - Contact AWS Support
  - Provide account ID, root email, payment method details
  - For root MFA loss: Emergency MFA removal process (24-48h)

Google:
  - Account Recovery flow (accounts.google.com/signin/recovery)
  - Time: Usually same-day for accounts with recovery phone/email
  - Alternative: Recovery codes (printed when setting up 2FA)

Prevention lesson: If you cannot access an account with 2FA enabled
AND have no backup codes AND no app backup: the service must
manually verify your identity. This process can take days and
sometimes fails — your backup codes are more important than you think.

Security Risks of TOTP: Phishing, SIM Swap and Mitigation

TOTP is significantly more secure than password-only auth, but it has real vulnerabilities. Understanding them is the difference between false confidence and informed security.

Attack 1: Real-Time Phishing (Most Dangerous)

TOTP does NOT protect against real-time phishing — this is its most critical limitation.

Real-Time TOTP Relay Attack:

[Legitimate user] → [Attacker's fake site] → [Real site]
                                ↓
1. User visits fake login page (e.g., g00gle.com/login)
2. User enters username + password
3. Attacker relays credentials to real Google login
4. Google responds: "Enter your 2FA code"
5. Fake site shows: "Enter your 6-digit code"
6. User enters their TOTP code
7. Attacker relays code to Google within seconds
8. Attacker gains full session access

This attack works because:
- TOTP codes are valid for ~90 seconds (T-1 to T+1)
- The relay happens in real-time (automated)
- The user sees a convincing fake site with valid SSL cert

Real-world examples:
- Evilginx2: Open-source TOTP bypass framework
- Modlishka: Reverse proxy for real-time credential relay
- Adversary-in-the-Middle (AiTM) attacks phished 10,000+
  Office 365 users in 2022 (bypassing Microsoft Authenticator)

Attack 2: Malware on Device

If your device has malware, the attacker can:
1. Read the TOTP secret from the authenticator app's storage
   (on rooted/jailbroken devices or via root-level exploits)
2. Read the TOTP code from the screen/clipboard
3. Intercept the 2FA code in the browser

Mitigation:
- Never root/jailbreak your primary authenticator device
- Use an authenticator app that requires biometric/PIN to show codes
- Use hardware security keys (not affected by device-level malware)

Attack 3: Backup Code Theft

Backup codes stored insecurely:
- In browser history / autofill
- In unencrypted notes
- In email (phishable)
- In plaintext files on computer

Mitigation: Store in encrypted password manager. Print for offline copy.

Why Passkeys Are Phishing-Resistant But TOTP Isn't

FIDO2/WebAuthn cryptographic domain binding:

During passkey authentication:
  - The browser sends the exact origin (e.g., "https://accounts.google.com")
    as part of the signed challenge
  - The passkey is created for THAT SPECIFIC DOMAIN
  - A fake site (g00gle.com) has a DIFFERENT domain
  - The passkey REFUSES to authenticate for a different domain
  - Attack impossible, regardless of how convincing the fake site looks

TOTP has no domain binding:
  - The 6-digit code works on any site that accepts it
  - The relay attack works because TOTP has no domain awareness

MFA Fatigue (Push Notification Attack)

This doesn't apply to TOTP but is worth knowing:

MFA Fatigue Attack:
1. Attacker has the target's credentials (from data breach)
2. Attacker repeatedly attempts login, triggering push notifications
3. Attacker bombards target's phone with "Allow login?" notifications
4. Target, confused or annoyed, eventually taps "Allow"
5. Account compromised

Solution for push-based 2FA: Enable "number matching" (show a number
in the push notification that user must match to a number shown at
login) — this eliminates pure fatigue attacks.

Uber's 2022 breach used this exact technique.

TOTP Best Practices: Backup Codes, Multiple Devices and App Recommendations

Here is the complete operational security playbook for TOTP — for individuals securing personal accounts and developers building TOTP into products.

For Individual Users: Security Checklist

Before enabling TOTP:
  □ Choose an authenticator app (Aegis for Android, Raivo for iOS,
    Bitwarden for cross-platform)
  □ Understand where your backup codes will be stored (password manager)
  □ Have a plan for device loss (cloud backup or multi-device enrollment)

At TOTP setup:
  □ Scan QR code on primary device
  □ Optionally: scan same QR code on secondary device (spare phone, tablet)
  □ Verify the code before finalizing setup
  □ Download/write down ALL backup codes immediately
  □ Store backup codes in password manager + printed copy in safe location
  □ Verify backup codes are readable and not expired

Ongoing:
  □ Keep backup codes updated (regenerate after using one)
  □ Test recovery annually: verify backup codes still work
  □ Update authenticator app (security patches)
  □ Don't share TOTP codes via SMS/email/phone call — it's always fraud

For Developers: Implementation Best Practices

// 1. Secret generation — use cryptographically secure randomness
import { randomBytes } from 'crypto';
const secret = randomBytes(20).toString('base32-encoded');  // 160 bits

// 2. Secret storage — NEVER plaintext
// Encrypt with KMS-managed key, store ciphertext in DB

// 3. Rate limiting — prevent brute force on 6-digit codes
// 10^6 = 1,000,000 possible codes. With 30s window and 3 attempts/window:
// Brute force takes ~17,000 years on average at 1 attempt/min rate limit

const rateLimiter = rateLimit({
  windowMs: 30 * 1000,  // 30 seconds (one TOTP window)
  max: 3,               // 3 attempts per window
  message: 'Too many authentication attempts',
  skipSuccessfulRequests: true,
});

// 4. Token reuse prevention — mark used tokens
// A TOTP code used once should NOT be usable again within its window
async function markTokenUsed(userId: string, token: string, T: number) {
  const key = `totp:used:${userId}:${T}:${token}`;
  const existed = await redis.set(key, '1', 'EX', 90, 'NX');
  if (!existed) throw new Error('Token already used');
}

// 5. Backup codes — hash before storing
import { scrypt, randomBytes } from 'crypto';
import { promisify } from 'util';
const scryptAsync = promisify(scrypt);

async function hashBackupCode(code: string): Promise {
  const salt = randomBytes(16);
  const derived = await scryptAsync(code, salt, 64) as Buffer;
  return `${salt.toString('hex')}:${derived.toString('hex')}`;
}

// 6. Timing-safe comparison — prevent timing attacks
import { timingSafeEqual } from 'crypto';
function safeCompare(a: string, b: string): boolean {
  const bufA = Buffer.from(a, 'utf8');
  const bufB = Buffer.from(b, 'utf8');
  if (bufA.length !== bufB.length) return false;
  return timingSafeEqual(bufA, bufB);
}

// 7. Account lockout after repeated failures
// Lock TOTP verification after 10 consecutive failures
// Require backup code or account recovery to unlock

Organizational TOTP Policy

Recommended TOTP policy for developer teams / companies:

□ Mandate TOTP (or better: passkeys) for all accounts with
  production access (AWS, GitHub, GCP, Azure, Cloudflare, etc.)

□ Never allow SMS 2FA for production system access

□ Use hardware keys (YubiKey) for:
  - Domain registrar accounts
  - DNS provider accounts (Cloudflare, Route53)
  - Certificate authority accounts
  - Company email admin accounts

□ Store team backup codes in team password manager
  (1Password Teams, Bitwarden Teams, Dashlane Business)

□ Require TOTP for:
  - VPN access
  - Internal admin dashboards
  - CI/CD pipelines with production deploy access
  - Secrets managers (HashiCorp Vault, AWS Secrets Manager)

□ Audit 2FA enrollment quarterly:
  - Which accounts have 2FA enabled?
  - Any accounts using SMS 2FA?
  - Any shared accounts without 2FA?

TOTP in CI/CD Pipelines

# CI/CD systems should NEVER use human-facing TOTP
# Instead, use:
# - Machine credentials (API keys, service account tokens)
# - OIDC (OpenID Connect) federated identity — no secrets needed
# - Short-lived tokens from your CI platform

# GitHub Actions OIDC to AWS (no static credentials):
# .github/workflows/deploy.yml
permissions:
  id-token: write   # Request JWT
  contents: read

- uses: aws-actions/configure-aws-credentials@v4
  with:
    role-to-assume: arn:aws:iam::123456789012:role/github-actions-role
    aws-region: us-east-1
    # No access key ID or secret — uses OIDC JWT instead

Advanced TOTP: TOTP for SSH, API Authentication and Incident Response

TOTP for SSH Access (PAM)

# Install Google PAM module (Linux)
sudo apt install libpam-google-authenticator

# Run setup as the user
google-authenticator
# Follow prompts: time-based, disallow multiple uses, 3-code window

# Configure PAM
# /etc/pam.d/sshd — add:
auth required pam_google_authenticator.so

# Configure SSH
# /etc/ssh/sshd_config:
ChallengeResponseAuthentication yes
AuthenticationMethods publickey,keyboard-interactive

# Now SSH requires: SSH key + TOTP code
sudo systemctl restart sshd

TOTP Code Validity Calculator

// Useful for debugging: show current TOTP code and validity window
function getCurrentTOTPWindow() {
  const now = Math.floor(Date.now() / 1000);  // Current UNIX time
  const T = Math.floor(now / 30);             // Current time step
  const windowStart = T * 30;                  // Window started at
  const windowEnd = windowStart + 30;           // Window ends at
  const secondsLeft = windowEnd - now;          // Seconds until rotation

  return {
    currentT: T,
    windowStart: new Date(windowStart * 1000).toISOString(),
    windowEnd: new Date(windowEnd * 1000).toISOString(),
    secondsLeft,
    previousT: T - 1,  // Also accepted by most servers
    nextT: T + 1,       // Also accepted by most servers
  };
}

console.log(getCurrentTOTPWindow());
// {
//   currentT: 57027841,
//   windowStart: "2026-03-19T10:00:30.000Z",
//   windowEnd: "2026-03-19T10:01:00.000Z",
//   secondsLeft: 14,
//   previousT: 57027840,
//   nextT: 57027842
// }

How to Use the Tool (Step by Step)

  1. 1

    Generate or enter a TOTP secret

    Use the TOTP 2FA Generator to create a new random Base32 secret, or enter an existing secret to test it. The tool generates a cryptographically secure 160-bit secret by default.

  2. 2

    Scan the QR code with an authenticator app

    Open your authenticator app (Aegis, Raivo, Bitwarden, or Google Authenticator) and scan the QR code. The app stores the secret and starts generating 6-digit codes.

  3. 3

    Verify the current TOTP code

    The tool shows the current 6-digit TOTP code and a countdown timer showing seconds until the next rotation. Verify it matches what your authenticator app shows.

  4. 4

    Note the time window

    Check the validity window — the code is valid for 30 seconds. Servers typically accept the previous and next window (T-1 and T+1) as well, giving an effective validity of up to 90 seconds.

  5. 5

    Test with different algorithms

    Advanced users can test TOTP with SHA-256 or SHA-512 algorithms, or 8-digit codes — used by some banking applications. Most services use the default SHA-1/6-digit configuration.

  6. 6

    Save backup codes for production use

    When enabling TOTP on real accounts, always save the service's backup codes immediately. Store them in your password manager and optionally print a physical copy.

Frequently Asked Questions

How does TOTP generate the same code on both my phone and the server without communicating?+

Both sides share the same secret key (exchanged once via QR code at setup) and both know the current time. TOTP applies HMAC-SHA1(secret, floor(current_time / 30)) to produce a deterministic code. Since both sides have the same inputs, they produce the same output — no communication needed. This is the cryptographic magic of TOTP: shared secret + synchronized clocks = synchronized codes.

Is TOTP safe in 2026? Should I use passkeys instead?+

TOTP is significantly more secure than password-only login and much more secure than SMS OTP. However, TOTP is NOT phishing-resistant — real-time relay attacks can steal TOTP codes along with passwords. Passkeys (FIDO2/WebAuthn) are phishing-resistant because they're cryptographically bound to the exact domain. In 2026, use passkeys where supported (Google, GitHub, Apple, Microsoft all support them). Use TOTP where passkeys aren't yet available. Never use SMS OTP for sensitive accounts.

What happens if my phone's clock is wrong?+

If your phone's clock is significantly wrong, TOTP codes won't match the server's codes. Most servers accept T-1 and T+1 (±30 seconds) to accommodate minor clock skew. Clocks that are off by more than ~90 seconds will cause authentication failures. Fix: enable automatic time sync (NTP) on your device. On Android: Settings → General management → Date and time → Automatic date and time ON. On iOS, time sync is always automatic.

Can TOTP codes be brute-forced?+

Theoretically, there are only 1,000,000 possible 6-digit codes. However, servers enforce rate limiting — typically 3 attempts per 30-second window. At that rate, brute-forcing the average code takes thousands of years. Additionally, servers lock accounts after repeated failures and use anti-automation measures. With proper server-side rate limiting, TOTP brute force is computationally infeasible in practice.

What's the difference between TOTP and HOTP?+

HOTP (RFC 4226) uses an incrementing counter: HMAC(secret, counter). Each generated code increments the counter. The problem: if a user generates codes without using them, the counter drifts out of sync with the server. TOTP (RFC 6238) replaces the counter with time: HMAC(secret, floor(current_time / 30)). Both client and server always agree on the current time, so there's no counter synchronization problem. TOTP is now the universal standard; HOTP is only used in specialized devices where time sync is impossible.

Why shouldn't I use Authy for TOTP?+

Authy's main drawbacks: (1) No export — you cannot export your TOTP secrets from Authy. If you want to switch apps, you must re-enroll every account manually. (2) Proprietary closed-source backup — you must trust Authy's cloud. (3) No desktop app for Linux. Alternatives: Aegis (Android, open source, fully exportable), Raivo (iOS, open source, iCloud E2E encrypted), Bitwarden (cross-platform, open source, full export/import). These give you full ownership of your TOTP secrets.

Free — No Signup Required

Generate and Test TOTP Codes Now

Generate a secure TOTP secret, get a QR code to scan, and verify the 6-digit codes in real time. Test TOTP with SHA-1, SHA-256 or SHA-512. Free, no server storage, all processing in browser.

Open TOTP 2FA Generator

Related Guides