Python One Time Password Library

raw JSON →
2.9.0 verified Tue May 12 auth: no python install: verified

PyOTP is a Python library for generating and verifying one-time passwords, supporting both Time-Based One-Time Passwords (TOTP) from RFC 6238 and HMAC-Based One-Time Passwords (HOTP) from RFC 4226. It is widely used to implement two-factor (2FA) or multi-factor (MFA) authentication in various systems, compatible with apps like Google Authenticator. The library is actively maintained, with its current version being 2.9.0, and follows a regular release cadence.

pip install pyotp
error ModuleNotFoundError: No module named 'pyotp'
cause The 'pyotp' library has not been installed in the current Python environment.
fix
Install the library using pip: pip install pyotp
error ValueError: Not a base32 string
cause The secret key provided to `pyotp.TOTP()` or `pyotp.HOTP()` is not a valid Base32 encoded string, which is required by the library.
fix
Ensure the secret key is properly Base32 encoded. You can generate a valid one using pyotp.random_base32() or encode an existing string using base32.b32encode().
error TypeError: TOTP.__init__() missing 1 required positional argument: 's'
cause The `pyotp.TOTP` (or `pyotp.HOTP`) class constructor requires a secret key (argument `s`) to be provided when an instance is created.
fix
Provide a valid Base32 encoded secret key when initializing the TOTP/HOTP object, for example: pyotp.TOTP('YOURBASE32SECRET').
error AttributeError: module 'pyotp' has no attribute 'now'
cause The `now()` method is an instance method of `pyotp.TOTP` and needs to be called on an instantiated `TOTP` object, not directly on the `pyotp` module or the `TOTP` class.
fix
First create a TOTP object with a secret key, then call now() on that object: totp_obj = pyotp.TOTP('BASE32SECRET'); current_otp = totp_obj.now().
breaking Python 3.6 support was dropped in pyotp v2.8.0. Users on Python 3.6 or older must upgrade their Python environment or pin pyotp to a version prior to 2.8.0.
fix Upgrade Python to 3.7 or newer, or pin `pyotp<2.8.0`.
breaking The default and minimum secret lengths were increased in versions 2.5.0 (base32 to 26 chars) and 2.6.0 (base32 to 32 chars, hex to 40 chars) to meet RFC recommendations. Versions 2.4.0 and later will raise an error if a secret is too short. Applications relying on implicitly generated or shorter secrets from older versions might encounter errors.
fix Ensure that all generated or provided secrets meet the new minimum length requirements (e.g., `pyotp.random_base32()` for 32 characters or `pyotp.random_hex()` for 40 characters).
gotcha To prevent replay attacks, the RFCs and `pyotp` documentation recommend storing the most recently authenticated timestamp, OTP, or a hash of the OTP in your database and rejecting any OTP that has been used before.
fix Implement server-side tracking of used OTPs or timestamps to prevent reuse. For TOTP, `TOTP.verify()` takes a `valid_window` parameter to allow for clock drift, but this does not prevent replay attacks without additional server-side state.
gotcha For TOTP, accurate time synchronization between the server and the client (authenticator app) is crucial. Significant clock drift can lead to OTPs being incorrectly rejected.
fix Ensure your server's clock is synchronized using NTP. When verifying TOTPs, consider using the `valid_window` parameter in `totp.verify()` to allow for minor clock discrepancies.
gotcha As of v2.8.0, OTP generation runs in constant time to mitigate timing side-channel attacks. While this is a security improvement, ensure any custom OTP generation or verification logic in your application also considers constant-time operations if sensitive to such attacks.
fix Review existing custom security-sensitive code paths for potential timing vulnerabilities.
python os / libc status wheel install import disk
3.10 alpine (musl) wheel - 0.05s 17.9M
3.10 alpine (musl) - - 0.04s 17.9M
3.10 slim (glibc) wheel 1.4s 0.02s 18M
3.10 slim (glibc) - - 0.03s 18M
3.11 alpine (musl) wheel - 0.07s 19.7M
3.11 alpine (musl) - - 0.06s 19.7M
3.11 slim (glibc) wheel 1.5s 0.05s 20M
3.11 slim (glibc) - - 0.04s 20M
3.12 alpine (musl) wheel - 0.04s 11.6M
3.12 alpine (musl) - - 0.05s 11.6M
3.12 slim (glibc) wheel 1.4s 0.04s 12M
3.12 slim (glibc) - - 0.04s 12M
3.13 alpine (musl) wheel - 0.04s 11.3M
3.13 alpine (musl) - - 0.04s 11.2M
3.13 slim (glibc) wheel 1.4s 0.04s 12M
3.13 slim (glibc) - - 0.05s 12M
3.9 alpine (musl) wheel - 0.03s 17.4M
3.9 alpine (musl) - - 0.04s 17.4M
3.9 slim (glibc) wheel 1.7s 0.03s 18M
3.9 slim (glibc) - - 0.03s 18M

This quickstart demonstrates how to generate a random secret, create a Time-Based One-Time Password (TOTP) object, generate a provisioning URI for client applications (like Google Authenticator), and then verify an OTP. It also shows a basic example for HMAC-Based One-Time Passwords (HOTP).

import pyotp
import time

# Generate a random base32 secret key
secret = pyotp.random_base32()
print(f"Generated Secret: {secret}")

# Create a TOTP object
totp = pyotp.TOTP(secret)

# Generate a provisioning URI for Google Authenticator (or similar)
# In a real app, 'alice@example.com' would be the user's email
# 'SecureApp' would be the name of your application
uri = totp.provisioning_uri(name="alice@example.com", issuer_name="SecureApp")
print(f"Provisioning URI: {uri}")

# In a real application, you'd render this URI as a QR code for the user to scan.
# For demonstration, we'll manually get a code.

# Simulate getting an OTP code from the user (e.g., from their authenticator app)
current_otp = totp.now()
print(f"Current OTP (will change every 30s): {current_otp}")

# Verify the OTP code
# You might wait a few seconds to demonstrate validity windows
# user_input_otp = input("Enter the OTP from your authenticator app: ")
user_input_otp = current_otp # For demonstration, assume correct input

if totp.verify(user_input_otp):
    print("OTP verified successfully!")
else:
    print("Invalid OTP.")

# For HOTP (counter-based):
hotp = pyotp.HOTP(secret)
initial_count = 0
first_hotp = hotp.at(initial_count)
print(f"HOTP for count {initial_count}: {first_hotp}")

# Verify HOTP
# In a real app, you'd store and increment the counter after each successful verification
if hotp.verify(first_hotp, initial_count):
    print("HOTP verified successfully!")