refactor: replace custom OTP generation with pyotp (RFC 6238/4226)
Summary
app/services/otp_service.py implements a non-standard OTP scheme using Python's random module and HMAC-SHA256 with the user's phone number as a salt. This is a bespoke protocol that diverges from industry standards and carries security risks.
Problem
- Uses
random(notsecrets) for OTP generation —randomis not cryptographically secure - HMAC with phone number as salt means the salt is predictable and static per user
- Non-standard token format means no interoperability with authenticator apps (Google Authenticator, Aegis, etc.) if the product ever moves toward app-based OTP
- The implementation must be security-reviewed independently since it doesn't follow a published standard
- SMS delivery uses a raw
requests.postcall with manual credential management instead of a provider SDK
Proposed Solution
Replace OTP generation with pyotp (MIT), which implements:
- TOTP (RFC 6238) — time-based one-time passwords (standard for SMS OTP flows)
- HOTP (RFC 4226) — counter-based one-time passwords
import pyotp
# Generate a time-based OTP (valid 30s window)
totp = pyotp.TOTP(pyotp.random_base32())
otp_code = totp.now() # generate
totp.verify(user_input) # verify (constant-time)
For SMS delivery, keep a minimal adapter (~20 lines) calling your gateway — this is the only truly custom part.
Library signals:
- License: MIT
- Weekly downloads: 1M+
- Maintenance: Active (
pyauthorg) - Standard: RFC 6238 / RFC 4226
- CVEs: None known
Impact
| Dimension | Current | After |
|---|---|---|
| Cryptographic correctness |
random module (not CSPRNG) |
CSPRNG via secrets internally |
| Standard compliance | Bespoke HMAC scheme | RFC 6238 (TOTP) |
| Security review burden | Full custom review required | Standard with published test vectors |
| Interoperability | None | Compatible with authenticator apps if needed |
| LOC | ~100 lines | ~20 lines (SMS adapter only) |
Files Affected
-
app/services/otp_service.py— replace OTP logic, retain SMS dispatch as thin adapter -
pyproject.toml— addpyotp