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.
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.
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
| Parameter | RFC Default | Common Values | Notes |
|---|---|---|---|
| Hash algorithm | HMAC-SHA1 | SHA1, SHA256, SHA512 | SHA1 is standard for most apps |
| Time step (X) | 30 seconds | 30s (universal), 60s (rare) | 30s balances usability and security |
| T0 (epoch) | 0 (UNIX epoch) | Always 0 | Jan 1, 1970 00:00:00 UTC |
| Code digits | 6 | 6 (universal), 8 (higher security) | 8 digits used by some banks |
| Look-ahead window | ±1 time step | 1-2 steps | Compensates for clock skew |
| Secret length | ≥128 bits | 160 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
| App | Platform | Open Source | Cloud Backup | Export | Recommended For |
|---|---|---|---|---|---|
| Aegis | Android only | Yes (GPL-3) | Self-managed encrypted | Yes | Privacy-conscious users |
| Raivo OTP | iOS only | Yes (MIT) | iCloud (encrypted) | Yes | iOS privacy users |
| Bitwarden | All platforms | Yes (AGPL) | Bitwarden cloud (encrypted) | Yes | Integrated password + 2FA management |
| 1Password | All platforms | No | 1Password cloud | Yes | Teams, business users |
| Google Authenticator | iOS, Android | No | Google account sync | Limited | Simplicity (but lacks export) |
| Microsoft Authenticator | iOS, Android | No | Microsoft account | Limited | Microsoft/Azure environments |
| Authy | All platforms | No | Authy 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
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
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
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
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
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
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.
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 GeneratorRelated Guides
Strong Password Guide
NIST 2024 guidelines, time-to-crack tables, and the right way to manage passwords.
Hash Generator — MD5, SHA-256, SHA-1 and More Explained (2026)
Generate MD5, SHA-1, SHA-256, SHA-512 hashes online — understand hashing, verify file integrity, secure data.
Base64 Encode & Decode — What It Is, How It Works & When to Use It
Developer guide to Base64 encoding: use cases, online decoder, and common pitfalls