Skip to content

Instantly share code, notes, and snippets.

@alexrecuenco
Last active September 4, 2023 23:00
Show Gist options
  • Save alexrecuenco/892bbc0dd740693854c9d55874978fed to your computer and use it in GitHub Desktop.
Save alexrecuenco/892bbc0dd740693854c9d55874978fed to your computer and use it in GitHub Desktop.
Memorable passwords with python secrets
#!/usr/bin/env python
# Imitates a bit the functionality that used to exist on macos.
# This sketch returns a entropy estimate for the password.
from math import log2
import secrets
import string
import sys
from typing import Tuple
from warnings import warn
import argparse
def rand_words(word_list: list[str], n: int):
# The entropy here could be not accurate, since some words could be contained in others.
return [secrets.choice(word_list).strip() for i in range(n)], n * log2(
len(word_list)
)
def rand_symbols(n: int):
return [secrets.choice(string.punctuation) for i in range(n)], n * log2(
len(string.punctuation)
)
def rand_hex(n: int):
if n % 2 != 0:
warn("Number must be even, otherwise you'll get issues")
return secrets.token_hex(n // 2), n // 2 * log2(16)
def rand_number(exclusive_max: int):
return secrets.randbelow(exclusive_max), log2(exclusive_max)
def rand_password(word_list: list[str]) -> Tuple[str, int]:
(word1, word2), entropy1 = rand_words(word_list, 2)
(sym1, sym2), entropy2 = rand_symbols(2)
number_content, entropy3 = rand_number(1_000_000)
return f"{word1}{sym1}{number_content}{sym2}{word2}", entropy1 + entropy2 + entropy3
NUMBER = "number"
SYM = "symbol"
HEX = "hex"
WORDS = "words"
def get_word_list(file: str):
if file:
return open(sys.argv[1]).readlines()
print("STDIN used, example: script.py < /usr/share/dict/words")
return sys.stdin.read().splitlines()
def pretty_print_entropy(password: list[str] | str, entropy: float):
if isinstance(password, list):
match = "\n\t — "
return f"""[{entropy=}] chosen {match}{match.join(password)}"""
return f"[{entropy=}] chosen {password=}"
def main():
parser = argparse.ArgumentParser("secret_pasword.py")
parser.add_argument(
"--type", required=False, default=None, choices=[NUMBER, SYM, HEX, WORDS]
)
parser.add_argument("--n", required=False, default=5, type=int)
parser.add_argument("file", default="", nargs="?")
args = parser.parse_args()
if args.type == NUMBER:
print(pretty_print_entropy(*rand_number(10**args.n)))
return
if args.type == SYM:
print(pretty_print_entropy(*rand_symbols(args.n)))
return
if args.type == HEX:
print(pretty_print_entropy(*rand_hex(args.n)))
return
word_list = get_word_list(args.file)
if args.type == WORDS:
print(pretty_print_entropy(*rand_words(word_list, args.n)))
return
password, entropy = rand_password(word_list)
print(pretty_print_entropy(password, entropy))
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment