3a64ea7efe
git-subtree-dir: utils_v2 git-subtree-split: 8bb584e4734606740c0b42d51bc7fd458c39031a
157 lines
5.8 KiB
Python
157 lines
5.8 KiB
Python
"""
|
|
|
|
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
|