Last active
September 4, 2023 23:00
-
-
Save alexrecuenco/892bbc0dd740693854c9d55874978fed to your computer and use it in GitHub Desktop.
Memorable passwords with python secrets
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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