Python One-Time Password Library

Overview

PyOTP - The Python One-Time Password Library

PyOTP is a Python library for generating and verifying one-time passwords. It can be used to implement two-factor (2FA) or multi-factor (MFA) authentication methods in web applications and in other systems that require users to log in.

Open MFA standards are defined in RFC 4226 (HOTP: An HMAC-Based One-Time Password Algorithm) and in RFC 6238 (TOTP: Time-Based One-Time Password Algorithm). PyOTP implements server-side support for both of these standards. Client-side support can be enabled by sending authentication codes to users over SMS or email (HOTP) or, for TOTP, by instructing users to use Google Authenticator, Authy, or another compatible app. Users can set up auth tokens in their apps easily by using their phone camera to scan otpauth:// QR codes provided by PyOTP.

Implementers should read and follow the HOTP security requirements and TOTP security considerations sections of the relevant RFCs. At minimum, application implementers should follow this checklist:

  • Ensure transport confidentiality by using HTTPS
  • Ensure HOTP/TOTP secret confidentiality by storing secrets in a controlled access database
  • Deny replay attacks by rejecting one-time passwords that have been used by the client (this requires storing the most recently authenticated timestamp, OTP, or hash of the OTP in your database, and rejecting the OTP when a match is seen)
  • Throttle brute-force attacks against your application's login functionality
  • When implementing a "greenfield" application, consider supporting FIDO U2F/WebAuthn in addition to HOTP/TOTP. U2F uses asymmetric cryptography to avoid using a shared secret design, which strengthens your MFA solution against server-side attacks. Hardware U2F also sequesters the client secret in a dedicated single-purpose device, which strengthens your clients against client-side attacks. And by automating scoping of credentials to relying party IDs (application origin/domain names), U2F adds protection against phishing attacks. One implementation of FIDO U2F/WebAuthn is PyOTP's sister project, PyWARP.

We also recommend that implementers read the OWASP Authentication Cheat Sheet and NIST SP 800-63-3: Digital Authentication Guideline for a high level overview of authentication best practices.

Quick overview of using One Time Passwords on your phone

  • OTPs involve a shared secret, stored both on the phone and the server
  • OTPs can be generated on a phone without internet connectivity
  • OTPs should always be used as a second factor of authentication (if your phone is lost, you account is still secured with a password)
  • Google Authenticator and other OTP client apps allow you to store multiple OTP secrets and provision those using a QR Code

Installation

pip install pyotp

Usage

Time-based OTPs

totp = pyotp.TOTP('base32secret3232')
totp.now() # => '492039'

# OTP verified for current time
totp.verify('492039') # => True
time.sleep(30)
totp.verify('492039') # => False

Counter-based OTPs

hotp = pyotp.HOTP('base32secret3232')
hotp.at(0) # => '260182'
hotp.at(1) # => '055283'
hotp.at(1401) # => '316439'

# OTP verified with a counter
hotp.verify('316439', 1401) # => True
hotp.verify('316439', 1402) # => False

Generating a Secret Key

A helper function is provided to generate a 32-character base32 secret, compatible with Google Authenticator and other OTP apps:

pyotp.random_base32()

Some applications want the secret key to be formatted as a hex-encoded string:

pyotp.random_hex()  # returns a 40-character hex-encoded secret

Google Authenticator Compatible

PyOTP works with the Google Authenticator iPhone and Android app, as well as other OTP apps like Authy. PyOTP includes the ability to generate provisioning URIs for use with the QR Code scanner built into these MFA client apps:

pyotp.totp.TOTP('JBSWY3DPEHPK3PXP').provisioning_uri(name='[email protected]', issuer_name='Secure App')

>>> 'otpauth://totp/Secure%20App:alice%40google.com?secret=JBSWY3DPEHPK3PXP&issuer=Secure%20App'

pyotp.hotp.HOTP('JBSWY3DPEHPK3PXP').provisioning_uri(name="[email protected]", issuer_name="Secure App", initial_count=0)

>>> 'otpauth://hotp/Secure%20App:alice%40google.com?secret=JBSWY3DPEHPK3PXP&issuer=Secure%20App&counter=0'

This URL can then be rendered as a QR Code (for example, using https://github.com/soldair/node-qrcode) which can then be scanned and added to the users list of OTP credentials.

Parsing these URLs is also supported:

pyotp.parse_uri('otpauth://totp/Secure%20App:alice%40google.com?secret=JBSWY3DPEHPK3PXP&issuer=Secure%20App')

>>> <pyotp.totp.TOTP object at 0xFFFFFFFF>

pyotp.parse_uri('otpauth://hotp/Secure%20App:alice%40google.com?secret=JBSWY3DPEHPK3PXP&issuer=Secure%20App&counter=0'

>>> <pyotp.totp.HOTP object at 0xFFFFFFFF>

Working example

Scan the following barcode with your phone's OTP app (e.g. Google Authenticator):

https://chart.apis.google.com/chart?cht=qr&chs=250x250&chl=otpauth%3A%2F%2Ftotp%2Falice%40google.com%3Fsecret%3DJBSWY3DPEHPK3PXP

Now run the following and compare the output:

import pyotp
totp = pyotp.TOTP("JBSWY3DPEHPK3PXP")
print("Current OTP:", totp.now())

Links

For new applications:

https://readthedocs.org/projects/pyotp/badge/?version=latest
Comments
  • Using provisioning_uri occaisionally mixes up secret & issuer

    Using provisioning_uri occaisionally mixes up secret & issuer

    I've noticed that upon generating a new TOTP with provisioning_uri (for Google) the string occasionally switches the secret & issuer causing the QR generation to break (with qrious).

    Working with qrious: otpauth://totp/Company:name%domain.com?secret=2345ABCD6789EFGH&issuer=Company

    Not Working with qrious: otpauth://totp/Company:name%domain.com?issuer=Company&secret=2345ABCD6789EFGH

    Should this be shared with the people working on qrious, is the format not important?

    opened by bshore 15
  • not support pypy3

    not support pypy3

    pypy3 support Python 3.2.5 syntax.

    on pypy3, try the example and pypy3 show following error

    import pyotp totp = pyotp.TOTP('base32secret3232') totp.now()

    Traceback (most recent call last): File "", line 1, in File "/opt/pypy3/site-packages/pyotp/totp.py", line 35, in now return self.generate_otp(self.timecode(datetime.datetime.now())) File "/opt/pypy3/site-packages/pyotp/otp.py", line 31, in generate_otp self.byte_secret(), File "/opt/pypy3/site-packages/pyotp/otp.py", line 52, in byte_secret return base64.b32decode(self.secret, casefold=True) File "/opt/pypy3/lib-python/3/base64.py", line 215, in b32decode raise TypeError("expected bytes, not %s" % s. _ _ class _ _ . _ _ name _ _ ) TypeError: expected bytes, not str

    opened by dd-han 13
  • normalize `verify` otp param before comparison

    normalize `verify` otp param before comparison

    Hi, thanks for the library. While using pyotp, I noticed that if you pass in an OTP to be verified as a string, the equality check will fail (despite the docstring advertising strings as being acceptable). I've attached a patch that fixes this.

    opened by jamesob 12
  • Test values out of range for platform time_t on various architectures

    Test values out of range for platform time_t on various architectures

    Hi,

    The testsuite seems to always fail on 32-bits/ARM architectures because of out of range test values like here. You can find a complete build log here.

    This makes the Debian package fails to build from source on i386, armhf and armel architectures.

    opened by hlef 9
  • Document the need to prevent OTP reuse and maybe provide suggested solutions

    Document the need to prevent OTP reuse and maybe provide suggested solutions

    According to Section 5.2 of RFC6238,

    Note that a prover may send the same OTP inside a given time-step window multiple times to a verifier. The verifier MUST NOT accept the second attempt of the OTP after the successful validation has been issued for the first OTP, which ensures one-time only use of an OTP.

    However, the existing implementation (v2.2.6) appears to violate this:

    $ python
    Python 3.5.2 (default, Nov 23 2017, 16:37:01) 
    [GCC 5.4.0 20160609] on linux
    Type "help", "copyright", "credits" or "license" for more information.
    >>> import pyotp; key = pyotp.random_base32(length=20)
    >>> t = pyotp.TOTP(key)
    >>> t.now()
    '925243'
    >>> t.verify('925243')
    True
    >>> t.verify('925243')
    True
    >>> t.verify('925243')
    True
    >>> t.verify('925243')
    True
    >>> 
    

    This could be a security issue: an attacker could conceivably MITM the connection between the verifier and provider, get the credentials & TOTP value, and use those to log in within the 30-second window. This would appear to defeat the verification because the TOTP can be replayed.

    Alternatively, an attacker could shoulder-surf the victim and then log in separately within 30 seconds.

    opened by vsajip 8
  • FortiToken Mobile compatibility

    FortiToken Mobile compatibility

    >>> import pyotp
    >>> import time
    >>> totp = pyotp.TOTP("xxxxxxxx", interval=60)
    >>> print("Current OTP:", totp.now(), time.asctime( time.localtime(time.time()) ))
    Current OTP: 473903 Fri Sep 18 10:38:14 2020
    
    FortiToken Mobile 865980
    
    

    Onboard Security Algorithm: OATH time and event based OTP generator: OTP Spec RFC 6238, RFC 4226

    https://www.fortinet.com/content/dam/fortinet/assets/data-sheets/fortitoken.pdf

    opened by saup007 6
  • Correct provisioning_uri method's output

    Correct provisioning_uri method's output

    Added to the provisioning_uri method in HOTP and TOTP classes the ability to call the util's function build_uri with all the necessary arguments.

    Added to the function build_uri the ability to handle values different than the defaults for algorithm, digits and period when generating URI.

    Closes #32

    opened by baco 6
  • Support issuer name in URI generation

    Support issuer name in URI generation

    In the Google Authenticator app, if the URI specified doesn't include an issuer_name parameter, the Authenticator entry will lack a title.

    This PR brings URI generation in pyotp closer into compliance with Authenticator's key URI format.

    opened by jamesob 6
  • Default output of `random_base32()` is not valid base32

    Default output of `random_base32()` is not valid base32

    Relates to #109 Introduced in 9576711d5de1b0873056ab668b409473a97e3a9c

    The current output of the random_base32() function is a string of base32 alphabet characters, of 26 length. This is not a valid base32 string, as it does not include padding to a length multiple of 8.

    This causes problems when it is used as the secret value for a TOTP, like the output of TOTP.provisioning_uri changing depending on whether or not TOTP.verify has previously been called:

    # "S46SQCPPTCNPROMHWYBDCTBZXV" is a sample output from random_base32() that exhibits buggy behaviour
    
    In [29]: code = pyotp.totp.TOTP("S46SQCPPTCNPROMHWYBDCTBZXV")
    
    In [30]: code.provisioning_uri()
    Out[30]: 'otpauth://totp/Secret?secret=S46SQCPPTCNPROMHWYBDCTBZXV'
    
    In [31]: code.verify("000000")
    Out[31]: False
    
    # This should give the same output, but it doesn't
    In [32]: code.provisioning_uri()
    Out[32]: 'otpauth://totp/Secret?secret=S46SQCPPTCNPROMHWYBDCTBZXV%3D%3D%3D%3D%3D%3D'
    

    More importantly, it introduces undefined behaviour when interoperating with other TOTP libraries, such as node's speakeasy. The example secret below is the same in both examples, but produces different codes in each library:

    In [16]: pyotp.totp.TOTP("S46SQCPPTCNPROMHWYBDCTBZXV").at(datetime.fromtimestamp(1612380872))
    Out[16]: '100172'
    
    > speakeasy.totp({"secret":"S46SQCPPTCNPROMHWYBDCTBZXV","encoding":"base32","time":1612380872})
    '184825'
    

    This is flaky behaviour, as a different base32 alphabet string of length 26 does give the same codes between libraries. I imagine how the two libraries handle invalid base32 differs in implementation detail.

    In [36]: pyotp.totp.TOTP("A4QGCTHL3HNMC3NAW2OT45WWWA").at(datetime.fromtimestamp(1612380872))
    Out[36]: '080982'
    
    > speakeasy.totp({"secret":"A4QGCTHL3HNMC3NAW2OT45WWWA","encoding":"base32","time":1612380872})
    '080982'
    

    To fix this, I suggest increasing the default length of the generated string to 32, which is a multiple of 8.

    opened by tommilligan 5
  • Add optional image parameter to provisioning_uri

    Add optional image parameter to provisioning_uri

    This PR adds an optional image parameter to the uri returned by provisioning_uri.

    The image parameter allows for the inclusion of a logo in the Authy and FreeOTP apps.

    I did this without realizing there was an existing issue: https://github.com/pyauth/pyotp/issues/96

    opened by ddboline 5
  • Question: How do you generate password from string param

    Question: How do you generate password from string param

    I needed to generate a TOTP from the given secure secret [email protected]. So, I encoded the secure secret by base32 beforehand, and the authentication worked! But I'm not sure if this is the right approach. Could somebody tell me if this is right or not?

    secret = b'[email protected]'
    secret = base64.b32encode(secret)
    totp = pyotp.TOTP(secret)
    totp.now() # => '199731'
    
    opened by yoshi486x 5
  • Support for Synology 2FA?

    Support for Synology 2FA?

    I'm using the Synology Python API to get data from 4 different NAS. Since I enabled 2FA on all of them I wanted to use PyOTP to generate the correct OTP codes for it. The problem is that those codes are not being accepted. I have extracted the secrets from the QR codes provided and pass those to the pytotp.TOTP() method. The problem is that the codes that PyOTP generates (I'm using totp.now()) are not being accepted.

    opened by cwbsl 2
  • Cannot decode URI due case-sensitivity

    Cannot decode URI due case-sensitivity

    When trying to parse a uri provided by runescape, the 'algorithm' parameter causes this error;

    File "/usr/local/lib/python3.8/dist-packages/pyotp/init.py", line 78, in parse_uri ValueError: Invalid value for algorithm, must be SHA1, SHA256 or SHA512

    stepping through the code shows this is a capitalization issue and should be easily fixed.

    Example URI: 'otpauth://totp/playerName?secret=yourB64SecretHere&issuer=RuneScape&algorithm=sha1&digits=6&period=30'

    opened by LeightonSmallshire 1
  • Return time remaining in generate_otp()

    Return time remaining in generate_otp()

    I have the following code in my library:

    def get_token(...):
    	...
    	msg = struct.pack(">Q", int(time / seconds))
    	r = hmac.new(secret, msg, sha1).digest()
    	k = r[19]
    	idx = k & 0x0f
    	h = struct.unpack(">L", r[idx:idx + 4])[0] & 0x7fffffff
    	return h % (10 ** digits), -(time % seconds - seconds)
    

    I'm replacing it with pyotp to simplify things. Unfortunately, I do use that second return parameter, which is the time remaining for the code's validity. I use it to show how long the user's code is still valid for, and to decide when to generate a new one.

    I understand why at() doesn't return the time remaining but having it in generate_otp() at the very least would make this functionality possible.

    Thanks!

    opened by jleclanche 2
  • Support clock skew and resync in TOTP validation

    Support clock skew and resync in TOTP validation

    Consider this test:

    import pyotp
    from time import sleep
    
    key = pyotp.random_base32()
    for i in range(1,10):
      otp = pyotp.TOTP(key).now()
      sleep(5)
      print pyotp.TOTP(key).verify(otp)
    

    Sample output is:

    True
    False
    True
    True
    True
    True
    True
    False
    True
    

    I have seen this in production. I don't know why it fails sometimes. Any idea why? And how I can go about fixing it? Thank you, Joseph

    opened by jjude 8
Releases(v2.8.0)
Owner
PyAuth
Python Web Authentication Libraries
PyAuth
Abusing Microsoft 365 OAuth Authorization Flow for Phishing Attack

Microsoft365_devicePhish Abusing Microsoft 365 OAuth Authorization Flow for Phishing Attack This is a simple proof-of-concept script that allows an at

Optiv Security 76 Jan 02, 2023
A generic, spec-compliant, thorough implementation of the OAuth request-signing logic

OAuthLib - Python Framework for OAuth1 & OAuth2 *A generic, spec-compliant, thorough implementation of the OAuth request-signing logic for Python 3.5+

OAuthlib 2.5k Jan 02, 2023
Generate payloads that force authentication against an attacker machine

Hashgrab Generates scf, url & lnk payloads to put onto a smb share. These force authentication to an attacker machine in order to grab hashes (for exa

xct 35 Dec 20, 2022
Mock authentication API that acceccpts email and password and returns authentication result.

Mock authentication API that acceccpts email and password and returns authentication result.

Herman Shpryhau 1 Feb 11, 2022
This project is an open-source project which I made due to sharing my experience around the Python programming language.

django-tutorial This project is an open-source project which I made due to sharing my experience around the Django framework. What is Django? Django i

MohammadMasoumi 6 May 12, 2022
Local server that gives you your OAuth 2.0 tokens needed to interact with the Conta Azul's API

What's this? This is a django project meant to be run locally that gives you your OAuth 2.0 tokens needed to interact with Conta Azul's API Prerequisi

Fábio David Freitas 3 Apr 13, 2022
A secure authentication module to validate user credentials in a Streamlit application.

Streamlit-Authenticator A secure authentication module to validate user credentials in a Streamlit application. Installation Streamlit-Authenticator i

M Khorasani 336 Dec 31, 2022
A JOSE implementation in Python

python-jose A JOSE implementation in Python Docs are available on ReadTheDocs. The JavaScript Object Signing and Encryption (JOSE) technologies - JSON

Michael Davis 1.2k Dec 28, 2022
Use this to create (admin) personal access token in gitlab database. Mainly used for automation.

gitlab-personal-access-token Ensure PAT is present in gitlab database. This tool is mainly used when you need to automate gitlab installation and conf

CINAQ Internet Technologies 1 Jan 30, 2022
JSON Web Token implementation in Python

PyJWT A Python implementation of RFC 7519. Original implementation was written by @progrium. Sponsor If you want to quickly add secure token-based aut

José Padilla 4.5k Jan 09, 2023
Auth-Starters - Different APIs using Django & Flask & FastAPI to see Authentication Service how its work

Auth-Starters Different APIs using Django & Flask & FastAPI to see Authentication Service how its work, and how to use it. This Repository based on my

Yasser Tahiri 7 Apr 22, 2022
API-key based security utilities for FastAPI, focused on simplicity of use

FastAPI simple security API key based security package for FastAPI, focused on simplicity of use: Full functionality out of the box, no configuration

Tolki 154 Jan 03, 2023
Django Auth Protection This package logout users from the system by changing the password in Simple JWT REST API.

Django Auth Protection Django Auth Protection This package logout users from the system by changing the password in REST API. Why Django Auth Protecti

Iman Karimi 5 Oct 26, 2022
FastAPI extension that provides JWT Auth support (secure, easy to use, and lightweight)

FastAPI JWT Auth Documentation: https://indominusbyte.github.io/fastapi-jwt-auth Source Code: https://github.com/IndominusByte/fastapi-jwt-auth Featur

Nyoman Pradipta Dewantara 468 Jan 01, 2023
A JSON Web Token authentication plugin for the Django REST Framework.

Simple JWT Abstract Simple JWT is a JSON Web Token authentication plugin for the Django REST Framework. For full documentation, visit django-rest-fram

Jazzband 3.2k Dec 29, 2022
Authentication for Django Rest Framework

Dj-Rest-Auth Drop-in API endpoints for handling authentication securely in Django Rest Framework. Works especially well with SPAs (e.g React, Vue, Ang

Michael 1.1k Jan 03, 2023
An extension of django rest framework, providing a configurable password reset strategy

Django Rest Password Reset This python package provides a simple password reset strategy for django rest framework, where users can request password r

Anexia 363 Dec 24, 2022
Alisue 299 Dec 06, 2022
This Python based program checks your CC Stripe Auth 1$ Based Checker

CC-Checker This Python based program checks your CC Stripe Auth 1$ Based Checker About Author Coded by xBlackx Reach Me On Telegram @xBlackx_Coder jOI

xBlackxCoder 11 Nov 20, 2022
Login System Using Django

Login System Django

Nandini Chhajed 6 Dec 12, 2021