{"id":1017,"library":"pyotp","title":"Python One Time Password Library","description":"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.","status":"active","version":"2.9.0","language":"python","source_language":"en","source_url":"https://github.com/pyotp/pyotp","tags":["otp","2fa","mfa","totp","hotp","authentication","security"],"install":[{"cmd":"pip install pyotp","lang":"bash","label":"Install latest version"}],"dependencies":[],"imports":[{"symbol":"TOTP","correct":"from pyotp import TOTP"},{"symbol":"HOTP","correct":"from pyotp import HOTP"},{"symbol":"random_base32","correct":"import pyotp\nsecret = pyotp.random_base32()"},{"symbol":"random_hex","correct":"import pyotp\nsecret = pyotp.random_hex()"},{"symbol":"parse_uri","correct":"import pyotp\notp_object = pyotp.parse_uri(uri_string)"}],"quickstart":{"code":"import pyotp\nimport time\n\n# Generate a random base32 secret key\nsecret = pyotp.random_base32()\nprint(f\"Generated Secret: {secret}\")\n\n# Create a TOTP object\ntotp = pyotp.TOTP(secret)\n\n# Generate a provisioning URI for Google Authenticator (or similar)\n# In a real app, 'alice@example.com' would be the user's email\n# 'SecureApp' would be the name of your application\nuri = totp.provisioning_uri(name=\"alice@example.com\", issuer_name=\"SecureApp\")\nprint(f\"Provisioning URI: {uri}\")\n\n# In a real application, you'd render this URI as a QR code for the user to scan.\n# For demonstration, we'll manually get a code.\n\n# Simulate getting an OTP code from the user (e.g., from their authenticator app)\ncurrent_otp = totp.now()\nprint(f\"Current OTP (will change every 30s): {current_otp}\")\n\n# Verify the OTP code\n# You might wait a few seconds to demonstrate validity windows\n# user_input_otp = input(\"Enter the OTP from your authenticator app: \")\nuser_input_otp = current_otp # For demonstration, assume correct input\n\nif totp.verify(user_input_otp):\n    print(\"OTP verified successfully!\")\nelse:\n    print(\"Invalid OTP.\")\n\n# For HOTP (counter-based):\nhotp = pyotp.HOTP(secret)\ninitial_count = 0\nfirst_hotp = hotp.at(initial_count)\nprint(f\"HOTP for count {initial_count}: {first_hotp}\")\n\n# Verify HOTP\n# In a real app, you'd store and increment the counter after each successful verification\nif hotp.verify(first_hotp, initial_count):\n    print(\"HOTP verified successfully!\")\n","lang":"python","description":"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)."},"warnings":[{"fix":"Upgrade Python to 3.7 or newer, or pin `pyotp<2.8.0`.","message":"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.","severity":"breaking","affected_versions":">=2.8.0"},{"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).","message":"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.","severity":"breaking","affected_versions":">=2.4.0"},{"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.","message":"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.","severity":"gotcha","affected_versions":"All"},{"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.","message":"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.","severity":"gotcha","affected_versions":"All"},{"fix":"Review existing custom security-sensitive code paths for potential timing vulnerabilities.","message":"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.","severity":"gotcha","affected_versions":"All"}],"env_vars":null,"last_verified":"2026-05-12T22:39:21.527Z","next_check":"2026-06-27T00:00:00.000Z","problems":[{"fix":"Install the library using pip: `pip install pyotp`","cause":"The 'pyotp' library has not been installed in the current Python environment.","error":"ModuleNotFoundError: No module named 'pyotp'"},{"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()`.","cause":"The secret key provided to `pyotp.TOTP()` or `pyotp.HOTP()` is not a valid Base32 encoded string, which is required by the library.","error":"ValueError: Not a base32 string"},{"fix":"Provide a valid Base32 encoded secret key when initializing the TOTP/HOTP object, for example: `pyotp.TOTP('YOURBASE32SECRET')`.","cause":"The `pyotp.TOTP` (or `pyotp.HOTP`) class constructor requires a secret key (argument `s`) to be provided when an instance is created.","error":"TypeError: TOTP.__init__() missing 1 required positional argument: 's'"},{"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()`.","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.","error":"AttributeError: module 'pyotp' has no attribute 'now'"}],"ecosystem":"pypi","meta_description":null,"install_score":100,"install_tag":"verified","quickstart_score":null,"quickstart_tag":null,"pypi_latest":"2.9.0","cli_name":"","install_checks":{"last_tested":"2026-05-12","tag":"verified","tag_description":"installs cleanly on critical runtimes, fast import, recently tested","results":[{"runtime":"python:3.10-alpine","python_version":"3.10","os_libc":"alpine (musl)","variant":"default","exit_code":0,"wheel_type":"wheel","failure_reason":null,"install_time_s":null,"import_time_s":0.05,"mem_mb":1.8,"disk_size":"17.9M"},{"runtime":"python:3.10-alpine","python_version":"3.10","os_libc":"alpine (musl)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.04,"mem_mb":1.8,"disk_size":"17.9M"},{"runtime":"python:3.10-slim","python_version":"3.10","os_libc":"slim (glibc)","variant":"default","exit_code":0,"wheel_type":"wheel","failure_reason":null,"install_time_s":1.4,"import_time_s":0.02,"mem_mb":1.8,"disk_size":"18M"},{"runtime":"python:3.10-slim","python_version":"3.10","os_libc":"slim (glibc)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.03,"mem_mb":1.8,"disk_size":"18M"},{"runtime":"python:3.11-alpine","python_version":"3.11","os_libc":"alpine (musl)","variant":"default","exit_code":0,"wheel_type":"wheel","failure_reason":null,"install_time_s":null,"import_time_s":0.07,"mem_mb":1.9,"disk_size":"19.7M"},{"runtime":"python:3.11-alpine","python_version":"3.11","os_libc":"alpine (musl)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.06,"mem_mb":1.9,"disk_size":"19.7M"},{"runtime":"python:3.11-slim","python_version":"3.11","os_libc":"slim (glibc)","variant":"default","exit_code":0,"wheel_type":"wheel","failure_reason":null,"install_time_s":1.5,"import_time_s":0.05,"mem_mb":1.9,"disk_size":"20M"},{"runtime":"python:3.11-slim","python_version":"3.11","os_libc":"slim (glibc)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.04,"mem_mb":1.9,"disk_size":"20M"},{"runtime":"python:3.12-alpine","python_version":"3.12","os_libc":"alpine (musl)","variant":"default","exit_code":0,"wheel_type":"wheel","failure_reason":null,"install_time_s":null,"import_time_s":0.04,"mem_mb":1.5,"disk_size":"11.6M"},{"runtime":"python:3.12-alpine","python_version":"3.12","os_libc":"alpine (musl)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.05,"mem_mb":1.5,"disk_size":"11.6M"},{"runtime":"python:3.12-slim","python_version":"3.12","os_libc":"slim (glibc)","variant":"default","exit_code":0,"wheel_type":"wheel","failure_reason":null,"install_time_s":1.4,"import_time_s":0.04,"mem_mb":1.5,"disk_size":"12M"},{"runtime":"python:3.12-slim","python_version":"3.12","os_libc":"slim (glibc)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.04,"mem_mb":1.5,"disk_size":"12M"},{"runtime":"python:3.13-alpine","python_version":"3.13","os_libc":"alpine (musl)","variant":"default","exit_code":0,"wheel_type":"wheel","failure_reason":null,"install_time_s":null,"import_time_s":0.04,"mem_mb":1.6,"disk_size":"11.3M"},{"runtime":"python:3.13-alpine","python_version":"3.13","os_libc":"alpine (musl)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.04,"mem_mb":1.6,"disk_size":"11.2M"},{"runtime":"python:3.13-slim","python_version":"3.13","os_libc":"slim (glibc)","variant":"default","exit_code":0,"wheel_type":"wheel","failure_reason":null,"install_time_s":1.4,"import_time_s":0.04,"mem_mb":1.4,"disk_size":"12M"},{"runtime":"python:3.13-slim","python_version":"3.13","os_libc":"slim (glibc)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.05,"mem_mb":1.4,"disk_size":"12M"},{"runtime":"python:3.9-alpine","python_version":"3.9","os_libc":"alpine (musl)","variant":"default","exit_code":0,"wheel_type":"wheel","failure_reason":null,"install_time_s":null,"import_time_s":0.03,"mem_mb":1.6,"disk_size":"17.4M"},{"runtime":"python:3.9-alpine","python_version":"3.9","os_libc":"alpine (musl)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.04,"mem_mb":1.6,"disk_size":"17.4M"},{"runtime":"python:3.9-slim","python_version":"3.9","os_libc":"slim (glibc)","variant":"default","exit_code":0,"wheel_type":"wheel","failure_reason":null,"install_time_s":1.7,"import_time_s":0.03,"mem_mb":1.6,"disk_size":"18M"},{"runtime":"python:3.9-slim","python_version":"3.9","os_libc":"slim (glibc)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.03,"mem_mb":1.6,"disk_size":"18M"}]},"quickstart_checks":{"last_tested":"2026-04-24","tag":null,"tag_description":null,"results":[{"runtime":"python:3.10-alpine","exit_code":0},{"runtime":"python:3.10-slim","exit_code":0},{"runtime":"python:3.11-alpine","exit_code":0},{"runtime":"python:3.11-slim","exit_code":0},{"runtime":"python:3.12-alpine","exit_code":0},{"runtime":"python:3.12-slim","exit_code":0},{"runtime":"python:3.13-alpine","exit_code":0},{"runtime":"python:3.13-slim","exit_code":0},{"runtime":"python:3.9-alpine","exit_code":0},{"runtime":"python:3.9-slim","exit_code":0}]}}