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
Simplifying third-party authentication for web applications.

Velruse is a set of authentication routines that provide a unified way to have a website user authenticate to a variety of different identity provider

Ben Bangert 253 Nov 14, 2022
Python's simple login system concept - Advanced level

Simple login system with Python - For beginners Creating a simple login system using python for beginners this repository aims to provide a simple ove

Low_Scarlet 1 Dec 13, 2021
蓝鲸用户管理是蓝鲸智云提供的企业组织架构和用户管理解决方案,为企业统一登录提供认证源服务。

蓝鲸用户管理 简体中文 | English 蓝鲸用户管理是蓝鲸智云提供的企业组织架构和用户管理解决方案,为企业统一登录提供认证源服务。 总览 架构设计 代码目录 功能 支持多层级的组织架构管理 支持通过多种方式同步数据:OpenLDAP、Microsoft Active Directory(MAD)

腾讯蓝鲸 35 Dec 14, 2022
A Login/Registration GUI Application with SQLite database for manipulating data.

Login-Register_Tk A Login/Registration GUI Application with SQLite database for manipulating data. What is this program? This program is a GUI applica

Arsalan 1 Feb 01, 2022
Library - Recent and favorite documents

Thingy Thingy is used to quickly access recent and favorite documents. It's an XApp so it can work in any distribution and many desktop environments (

Linux Mint 23 Sep 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
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
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
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
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 08, 2022
A simple Boilerplate to Setup Authentication using Django-allauth 🚀

A simple Boilerplate to Setup Authentication using Django-allauth, with a custom template for login and registration using django-crispy-forms.

Yasser Tahiri 13 May 13, 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
Login System Using Django

Login System Django

Nandini Chhajed 6 Dec 12, 2021
A host-guest based app in which host can CREATE the room. and guest can join room with room code and vote for song to skip. User is authenticated using Spotify API

A host-guest based app in which host can CREATE the room. and guest can join room with room code and vote for song to skip. User is authenticated using Spotify API

Aman Raj 5 May 10, 2022
Object Moderation Layer

django-oml Welcome to the documentation for django-oml! OML means Object Moderation Layer, the idea is to have a mixin model that allows you to modera

Angel Velásquez 12 Aug 22, 2019
FastAPI Simple authentication & Login API using GraphQL and JWT

JeffQL A Simple FastAPI authentication & Login API using GraphQL and JWT. I choose this Name JeffQL cause i have a Low level Friend with a Nickname Je

Yasser Tahiri 26 Nov 24, 2022
Simple implementation of authentication in projects using FastAPI

Fast Auth Facilita implementação de um sistema de autenticação básico e uso de uma sessão de banco de dados em projetos com tFastAPi. Instalação e con

3 Jan 08, 2022
Spotify User Token Generator Template

Spotify User Token Generator Template Quick Start $ pip3 install -r requirements

Arda Soyer 1 Feb 01, 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
A module making it easier to manage Discord oAuth with Quart

quart_discord A module making it easier to manage Discord oAuth with Quart Install pip install git+https://github.com/xelA/ 5 Oct 27, 2022