Squashed 'utils_v2/' content from commit 13ad158
git-subtree-dir: utils_v2 git-subtree-split: 13ad1588815ba34a57089927a5618f683d44cd29
This commit is contained in:
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,245 @@
|
||||
"""
|
||||
|
||||
AUTHOR:
|
||||
|
||||
Khushal P Soonderji
|
||||
|
||||
DATE:
|
||||
|
||||
Monday, 26th Aug., 2024
|
||||
|
||||
OBJECTIVE:
|
||||
|
||||
To provide an easy way to hash inputs.
|
||||
|
||||
REFERENCES:
|
||||
|
||||
1) Book: Full Stack Python Security - Dennis Byrne
|
||||
|
||||
DOWNLOADS:
|
||||
|
||||
N/A
|
||||
|
||||
"""
|
||||
|
||||
|
||||
# *****************************************************************************************************************
|
||||
# ***** ****
|
||||
# *** IMPORT ***
|
||||
# ***** ****
|
||||
# *****************************************************************************************************************
|
||||
|
||||
|
||||
# For hashing:
|
||||
import hashlib
|
||||
import hmac
|
||||
import secrets
|
||||
from bcrypt import hashpw, gensalt
|
||||
|
||||
# To work with buffers:
|
||||
import io
|
||||
|
||||
|
||||
# *****************************************************************************************************************
|
||||
# ***** ****
|
||||
# *** MACROS / ONE-TIME INIT ***
|
||||
# ***** ****
|
||||
# *****************************************************************************************************************
|
||||
|
||||
|
||||
# --- Nothing Yet
|
||||
|
||||
|
||||
# *****************************************************************************************************************
|
||||
# ***** ****
|
||||
# *** VARIABLES ***
|
||||
# ***** ****
|
||||
# *****************************************************************************************************************
|
||||
|
||||
|
||||
# --- Nothing Yet
|
||||
|
||||
|
||||
# *****************************************************************************************************************
|
||||
# ***** ****
|
||||
# *** CLASSES ***
|
||||
# ***** ****
|
||||
# *****************************************************************************************************************
|
||||
|
||||
|
||||
class Hasher:
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
algorithm = hashlib.sha256,
|
||||
key = None
|
||||
):
|
||||
|
||||
"""
|
||||
hashes data or a message or a file. Uses HMAC if 'key' is specified, else performs simple hashing.
|
||||
:param algorithm: The algorithm to use. SHA256 by default.
|
||||
:param key: Specify this as either a string or as bytes to use HMAC. Leave as null for simple hashing.
|
||||
"""
|
||||
|
||||
# Initialize the hasher:
|
||||
self.__hasher = None,
|
||||
self.__algorithm = algorithm
|
||||
self.__hmac_key = key
|
||||
if self.__hmac_key is not None:
|
||||
self.__hmac_key = self.__hmac_key.encode("utf-8") if isinstance(self.__hmac_key, str) else self.__hmac_key
|
||||
self.reset()
|
||||
|
||||
@staticmethod
|
||||
def generate_key(byte_count = 32, url_safe = False):
|
||||
|
||||
"""
|
||||
A mechanism to generate key/salt values.
|
||||
NOTE: For a proper salt for passwords, I recommend using "generate_salt" method. It's far better.
|
||||
:param byte_count: The number of bytes to have in the key. The hex output (as string) will have 2x the
|
||||
characters.
|
||||
:param url_safe: Set to True if you need the generated output to be a part of a URL.
|
||||
:return: The generate key/salt.
|
||||
"""
|
||||
|
||||
return secrets.token_urlsafe(byte_count) if url_safe else secrets.token_hex(byte_count)
|
||||
|
||||
@staticmethod
|
||||
def generate_salt():
|
||||
|
||||
"""
|
||||
Generate a salt to use while hashing things like passwords.
|
||||
:return: The salt as bytes.
|
||||
"""
|
||||
|
||||
return gensalt()
|
||||
|
||||
@staticmethod
|
||||
def hash_password(password, salt):
|
||||
|
||||
"""
|
||||
Hashes a password with the given salt.
|
||||
:param password: The password to hash, either as a string or as bytes.
|
||||
:param salt: The salt to hash the password with, either as a string or as bytes.
|
||||
:return: The hashed string.
|
||||
"""
|
||||
|
||||
return hashpw(
|
||||
password = password.encode("utf-8") if isinstance(password, str) else password,
|
||||
salt = salt.encode("utf-8") if isinstance(salt, str) else salt
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def compare_hashes(hash_0, hash_1):
|
||||
|
||||
"""
|
||||
Compares two hashes to see if they match.
|
||||
Comparison is done in constant time to avoid timing-based side-channel attacks.
|
||||
:param hash_0: One of the hashes to compare.
|
||||
:param hash_1: The other hash to compare.
|
||||
:return: True if they match, else False.
|
||||
"""
|
||||
|
||||
return hmac.compare_digest(hash_0, hash_1)
|
||||
|
||||
def reset(self):
|
||||
|
||||
"""
|
||||
Resets the hasher by removing all the data that was fed into it.
|
||||
:return: None.
|
||||
"""
|
||||
|
||||
if self.__hmac_key is not None:
|
||||
self.__hasher = hmac.new(
|
||||
key = self.__hmac_key,
|
||||
digestmod = self.__algorithm
|
||||
)
|
||||
else: self.__hasher = self.__algorithm()
|
||||
|
||||
def update(self, data):
|
||||
|
||||
"""
|
||||
Adds data to the hash to update it.
|
||||
:param data: The data to be hashed.
|
||||
:return: None.
|
||||
"""
|
||||
|
||||
data = data.encode("utf-8") if isinstance(data, str) else data
|
||||
self.__hasher.update(data)
|
||||
|
||||
def digest(self):
|
||||
|
||||
"""
|
||||
Returns the hexadecimal representation of the hash as a string.
|
||||
:return: The hexadecimal representation of the hash as a string
|
||||
"""
|
||||
|
||||
return self.__hasher.digest()
|
||||
|
||||
def hexdigest(self):
|
||||
|
||||
"""
|
||||
Returns the hexadecimal representation of the hash as a string.
|
||||
:return: The hexadecimal representation of the hash as a string
|
||||
"""
|
||||
|
||||
return self.__hasher.hexdigest()
|
||||
|
||||
def hash_message(self, message, as_hex = True):
|
||||
|
||||
"""
|
||||
Hashes one message and returns the result, and then resets the instance.
|
||||
:param message: The data you want to hash.
|
||||
:param as_hex: Invokes 'hexdigest' if True, else 'digest'.
|
||||
:return: The hash of the message in either hexadecimal string form or binary form.
|
||||
"""
|
||||
|
||||
self.update(message)
|
||||
hash_result = self.hexdigest() if as_hex else self.digest()
|
||||
self.reset()
|
||||
return hash_result
|
||||
|
||||
def hash_file(self, file, chunk_size = 4096, as_hex = True):
|
||||
|
||||
"""
|
||||
Hashes one file and returns the result, and then resets the instance.
|
||||
:param file: The file you want to hash either as a path or as some buffer (like io.BytesIO).
|
||||
:param chunk_size: The size of data (in bytes) that you would like to pick at one time.
|
||||
:param as_hex: Invokes 'hexdigest' if True, else 'digest'.
|
||||
:return: The hash of the file in either hexadecimal string form or binary form.
|
||||
"""
|
||||
|
||||
# In case the file was given as a io.BytesIO buffer:
|
||||
if isinstance(file, io.BytesIO):
|
||||
file.seek(0)
|
||||
while True:
|
||||
chunk = file.read(chunk_size)
|
||||
if not chunk: break
|
||||
self.update(chunk)
|
||||
|
||||
# In case the file was given as a path:
|
||||
else:
|
||||
with open(file, "rb") as f:
|
||||
for chunk in iter(lambda: f.read(chunk_size), b""):
|
||||
self.update(chunk)
|
||||
|
||||
# Now we capture the results, reset the instance, and return the result:
|
||||
hash_result = self.hexdigest() if as_hex else self.digest()
|
||||
self.reset()
|
||||
return hash_result
|
||||
|
||||
|
||||
# *****************************************************************************************************************
|
||||
# ***** ****
|
||||
# *** MAIN PROGRAM ***
|
||||
# ***** ****
|
||||
# *****************************************************************************************************************
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
print(
|
||||
Hasher.hash_password(
|
||||
password = "mic test, mic test, 123",
|
||||
salt = Hasher.generate_salt()
|
||||
)
|
||||
)
|
||||
+156
@@ -0,0 +1,156 @@
|
||||
"""
|
||||
|
||||
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
|
||||
@@ -0,0 +1,135 @@
|
||||
"""
|
||||
|
||||
AUTHOR:
|
||||
|
||||
Khushal P Soonderji
|
||||
|
||||
DATE:
|
||||
|
||||
Thursday, 25th Jul., 2024
|
||||
|
||||
OBJECTIVE:
|
||||
|
||||
To provide a set of data cleaning functions for inputs like phone numbers, emails, etc.
|
||||
|
||||
REFERENCES:
|
||||
|
||||
N/A
|
||||
|
||||
DOWNLOADS:
|
||||
|
||||
N/A
|
||||
|
||||
"""
|
||||
|
||||
|
||||
# *****************************************************************************************************************
|
||||
# ***** ****
|
||||
# *** IMPORT ***
|
||||
# ***** ****
|
||||
# *****************************************************************************************************************
|
||||
|
||||
|
||||
# To make sibling directories accessible for imports:
|
||||
import sys
|
||||
sys.path.append(".")
|
||||
sys.path.append("..")
|
||||
|
||||
# My utils:
|
||||
from utils_v2.string import regex
|
||||
|
||||
# For random strings and tokens:
|
||||
import string
|
||||
import random
|
||||
|
||||
|
||||
# *****************************************************************************************************************
|
||||
# ***** ****
|
||||
# *** MACROS / ONE-TIME INIT ***
|
||||
# ***** ****
|
||||
# *****************************************************************************************************************
|
||||
|
||||
|
||||
# --- Nothing Yet
|
||||
|
||||
|
||||
# *****************************************************************************************************************
|
||||
# ***** ****
|
||||
# *** VARIABLES ***
|
||||
# ***** ****
|
||||
# *****************************************************************************************************************
|
||||
|
||||
|
||||
# --- Nothing Yet
|
||||
|
||||
|
||||
# *****************************************************************************************************************
|
||||
# ***** ****
|
||||
# *** FUNCTIONS ***
|
||||
# ***** ****
|
||||
# *****************************************************************************************************************
|
||||
|
||||
|
||||
def file_name(input_string: str):
|
||||
|
||||
"""
|
||||
Cleans up the string to allow it to safely become a filename.
|
||||
:param input_string: The string that you want to make safe for using as a filename.
|
||||
:return: The string that can safely be used as a filename.
|
||||
"""
|
||||
|
||||
return regex.replace(
|
||||
text = input_string.replace("\n", " "),
|
||||
pattern = r"[^a-zA-Z0-9 \-_\.]",
|
||||
substitute_text = ""
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
def for_mongo(input_document):
|
||||
|
||||
"""
|
||||
Sanitizes and disarms any JSON-like input that could be used for NoSQL-injection attacks.
|
||||
:param input_document: The list or dict to be sanitized.
|
||||
:return: The sanitized list or dict.
|
||||
"""
|
||||
|
||||
# A special function that disarms any input string by dealing with special characters
|
||||
# that Mongo may consider to be instructions:
|
||||
def disarm(input_string):
|
||||
input_string = regex.replace(
|
||||
text = input_string,
|
||||
pattern = r"[^a-zA-Z0-9,_\-\.\\\/:;'\(\) ]",
|
||||
substitute_text = ""
|
||||
)
|
||||
return input_string
|
||||
|
||||
# Initially we assign the value of the input to the output:
|
||||
sanitized_document = input_document
|
||||
|
||||
# Handle the case where the input is an array:
|
||||
if isinstance(input_document, list):
|
||||
sanitized_document = [for_mongo(document) for document in input_document]
|
||||
|
||||
# Handle the case when the input is a document:
|
||||
elif isinstance(input_document, dict):
|
||||
sanitized_document = {}
|
||||
for k, v in input_document.items():
|
||||
sanitized_document[disarm(k)] = v if type(v) not in [list, dict] else for_mongo(v)
|
||||
|
||||
# Done here:
|
||||
return sanitized_document
|
||||
|
||||
|
||||
# *****************************************************************************************************************
|
||||
# ***** ****
|
||||
# *** MAIN PROGRAM ***
|
||||
# ***** ****
|
||||
# *****************************************************************************************************************
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
pass
|
||||
Reference in New Issue
Block a user