92 lines
3.5 KiB
Python
92 lines
3.5 KiB
Python
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 SHA‑512 crypt‑style hash (like ``crypt.crypt(..., METHOD_SHA512)``)
|
||
using only ``hashlib``.
|
||
|
||
The output format matches the traditional ``$6$salt$hash`` string.
|
||
"""
|
||
# Generate a 16‑byte 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 SHA‑512 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 crypt‑style 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)
|