Files
sshworkshop/namepicker/main.py
2025-11-04 21:10:31 +01:00

92 lines
3.5 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from bs4 import BeautifulSoup
import httpx, lxml # need lxml as bs4 parser
from pprint import pp
import pandas as pd
from typing import Set
import random
from io import StringIO
def fetch_names_table() -> pd.DataFrame:
"""Grab the list of most common names across the last century from the american social security agency SSA
and parse it out as a pandas dataframe"""
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36",
"Accept-Language": "en-US,en;q=0.9,de;q=0.8",
}
page = httpx.get('https://www.ssa.gov/OACT/babynames/decades/century.html', headers=headers)
soup = BeautifulSoup(page.content, 'lxml')
table = soup.find('table')
with StringIO(str(table)) as f:
df = pd.read_html(f)[0]
return df
def make_names_set() -> Set[str]:
"""extract the male & female names from the SSA table and lowercase them into a single set (to be 100% unique, even if
they should already be from the source material)"""
my_names = fetch_names_table()
return set([ n.lower() for n in [ n for n in my_names['Females']['Name'] ] + [ n for n in my_names['Males']['Name'] ] if ' ' not in n ])
def get_randomized_names_list() -> list[str]:
"""takes the unique (ordered) set of common names and returns a shuffled version as a list to be used for
sequentially assigning randomized user names to containers"""
names = list(make_names_set())
random.shuffle(names)
return(names)
def sha512_crypt(password: str, salt: bytes = None) -> str:
"""
Produce a SHA512 cryptstyle hash (like ``crypt.crypt(..., METHOD_SHA512)``)
using only ``hashlib``.
The output format matches the traditional ``$6$salt$hash`` string.
"""
# Generate a 16byte salt if none is provided
if salt is None:
salt = os.urandom(16)
# Encode password and salt as bytes
pwd_bytes = password.encode("utf-8")
# ``crypt`` uses a base64 variant; ``base64`` with ``urlsafe_b64encode``
# and stripping padding gives the same character set.
salt_str = base64.urlsafe_b64encode(salt).rstrip(b"=").decode("ascii")
# Perform the SHA512 hash
hasher = hashlib.sha512()
hasher.update(pwd_bytes + salt)
digest = hasher.digest()
# Encode the digest in the same base64 variant
hash_str = base64.urlsafe_b64encode(digest).rstrip(b"=").decode("ascii")
# Return the full cryptstyle string
return f"$6${salt_str}${hash_str}"
def create_password(username: str) -> str:
"""generate a crypted pw hash for this username"""
return sha512_crypt(f'{username}123!')
def my_password(password) -> str:
salt = os.urandom(8)
pwd_hash = sha512_crypt.using(salt=salt, rounds=1000).hash(password)
return pwd_hash
if __name__ == "__main__":
import hashlib
import os
import base64
import yaml
from xkcd_passwords import generate_password
from passlib.hash import sha512_crypt
from pathlib import Path
with Path('../names.yml').open('r') as yaml_file:
names = yaml.safe_load(yaml_file)
# names = get_randomized_names_list()
# name_pw_map = { n: (sha512_crypt(pwd:= generate_password(separator="", capitalize=True).strip())) + ',' + pwd for n in names}
name_pw_map = { n: (my_password(pwd:= generate_password(separator="", capitalize=True).strip())) + ',' + pwd for n in names}
with Path('names.yml').open('w') as yaml_file:
yaml.dump(name_pw_map, yaml_file)
pp(name_pw_map, width=200, compact=True)