How To Generate OTPs Using PyOTP in Python

How To Generate OTPs Using PyOTP in Python

Generating One-Time Passwords Like a Master with PyOTP

In a digital age filled with hackers and cybersecurity threats where "password123" just doesn't cut it anymore, Two-Factor Authentication (2FA) emerges as the superhero of online security. OTPs are an important part of 2FA. In this article, we'll learn how to create OTPs using Python and the PyOTP library. So, grab your Python wizard hat, and let's dive in!

What are OTPs?

You're no stranger to the scenario: you're online, making that critical purchase, and then it happens - the dreaded login screen. Before you use your trusty "password123," you should know it's not as strong as you think. But don't worry, we've got a superhero in the digital world - OTPs!

Image by storyset on Freepik

An OTP, or One-Time Password, is a temporary code that changes each time you use it. It's like having a new secret handshake every time you want to access your online accounts. This dynamic nature makes OTPs incredibly secure, as they're almost impossible for malicious actors to predict or crack.

Before we dive into the code, let's understand the two main types of OTPs:

1. Time-based OTP (TOTP): These OTPs change at regular time intervals (usually 30 or 60 seconds). Think of it as a constantly evolving secret code that only you and your device know.

2. HMAC-based OTP (HOTP): In contrast, HOTPs are event-based OTPs. They change each time you authenticate, providing an extra layer of security.

How To Generate OTPs Using Python?

In Python, you can generate OTPs using the random library as below:

from random import choice
import string


def generate_otp(number_of_digits):
    otp = ''.join(choice(string.digits) for _ in range(number_of_digits))
    return otp


print(generate_otp(6))

The above generate_otp method uses list comprehension to generate an OTP of the required number of digits. However, there are a few limitations to generating OTPs this way. The generated OTPs are predictable and attackers can easily predict the values. These OTPs do not have a built-in expiration time. For security reasons, OTPs should have a limited validity period to prevent replay attacks. The random module is not intended for cryptographic purposes. For high-security applications, it's better to use a cryptographic library like secrets or dedicated OTP generation libraries that provide cryptographically secure randomness.

This is where libraries such as PytOTP come into the picture. PyOTP is a fantastic Python library that simplifies the creation and verification of OTPs. With PyOTP, you can generate OTPs using Time-based OTP (TOTP) and HMAC-based OTP (HOTP) algorithms effortlessly. So, let's get started!

How to Generate OTPs using PyOTP?

Before you can start generating OTPs, you need to install PyOTP. If you don't have Python installed, go ahead and do that first. Then, open your terminal and run the following command:

pip install pyotp

This command installs PyOTP and prepares you for the exciting journey ahead. Please note that you can also create a virtual environment before installing PyOTP. But for the sake of this simple tutorial, we won't be doing that here.

Time-Based OTPs (TOTPs)

The moving factor in a TOTP is time-based. The amount of time duration for which the OTP is valid is called timestep. If you haven’t used your password within the timestep window, it will no longer be valid, and you’ll need to request a new one.

Let's start by importing the PyOTP library. We'll also create a secret key using the PyOTP library. Remember, this key should be kept as secret as your mother's secret recipe for your favorite food!

import pyotp

# Create a secret key (keep it secret!)
secret_key = pyotp.random_base32()

PyOTP provides a random_base32() function that helps you generate a random secret key in Base32 encoding. It generates a random sequence of bytes, which is essentially a random collection of numbers and letters. It then encodes this random sequence of bytes into Base32 format.

Now, let's generate an OTP using our secret key. We'll use the TOTP algorithm:

# Generate an OTP using TOTP
otp = pyotp.TOTP(secret_key)
otp_code = otp.now()

print("Your Time-based OTP:", otp_code)

That's it! You've just created a Time-based OTP. This code will generate a new OTP every 30 seconds, making it an excellent choice for securing your online accounts.

Your Time-based OTP: 641079

Do you want to verify that the OTP changes after 30 seconds? Let's use the sleep function:

import time
import pyotp

# Create a secret key (keep it secret!)̥
secret_key = pyotp.random_base32()

otp = pyotp.TOTP(secret_key)
# Generate an OTP using TOTP after every 30 seconds
while True:
    print(f"Your Time-based OTP at {time.ctime()}:", otp.now())

    time.sleep(30)

In the above code, you enter an infinite loop (while True) to continuously generate OTPs. Inside the loop, you generate a TOTP and then let the code sleep for 30 seconds. Then after 30 seconds, it again generates the OTP. You will see an output as below:

Your Time-based OTP at Sun Oct  1 11:42:06 2023: 127722
Your Time-based OTP at Sun Oct  1 11:42:36 2023: 582057
Your Time-based OTP at Sun Oct  1 11:43:06 2023: 744459
Your Time-based OTP at Sun Oct  1 11:43:36 2023: 508890

HMAC-based OTPs (HOTPs)

HOTPs are event-based OTPs where the moving factor in each code is based on a counter. Each time the HOTP is requested and validated, the moving factor is incremented based on a counter.

While Time-based OTPs are fantastic, HMAC-based OTPs offer another layer of security. Let's take a look at generating HOTPs.

Start by importing the library and generating the secret key.

import pyotp

# Create a secret key (keep it secret!)̥
secret_key = pyotp.random_base32()

Next, you can generate the OTP as below:

import pyotp

# Create a secret key (keep it secret!)̥
secret_key = pyotp.random_base32()

# Generate an OTP using HOTP
hotp = pyotp.HOTP(secret_key)
otp_code = hotp.at(0)  # You can use different counter values for different OTPs

print("Your HMAC-based OTP:", otp_code)

Output:

Your HMAC-based OTP: 469785

With this code, you'll have a new HMAC-based OTP each time you increment the counter value.

import pyotp

# Create a secret key (keep it secret!)̥
secret_key = pyotp.random_base32()

# Generate an OTP using HOTP
hotp = pyotp.HOTP(secret_key)

print("Your HMAC-based OTP at counter 0:", hotp.at(0))
print("Your HMAC-based OTP at counter 1:", hotp.at(1))
print("Your HMAC-based OTP at counter 2:", hotp.at(2))

Output:

Your HMAC-based OTP at counter 0: 230227
Your HMAC-based OTP at counter 1: 437103
Your HMAC-based OTP at counter 2: 290927

How to Customize the OTP Generation?

Just as you order pizza with customizations like extra cheese and toppings, you can customize the OTPs when generating using PyOTP. You can adjust the length, choose different algorithms, and set expiration periods to suit your needs. These customizations are possible for both the TOTP and HOTP classes.

Let's explore some customization options:

Adjusting OTP Length

By default, PyOTP generates OTPs with 6 digits. However, you can easily customize the length of your OTPs. For example, if you want to generate OTPs with 4 digits, you can do so as follows:

import pyotp

# Create a secret key (keep it secret!)
secret_key = pyotp.random_base32()

# Create a TOTP object with a custom OTP length (e.g., 4 digits)
otp = pyotp.TOTP(secret_key, digits=4)

# Generate and print the OTP
print("You 4-Digit OTP:", otp.now())

Output:

You 4-Digit OTP: 9854

By setting the digits parameter to 4, you've changed the OTP length for this TOTP object to 4 digits instead of the default 6 digits.

Handling OTP Expiration

Sometimes, you might want to set an expiration period for your OTPs. You can do this by specifying the interval parameter when creating a TOTP object. For example, if you want OTPs to expire every 60 seconds, you can configure it like this:

import pyotp

# Create a secret key (keep it secret!)
secret_key = pyotp.random_base32()

# Create a TOTP object with a custom expiration interval (60 seconds)
otp = pyotp.TOTP(secret_key, interval=60)

# Generate and print the OTP
print("OTP with 60-second expiration:", otp.now())

Output:

OTP with 60-second expiration: 144980

To verify this, you can set your code to sleep for 30 seconds, and see that the same OTP is printed twice in a minute:

import time
import pyotp

# Create a secret key (keep it secret!)̥
secret_key = pyotp.random_base32()


otp = pyotp.TOTP(secret_key, interval=60)
# Generate an OTP using TOTP after every 60 seconds
while True:
    print(f"Your Time-based OTP at {time.ctime()}:", otp.now())

    time.sleep(30)

Output:

Your Time-based OTP at Sun Oct  1 12:22:11 2023: 043977
Your Time-based OTP at Sun Oct  1 12:22:41 2023: 043977
Your Time-based OTP at Sun Oct  1 12:23:11 2023: 491773
Your Time-based OTP at Sun Oct  1 12:23:41 2023: 491773
Your Time-based OTP at Sun Oct  1 12:24:11 2023: 468957

There are more customizations available but they are out of scope for this tutorial. By customizing OTP generation in these ways, you can tailor your OTPs to fit your application's security requirements and user experience.

How to Verify the OTPs?

Creating OTPs is just one side of the coin; verifying them is equally important. PyOTP simplifies OTP verification through its verify method. Let's explore how to verify OTPs using PyOTP.

import pyotp

# Create a secret key (keep it secret!)̥
secret_key = pyotp.random_base32()

otp = pyotp.TOTP(secret_key, interval=60)
# Generate an OTP using TOTP after every 30 seconds
print("Your TOTP is: ", otp.now())

user_otp = input("Enter the OTP: ")
if (otp.verify(user_otp)):
    print("Access granted!")
else:
    print("Incorrect OTP")

Output:

Your TOTP is:  747559
Enter the OTP: 747559
Access granted!
Your TOTP is:  676707
Enter the OTP: 123456
Incorrect OTP

By using the verify method, you can easily determine whether the user-provided OTP matches the expected OTP generated from the secret key. If they match, access is granted; otherwise, access is denied.

Wrapping Up

Now that you've got your OTP-generating skills down, you can integrate OTPs into your Python applications. In a world where "password123" just won't cut it anymore, OTPs are your digital knights in shining armor. PyOTP, with its Pythonic charm, makes implementing OTPs a breeze. So, go ahead, give it a try, and keep those cyber villains at bay.

Did you find this article valuable?

Support Ashutosh Krishna by becoming a sponsor. Any amount is appreciated!