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
row level security for FastAPI framework

Row Level Permissions for FastAPI While trying out the excellent FastApi framework there was one peace missing for me: an easy, declarative way to def

Holger Frey 315 Dec 25, 2022
Easy and secure implementation of Azure AD for your FastAPI APIs 🔒 Single- and multi-tenant support.

Easy and secure implementation of Azure AD for your FastAPI APIs 🔒 Single- and multi-tenant support.

Intility 220 Jan 05, 2023
Social auth made simple

Python Social Auth Python Social Auth is an easy-to-setup social authentication/registration mechanism with support for several frameworks and auth pr

Matías Aguirre 2.8k Dec 24, 2022
Cack facebook tidak login

Cack facebook tidak login

Angga Kurniawan 5 Dec 12, 2021
Django Rest Framework App wih JWT Authentication and other DRF stuff

Django Queries App with JWT authentication, Class Based Views, Serializers, Swagger UI, CI/CD and other cool DRF stuff API Documentaion /swagger - Swa

Rafael Salimov 4 Jan 29, 2022
A fully tested, abstract interface to creating OAuth clients and servers.

Note: This library implements OAuth 1.0 and not OAuth 2.0. Overview python-oauth2 is a python oauth library fully compatible with python versions: 2.6

Joe Stump 3k Jan 02, 2023
Simple extension that provides Basic, Digest and Token HTTP authentication for Flask routes

Flask-HTTPAuth Simple extension that provides Basic and Digest HTTP authentication for Flask routes. Installation The easiest way to install this is t

Miguel Grinberg 1.1k Jan 05, 2023
A wagtail plugin to replace the login by an OAuth2.0 Authorization Server

Wagtail OAuth2.0 Login Plugin to replace Wagtail default login by an OAuth2.0 Authorization Server. What is wagtail-oauth2 OAuth2.0 is an authorizatio

Gandi 7 Oct 07, 2022
JSON Web Token Authentication support for Django REST Framework

REST framework JWT Auth Notice This project is currently unmaintained. Check #484 for more details and suggested alternatives. JSON Web Token Authenti

José Padilla 3.2k Dec 31, 2022
Basic auth for Django.

easy-basicauth WARNING! THIS LIBRARY IS IN PROGRESS! ANYTHING CAN CHANGE AT ANY MOMENT WITHOUT ANY NOTICE! Installation pip install easy-basicauth Usa

bichanna 2 Mar 25, 2022
Authentication testing framework

What is this This is a framework designed to test authentication for web applications. While web proxies like ZAProxy and Burpsuite allow authenticate

DigeeX 140 Jul 06, 2022
REST implementation of Django authentication system.

djoser REST implementation of Django authentication system. djoser library provides a set of Django Rest Framework views to handle basic actions such

Sunscrapers 2.2k Jan 01, 2023
Django x Elasticsearch Templates

Django x Elasticsearch Requirements Python 3.7 Django = 3 Elasticsearch 7.15 Setup Elasticsearch Install via brew Install brew tap elastic/tap brew

Aji Pratama 0 May 22, 2022
This is a Python library for accessing resources protected by OAuth 2.0.

This is a client library for accessing resources protected by OAuth 2.0. Note: oauth2client is now deprecated. No more features will be added to the l

Google APIs 787 Dec 13, 2022
A simple model based API maker written in Python and based on Django and Django REST Framework

Fast DRF Fast DRF is a small library for making API faster with Django and Django REST Framework. It's easy and configurable. Full Documentation here

Mohammad Ashraful Islam 18 Oct 05, 2022
A simple username/password database authentication solution for Streamlit

TL;DR: This is a simple username/password login authentication solution using a backing database. Both SQLite and Airtable are supported.

Arvindra 49 Nov 25, 2022
Login System Using Django

Login System Django

Nandini Chhajed 6 Dec 12, 2021
Script that provides your TESLA access_token and refresh_token

TESLA tokens This script helps you get your TESLA access_token and refresh_token in order to connect to third party applications (Teslamate, TeslaFi,

Bun-Ny TAN 3 Apr 28, 2022
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
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