""" AUTHOR: Khushal P Soonderji DATE: Tuesday, 10th Sept., 2024 OBJECTIVE: To provide a way to generate and verify OTPs. REFERENCES: 01. https://pyauth.github.io/pyotp/# 02. https://en.wikipedia.org/wiki/Google_Authenticator DOWNLOADS: N/A """ # ***************************************************************************************************************** # ***** **** # *** IMPORT *** # ***** **** # ***************************************************************************************************************** # To make sibling directories accessible for imports: import sys sys.path.append(".") sys.path.append("..") # To work with OTPs: import pyotp import base64 import hashlib # To work with date and time: import time import datetime # For debugging: from icecream import IceCreamDebugger # ***************************************************************************************************************** # ***** **** # *** MACROS / ONE-TIME INIT *** # ***** **** # ***************************************************************************************************************** # --- Nothing Yet # ***************************************************************************************************************** # ***** **** # *** VARIABLES *** # ***** **** # ***************************************************************************************************************** # --- Nothing Yet # ***************************************************************************************************************** # ***** **** # *** CLASSES *** # ***** **** # ***************************************************************************************************************** class HashedOTP: def __init__( self, secret ): """ Used to generate and verify HMAC-based OTPs. :param secret: The key to use to generate and verify OTPs. """ # Note down the input variables: self.__secret = secret self.__otp_client = pyotp.HOTP(secret) @staticmethod def generate_secret(message = None): """ Generate a secret key to then use to generate and verify the OTPs. You may override the random generator by giving a "message" of any length. :param message: A custom value to convert into a key. Avoid using this for better security, but this can be used to generate keys based on user identifiers. THERE IS NO RANDOMNESS IF YOU USE THIS FEATURE. IT IS FOR CONVENIENCE ONLY. NOT RECOMMENDED. :return: The key (as a string) that can be used to generate and verify the OTPs. """ # If the user wants to generate a key from a custom input: if message: # Ensure we have a bytes object: if not isinstance(message, (str, bytes)): message = str(message) if isinstance(message, str): message = message.encode("utf-8") # Hash the bytes object: sha256_hash = hashlib.sha256() sha256_hash.update(message) hashed_key = sha256_hash.digest() # Convert to base-32: return base64.b32encode(hashed_key).decode("utf-8") # If the user wants a totally random key: else: return pyotp.random_base32() def generate_otp(self, count: int): """ Generates the OTP at a particular step. :param count: The step at which the OTP needs to be generated. :return: The OTP string (6 digits). """ return str(self.__otp_client.at(count)) def verify_otp(self, otp, count: int): """ Verifies the claimed OTP. :param otp: The OTP as claimed by the end user. :param count: The step at which the OTP needs to be verified. :return: True if the OTP is valid, else False. """ return self.__otp_client.verify(otp, counter = count) # ***************************************************************************************************************** # ***** **** # *** MAIN PROGRAM *** # ***** **** # ***************************************************************************************************************** if __name__ == "__main__": pass