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
JSON Web Token Authentication support for Django REST Framework

REST framework JWT Auth JSON Web Token Authentication support for Django REST Framework Overview This package provides JSON Web Token Authentication s

Styria Digital Development 178 Jan 02, 2023
User-related REST API based on the awesome Django REST Framework

Django REST Registration User registration REST API, based on Django REST Framework. Documentation Full documentation for the project is available at

Andrzej Pragacz 399 Jan 03, 2023
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

Hassan Shahzad 12 Dec 16, 2022
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
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 997 Jan 06, 2023
Beihang University Network Authentication Login

北航自动网络认证使用说明 主文件 gw_buaa.py # @file gw_buaa.py # @author Dong # @date 2022-01-25 # @email windcicada 0 Jul 22, 2022

python-social-auth and oauth2 support for django-rest-framework

Django REST Framework Social OAuth2 This module provides OAuth2 social authentication support for applications in Django REST Framework. The aim of th

1k Dec 22, 2022
Kube OpenID Connect is an application that can be used to easily enable authentication flows via OIDC for a kubernetes cluster

Kube OpenID Connect is an application that can be used to easily enable authentication flows via OIDC for a kubernetes cluster. Kubernetes supports OpenID Connect Tokens as a way to identify users wh

7 Nov 20, 2022
OAuth2 goodies for the Djangonauts!

Django OAuth Toolkit OAuth2 goodies for the Djangonauts! If you are facing one or more of the following: Your Django app exposes a web API you want to

Jazzband 2.7k Dec 31, 2022
Simple Login - Login Extension for Flask - maintainer @cuducos

Login Extension for Flask The simplest way to add login to flask! How it works First, install it from PyPI: $ pip install flask_simplelogin Then, use

Flask Extensions 181 Jan 01, 2023
A Python package, that allows you to acquire your RecNet authorization bearer token with your account credentials!

RecNet-Login This is a Python package, that allows you to acquire your RecNet bearer token with your account credentials! Installation Done via git: p

Jesse 6 Aug 18, 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
Quick and simple security for Flask applications

Note This project is non maintained anymore. Consider the Flask-Security-Too project as an alternative. Flask-Security It quickly adds security featur

Matt Wright 1.6k Dec 19, 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.

9 Sep 19, 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
Luca Security Concept

Luca Security Concept This is the document source of luca's security concept. Please go here for the HTML version: https://luca-app.de/securityconcept

luca 43 Oct 22, 2022
Foundation Auth Proxy is an abstraction on Foundations' authentication layer and is used to authenticate requests to Atlas's REST API.

foundations-auth-proxy Setup By default the server runs on http://0.0.0.0:5558. This can be changed via the arguments. Arguments: '-H' or '--host': ho

Dessa - Open Source 2 Jul 03, 2020
Authentication with fastapi and jwt cd realistic

Authentication with fastapi and jwt cd realistic Dependencies bcrypt==3.1.7 data

Fredh Macau 1 Jan 04, 2022
Simple two factor authemtication system, made by me.

Simple two factor authemtication system, made by me. Honestly, i don't even know How 2FAs work I just used my knowledge and did whatever i could. Send

Refined 5 Jan 04, 2022
🔐 Login & Register System

🔐 Login & Register System This is a developable login and register system. Enter your username and password to register or login to account. Automati

Firdevs Akbayır 10 Dec 12, 2022