Two-Factor Authentication with Time-based One-Time Passwords (TOTP)
Time-based One-Time Passwords (TOTP) is a widely used method for enhancing the security of user authentication. It is a two-factor authentication method that requires both something you know (your password) and something you have (a device that generates the TOTP) to access an account. TOTP is an extension of the well-known HOTP (HMAC-based One-Time Passwords) algorithm, which uses the current time as the moving factor.
TOTP algorithm relies on a shared secret key between the user’s device (e.g. mobile phone) and the server. The secret key is used to generate a unique one-time password (OTP) that changes based on the current time. The OTP is valid for a certain period of time, usually around 30-60 seconds, and becomes invalid after that.
The basic process of TOTP algorithm is as follows:
- The user’s device generates a timestamp (usually the number of seconds since the epoch).
- The user’s device generates an HMAC (Hash-based Message Authentication Code) of the timestamp using the shared secret key.
- The user’s device extracts a 4-byte binary string from the HMAC’s digest.
- The user’s device converts the 4-byte binary string to an integer.
- The user’s device uses the last 6 digits of the integer as the OTP.
SHA1 Algorithm
import hmac
import hashlib
import base64
import struct
import time
def generate_totp(secret, interval=30):
# Get the current time in seconds since the epoch
timestamp = int(time.time() / interval)
# Encode the timestamp as a big-endian 8-byte binary string
timestamp_bytes = struct.pack(">q", timestamp)
# Generate the HMAC-SHA1 of the timestamp using the secret
hmac_sha1 = hmac.new(secret, timestamp_bytes, hashlib.sha1)
# Extract the 4-byte binary string from the HMAC's digest
hmac_binary = hmac_sha1.digest()[-4:]
# Convert the 4-byte binary string to an integer
code = struct.unpack(">L", hmac_binary)[0]
# Use the last 6 digits of the code as the TOTP
return str(code % 10 ** 6).zfill(6)
# example usage
secret = b'your_secret_key'
print(generate_totp(secret))
This example uses the hmac
and hashlib
libraries to generate the HMAC-SHA1 of the timestamp using the secret. It then extracts the last 4 bytes of the digest and converts them to an integer. The last 6 digits of the integer are used as the TOTP. The interval parameter determines the time period for which the TOTP is valid.
The most important thing that should be considered when implementing TOTP is the security of the shared secret key. The shared secret key should be stored in a secure way, such as storing it in a secure element or a HSM. Also, it’s highly recommended to use a stronger algorithm like SHA256 or SHA512 rather than SHA1 which is less secure.
SHA256 or SHA512
import hmac
import hashlib
import base64
import struct
import time
def generate_totp(secret, algorithm='sha256', interval=30):
# Get the current time in seconds since the epoch
timestamp = int(time.time() / interval)
# Encode the timestamp as a big-endian 8-byte binary string
timestamp_bytes = struct.pack(">q", timestamp)
# Choose the algorithm
if algorithm == 'sha256':
hmac_algo = hashlib.sha256
elif algorithm == 'sha512':
hmac_algo = hashlib.sha512
else:
raise ValueError("Invalid algorithm")
# Generate the HMAC of the timestamp using the secret and chosen algorithm
hmac_algo = hmac.new(secret, timestamp_bytes, hmac_algo)
# Extract the 4-byte binary string from the HMAC's digest
hmac_binary = hmac_algo.digest()[-4:]
# Convert the 4-byte binary string to an integer
code = struct.unpack(">L", hmac_binary)[0]
# Use the last 6 digits of the code as the TOTP
return str(code % 10 ** 6).zfill(6)
# example usage
secret = b'your_secret_key'
print(generate_totp(secret, algorithm='sha256'))
print(generate_totp(secret, algorithm='sha512'))
The algorithm
parameter determines the algorithm to be used (sha256 or sha512).
Another important aspect to consider when implementing TOTP is to use a library that provides a tested and secure implementation of the TOTP algorithm, such as pyotp or python-otp.
When using TOTP, it’s also important to keep in mind that the implementation should be compliant with the security regulations and standards.
TOTP algorithm in a Django REST framework application
Here’s an example of how you could use the TOTP algorithm in a Django REST framework application to generate one-time passwords (OTPs) for users that change every 60 seconds:
-
Create a new Django app called
otp
and add it to theINSTALLED_APPS
list in the project’s settings.py. -
Create a model for storing the user’s secret key and the last time an OTP was generated.
from django.db import models
from django.contrib.auth.models import User
class UserOTP(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
secret = models.CharField(max_length=100)
last_generated = models.DateTimeField(auto_now=True)
- Create a serializer for the
UserOTP
model.
from rest_framework import serializers
from .models import UserOTP
class UserOTPSerializer(serializers.ModelSerializer):
class Meta:
model = UserOTP
fields = ('user', 'secret', 'last_generated')
- Create a
ViewSet
for handling the OTP generation
from rest_framework import viewsets
from .models import UserOTP
from .serializers import UserOTPSerializer
import hmac
import hashlib
import base64
import struct
import time
class UserOTPViewSet(viewsets.ModelViewSet):
queryset = UserOTP.objects.all()
serializer_class = UserOTPSerializer
def generate_totp(self,secret,interval = 60):
timestamp = int(time.time() / interval)
timestamp_bytes = struct.pack(">q", timestamp)
hmac_sha256 = hmac.new(secret, timestamp_bytes, hashlib.sha256)
hmac_binary = hmac_sha256.digest()[-4:]
code = struct.unpack(">L", hmac_binary)[0]
return str(code % 10 ** 6).zfill(6)
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
secret = serializer.validated_data['secret']
otp = self.generate_totp(secret.encode())
serializer.save(otp=otp)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
This is just an example on how you could use the TOTP algorithm in a Django REST framework application to generate one-time passwords that change every 60 seconds.
You also should consider adding a way to send the OTP to the user, i.e. via email, SMS, etc.
Please keep in mind that this is just an example and it’s not recommended to use this implementation in a real-world scenario without proper testing and review, also you should make sure that your code is complying with the security standards and best practices.
You should also consider adding additional security measures such as rate limiting on the OTP generation endpoint and logging all the OTP generation requests.
You can also consider adding a way to invalidate/revoke OTPs if needed, for example, after a successful login or if the user requests a new OTP.
You also could consider using a library such as pyotp or python-otp to handle the OTP generation and validation, as they provide a tested and secure implementation of the TOTP algorithm.
In general, implementing two-factor authentication is a complex task and require a thorough understanding of security best practices, and should be done by experts in the field.
In summary, TOTP is a strong method for enhancing the security of user authentication and it’s widely used in many systems. It is a simple, yet effective method for providing two-factor authentication by requiring both something you know (your password) and something you have (a device that generates the TOTP). However, it’s important to keep in mind that the security of the shared secret key and the compliance with the security regulations and standards should be considered when implementing TOTP.