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
Customizable User Authorization & User Management: Register, Confirm, Login, Change username/password, Forgot password and more.

Flask-User v1.0 Attention: Flask-User v1.0 is a Production/Stable version. The previous version is Flask-User v0.6. User Authentication and Management

Ling Thio 916 Feb 15, 2021
This python package provides a simple password reset strategy for django rest framework

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
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
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
Graphical Password Authentication System.

Graphical Password Authentication System. This is used to increase the protection/security of a website. Our system is divided into further 4 layers of protection. Each layer is totally different and diverse than the others. This not only increases protection, but also makes sure that no non-human can log in to your account using different activities such as Brute Force Algorithm and so on.

Hassan Shahzad 12 Dec 16, 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
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
This program automatically logs you into a Zoom session at your alloted time

This program automatically logs you into a Zoom session at your alloted time. Optionally you can choose to have end the session at your allotted time.

null 9 Sep 19, 2022
The ultimate Python library in building OAuth, OpenID Connect clients and servers. JWS,JWE,JWK,JWA,JWT included.

Authlib The ultimate Python library in building OAuth and OpenID Connect servers. JWS, JWK, JWA, JWT are included. Authlib is compatible with Python2.

Hsiaoming Yang 3.4k Jan 4, 2023
Google Auth Python Library

Google Auth Python Library This library simplifies using Google's various server-to-server authentication mechanisms to access Google APIs. Installing

Google APIs 598 Jan 7, 2023
Simple yet powerful authorization / authentication client library for Python web applications.

Authomatic Authomatic is a framework agnostic library for Python web applications with a minimalistic but powerful interface which simplifies authenti

null 1k Dec 28, 2022
Django CAS 1.0/2.0/3.0 client authentication library, support Django 2.0, 2.1, 2.2, 3.0 and Python 3.5+

django-cas-ng django-cas-ng is Django CAS (Central Authentication Service) 1.0/2.0/3.0 client library to support SSO (Single Sign On) and Single Logou

django-cas-ng 347 Dec 18, 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 Python library for OAuth 1.0/a, 2.0, and Ofly.

Rauth A simple Python OAuth 1.0/a, OAuth 2.0, and Ofly consumer library built on top of Requests. Features Supports OAuth 1.0/a, 2.0 and Ofly Service

litl 1.6k Dec 8, 2022
The ultimate Python library in building OAuth, OpenID Connect clients and servers. JWS,JWE,JWK,JWA,JWT included.

Authlib The ultimate Python library in building OAuth and OpenID Connect servers. JWS, JWK, JWA, JWT are included. Authlib is compatible with Python2.

Hsiaoming Yang 2.3k Feb 17, 2021
Simple yet powerful authorization / authentication client library for Python web applications.

Authomatic Authomatic is a framework agnostic library for Python web applications with a minimalistic but powerful interface which simplifies authenti

null 962 Feb 4, 2021
Flask JWT Router is a Python library that adds authorised routes to a Flask app.

Read the docs: Flask-JWT-Router Flask JWT Router Flask JWT Router is a Python library that adds authorised routes to a Flask app. Both basic & Google'

Joe Gasewicz 52 Jan 3, 2023
Simple yet powerful authorization / authentication client library for Python web applications.

Authomatic Authomatic is a framework agnostic library for Python web applications with a minimalistic but powerful interface which simplifies authenti

null 962 Feb 19, 2021
Imia is an authentication library for Starlette and FastAPI (python 3.8+).

Imia Imia (belarussian for "a name") is an authentication library for Starlette and FastAPI (python 3.8+). Production status The library is considered

Alex Oleshkevich 91 Nov 24, 2022