正在加载,请稍候…

OTP Security: From SMS to In-App Tokens – Vulnerabilities and Best Practices

Explore OTP security risks including SMS interception, SIM swap, and phishing. Learn why in-app OTPs are safer, with best practices and a worked example.

One-Time Passwords (OTPs) are everywhere: login, password reset, payment confirmation. They seem simple – a short code sent to your phone – but their security depends heavily on the delivery channel. SMS OTPs, once the gold standard, are now a prime attack surface. This article dives into the vulnerabilities of SMS-based OTPs, explains why in-app OTPs are gaining traction, and provides concrete best practices for developers. Try generating test OTPs with our OTP Generator to see the difference in implementations.

person holding phone with SMS verification code on screen

The Three Main Attacks on SMS OTPs

SMS OTPs rely on the assumption that "your phone number = you." Attackers have three proven ways to break that assumption:

SIM Swap

An attacker convinces (or bribes) a mobile carrier to transfer your phone number to a SIM card they control. Once done, all your SMS – including OTPs – go to their device. This is a well-known attack, especially in regions with weak carrier verification.

SS7 Interception

Signaling System No. 7 (SS7) is the protocol that lets telecom networks talk to each other. It was designed decades ago with minimal security. Attackers can exploit SS7 vulnerabilities to intercept SMS messages in transit, without touching your phone. This technique is now commoditized in the underground.

Phishing

The simplest attack: a fake website or app that mimics a bank or service, tricking you into entering the OTP you just received. The attacker then uses that OTP in real-time to complete a transaction.

These attacks are not theoretical. In the Philippines, the central bank (BSP) recorded 70,000 financial fraud complaints in 2024 alone, leading to a regulatory ban on SMS OTPs for high-risk transactions by mid-2026.

Why In-App OTPs Are More Secure

An in-app OTP is generated and displayed inside the authenticator app itself (e.g., Google Authenticator, Authy, or a built-in bank app). It never travels over the cellular network. This eliminates SIM swap and SS7 interception entirely. Phishing is also much harder because the code is tied to the device and app session.

Feature SMS OTP In-App OTP
Delivery channel Cellular network (untrusted) App push / local generation (controlled)
Susceptible to SIM swap Yes No
Susceptible to SS7 interception Yes No
Phishable High (code sent to phone, can be forwarded) Low (code bound to device)
User experience Slow, requires switching apps Fast, one-tap confirmation
Offline capability No Yes (TOTP)

When to Use Which OTP Method

  • SMS OTP is still acceptable for low-risk actions like verifying a phone number during registration. But it should never be used for high-risk transactions (money transfers, password changes, critical account updates).
  • In-App OTP (especially TOTP – Time-based One-Time Password) is the current best practice for two-factor authentication (2FA). It is free, open-standard (RFC 6238), and works offline.
  • Push-based in-app OTP (where the app receives a push notification and you tap "Approve") offers even better UX and security, as it requires an active app session.

Worked Example: Implementing TOTP In-App

Let's walk through a minimal TOTP implementation in Python. This is the core of most in-app authenticators.

import hmac
import hashlib
import struct
import time

def hotp(secret: bytes, counter: int, digits: int = 6) -> str:
    """Generate an HMAC-based one-time password (HOTP)."""
    msg = struct.pack('>Q', counter)
    h = hmac.new(secret, msg, hashlib.sha1).digest()
    offset = h[-1] & 0x0f
    truncated = struct.unpack('>I', h[offset:offset+4])[0] & 0x7fffffff
    return str(truncated % (10 ** digits)).zfill(digits)

def totp(secret: bytes, digits: int = 6, interval: int = 30) -> str:
    """Generate a time-based one-time password (TOTP)."""
    counter = int(time.time()) // interval
    return hotp(secret, counter, digits)

# Example usage
secret = b'12345678901234567890'  # In production, generate securely
print(totp(secret))  # e.g., "287082"

Key points:

  • The secret must be generated securely (e.g., os.urandom(20)) and shared with the user via QR code (using otpauth:// URI).
  • The counter is the current Unix timestamp divided by 30 seconds. This gives a 30-second window.
  • The code uses HMAC-SHA1, which is the default for TOTP. SHA256 or SHA512 can also be used.

To verify, the server computes the same TOTP and compares it to the user's input. A common practice is to allow a small time skew (e.g., ±1 interval) to account for clock drift.

Common Pitfalls in OTP Implementation

  • Hardcoded secrets: Never embed secrets in client-side code. Use a QR code to transfer the secret securely.
  • No rate limiting: Without rate limiting, an attacker can brute-force a 6-digit OTP (1 million possibilities) in minutes. Implement exponential backoff or CAPTCHA after a few failures.
  • Long validity windows: A 10-minute OTP window is too long. Stick to 30 seconds for TOTP, or 1-2 minutes for email/SMS.
  • Reusing OTPs: Each OTP should be single-use. Once verified, it must not be accepted again (even within the time window).
  • Logging OTPs: Never log OTP values. They are sensitive secrets.
  • Ignoring clock skew: TOTP requires accurate time. Allow a small drift (e.g., ±1 interval) on the server.

FAQ

Why are SMS OTPs still used if they are insecure?

SMS OTPs are easy to implement and work on any phone without an app. Many legacy systems rely on them. However, regulators are increasingly banning them for high-risk transactions.

What is the difference between HOTP and TOTP?

HOTP (HMAC-based) uses a counter that increments with each use. TOTP uses time as the counter. TOTP is more common for 2FA because it doesn't require the server to track the counter.

Can in-app OTPs be phished?

Yes, but it's harder. An attacker would need to trick the user into entering the code on a fake site in real-time. Push-based approvals (where the user taps "Approve") are even more resistant.

Should I generate OTPs on the server or client?

For TOTP, the secret is shared at enrollment, then both sides generate the same code independently. The server must verify, but generation can happen on the client.

What is the best OTP delivery method for high-security apps?

In-app TOTP or push-based approval, combined with biometric verification (fingerprint/Face ID) on the device. Avoid SMS entirely for critical operations.