Advertisement
FocusedWolf

Python: Password Generator

Apr 3rd, 2022 (edited)
552
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 10.33 KB | None | 0 0
  1. #!/usr/bin/env python3
  2.  
  3. # Version 24
  4.  
  5. # POSTED ONLINE: https://pastebin.com/g10jVftx
  6.  
  7. # Windows: $ pip install windows-curses pyperclip
  8. #
  9. # Linux:   $ sudo pacman -S python-pyperclip
  10. #          $ sudo pacman -S wl-clipboard    <-- Pyperclip has issues with KDE + Wayland. Installing this seems to get it to work.
  11. #
  12. #          NOTE: For Linux you also need to install a copy/paste mechanism if you don't have one.
  13. #                KDE has /bin/klipper installed by default. For other window managers try [$ sudo pacman -S xclip] or [$ sudo pacman -S xsel].
  14. #
  15. #          NOTE: If you get error "Cannot find '.setClipboardContents' in object /klipper at org.kde.klipper",
  16. #                its because klipper isn't running on startup. Try Start > klipper to see if the problem goes away.
  17. #
  18. #          NOTE: To display klipper in the taskbar, right click the ^ > Configure System Tray... > Entries > Clipboard: [Show when relevant].
  19.  
  20. import curses
  21. import pyperclip
  22.  
  23. # -----
  24.  
  25. def init_colors():
  26.     # Enable color functionality.
  27.     curses.start_color()
  28.  
  29.     # Define all standard colors with black background.
  30.     curses.init_pair(1, curses.COLOR_RED, curses.COLOR_BLACK)
  31.     curses.init_pair(2, curses.COLOR_GREEN, curses.COLOR_BLACK)
  32.     curses.init_pair(3, curses.COLOR_YELLOW, curses.COLOR_BLACK)
  33.     curses.init_pair(4, curses.COLOR_BLUE, curses.COLOR_BLACK)
  34.     curses.init_pair(5, curses.COLOR_MAGENTA, curses.COLOR_BLACK)
  35.     curses.init_pair(6, curses.COLOR_CYAN, curses.COLOR_BLACK)
  36.     curses.init_pair(7, curses.COLOR_WHITE, curses.COLOR_BLACK)
  37.  
  38.     # Create variables for easier use.
  39.     global COLOR_RED, COLOR_GREEN, COLOR_YELLOW, COLOR_BLUE, COLOR_MAGENTA, COLOR_CYAN, COLOR_WHITE
  40.     COLOR_RED = curses.color_pair(1)
  41.     COLOR_GREEN = curses.color_pair(2)
  42.     COLOR_YELLOW = curses.color_pair(3)
  43.     COLOR_BLUE = curses.color_pair(4)
  44.     COLOR_MAGENTA = curses.color_pair(5)
  45.     COLOR_CYAN = curses.color_pair(6)
  46.     COLOR_WHITE = curses.color_pair(7)
  47.  
  48. # -----
  49.  
  50. row = 0
  51. col = 0
  52. def writeline(stdscr, text='', color=None):
  53.     global row, col
  54.     lines = text.split('\n')
  55.     for line in lines:
  56.         try:
  57.             stdscr.addstr(row, col, line, color or COLOR_WHITE)
  58.         except curses.error: # Happens with off screen writing.
  59.             pass
  60.         row += 1
  61.         col = 0
  62.  
  63. def write(stdscr, text='', color=None):
  64.     global row, col
  65.     lines = text.split('\n')
  66.     for i, line in enumerate(lines):
  67.         try:
  68.             stdscr.addstr(row, col, line, color or COLOR_WHITE)
  69.         except curses.error: # Happens with off screen writing.
  70.             pass
  71.         if i < len(lines) - 1:
  72.             row += 1
  73.             col = 0
  74.         else:
  75.             col += len(line)
  76.  
  77. def clear(stdscr):
  78.     global row
  79.     global col
  80.     row = 0
  81.     col = 0
  82.     stdscr.clear()
  83.  
  84. # -----
  85.  
  86. def draw_menu(stdscr, menu_items, selected_index, title, highlight):
  87.     # Clear screen.
  88.     clear(stdscr)
  89.  
  90.     if title:
  91.         # Draw title.
  92.         writeline(stdscr, title)
  93.  
  94.     # Draw menu items.
  95.     for i in range(len(menu_items)):
  96.         menuItem = menu_items[i]
  97.         if highlight in menuItem:
  98.             left,right = menuItem.split(highlight)
  99.             write(stdscr, f' {"●" if i == selected_index else "○"} {left}', COLOR_GREEN if i == selected_index else COLOR_CYAN)
  100.             write(stdscr, f'{highlight}', COLOR_RED)
  101.             writeline(stdscr, f'{right}', COLOR_GREEN if i == selected_index else COLOR_CYAN)
  102.         else:
  103.             writeline(stdscr, f' {"●" if i == selected_index else "○"} {menuItem}', COLOR_RED)
  104.  
  105.     # Draw usage information.
  106.     writeline(stdscr)
  107.     writeline(stdscr, ' Make → Space', COLOR_YELLOW)
  108.     writeline(stdscr, ' Copy → Enter', COLOR_YELLOW)
  109.     writeline(stdscr, ' Rise → Up', COLOR_YELLOW)
  110.     writeline(stdscr, ' Fall → Down', COLOR_YELLOW)
  111.     writeline(stdscr, ' Smol → Left', COLOR_YELLOW)
  112.     writeline(stdscr, ' Swol → Right', COLOR_YELLOW)
  113.     writeline(stdscr, ' Zoom → Ctrl+/-', COLOR_YELLOW)
  114.     writeline(stdscr, ' Quit → Esc', COLOR_YELLOW)
  115.  
  116.     # Redraw screen.
  117.     stdscr.refresh()
  118.  
  119. # -----
  120.  
  121. import string
  122.  
  123. # Character groups to be used in the password.
  124. CHARACTER_GROUPS = [
  125.     string.ascii_lowercase,
  126.     string.ascii_uppercase,
  127.     string.digits,
  128.     #string.punctuation,
  129.     '!@#$%^&*()', # PayPal allowed punctuation characters.
  130. ]
  131.  
  132. PASSWORD_CHARACTERS = ''.join(CHARACTER_GROUPS)
  133.  
  134. # Remove unwanted characters.
  135. removal_table = str.maketrans('', '', r'|\\/`\'\"')
  136. PASSWORD_CHARACTERS = PASSWORD_CHARACTERS.translate(removal_table)
  137.  
  138. # Remove timestamp divider characters so they can be uniquely added to the password around the timestamp.
  139. TIMESTAMP_DIVIDER_LEFT = '('
  140. TIMESTAMP_DIVIDER_RIGHT = ')'
  141. PASSWORD_CHARACTERS = PASSWORD_CHARACTERS.replace(TIMESTAMP_DIVIDER_LEFT, '').replace(TIMESTAMP_DIVIDER_RIGHT, '')
  142.  
  143. from datetime import datetime
  144. import secrets
  145. def generate_timestamped_passwords(password_length, count=5):
  146.     dt = datetime.now()
  147.     short_time_stamp = f"{TIMESTAMP_DIVIDER_LEFT}{dt.strftime('%y%m%d')}{TIMESTAMP_DIVIDER_RIGHT}"
  148.     short_time_stamp_len = len(short_time_stamp)
  149.     passwords = []
  150.     retries = 100000
  151.     for _ in range(count):
  152.         for attempt in range(retries):
  153.             # SOURCE: https://docs.python.org/3/library/secrets.html
  154.             password = ''.join(secrets.choice(PASSWORD_CHARACTERS) for _ in range(password_length - short_time_stamp_len))
  155.  
  156.             # If the password has repeating characters.
  157.             # NOTE: The non-timestamped password is tested because the timestamp might fail the unique-segments test depending on what day it is.
  158.             if not has_unique_segments(password):
  159.                 continue
  160.  
  161.             # If the password uses characters from all categories.
  162.             if has_representative_characters(password):
  163.                 timestamped_password = shuffle(password, short_time_stamp)
  164.                 passwords.append(timestamped_password)
  165.                 break
  166.         else:
  167.             passwords.append(f'ERROR: Failed to generate a valid password after {retries} attempts.')
  168.  
  169.     large_time_stamp = f' Created: {dt.strftime("%Y-%m-%d %H:%M:%S")}\n'
  170.     large_time_stamp += f' Length: {password_length}\n'
  171.     return passwords, large_time_stamp, short_time_stamp
  172.  
  173. # -----
  174.  
  175. def has_representative_characters(s):
  176.     min_characters_per_group = min(4, len(s) // len(CHARACTER_GROUPS))
  177.     for group in CHARACTER_GROUPS:
  178.         if sum(char in group for char in s) < min_characters_per_group:
  179.             return False
  180.     return True
  181.  
  182. def has_unique_segments(s):
  183.     segment_length = 2 if len(s) > 80 else 10
  184.     for i in range(0, len(s), segment_length):
  185.         segment = s[i:i + segment_length]
  186.         if len(set(segment)) != len(segment):
  187.             return False
  188.     return True
  189.  
  190. # -----
  191.  
  192. # Insert a word into a sentence randomly.
  193. def shuffle(sentence, word):
  194.     max_word_index = len(sentence)
  195.  
  196.     # Generate a random integer in the range [0, n).
  197.     random_index = secrets.randbelow(max_word_index + 1)
  198.  
  199.     # Insert the word into the sentence.
  200.     return sentence[:random_index] + word + sentence[random_index:]
  201.  
  202. # -----
  203.  
  204. #  def wait_for_enter_keypress(stdscr):
  205. #      writeline(stdscr, ' Press ENTER to continue . . . ')
  206. #      stdscr.refresh()
  207. #
  208. #      while True:
  209. #          # Get keyboard input.
  210. #          key = stdscr.getch()
  211. #
  212. #          # If Enter key is pressed.
  213. #          if key in (curses.KEY_ENTER, ord('\r'), ord('\n')):
  214. #              break
  215.  
  216. def wait_for_any_keypress(stdscr):
  217.     writeline(stdscr, ' Press any key to continue . . . ')
  218.     stdscr.refresh()
  219.  
  220.     # Get keyboard input.
  221.     key = stdscr.getch()
  222.  
  223. # -----
  224.  
  225. MIN_PASSWORD_LENGTH = 12 # 6 numbers (timestamp) + 2 uppercase letters + 2 lowercase letters + 2 punctuation (timestamp-dividers).
  226.  
  227. def main(stdscr):
  228.     init_colors()
  229.  
  230.     password_length = 20
  231.     menu_items, large_time_stamp, short_time_stamp = generate_timestamped_passwords(password_length)
  232.     selected_index = 0
  233.  
  234.     draw_menu(stdscr, menu_items, selected_index, large_time_stamp, short_time_stamp) # Initial drawing.
  235.  
  236.     while True:
  237.         # Get keyboard input.
  238.         key = stdscr.getch()
  239.  
  240.         # If Escape key is pressed.
  241.         if key == ord('\x1b'):
  242.             break
  243.  
  244.         # If Space key is pressed.
  245.         elif key == ord(' '):
  246.             menu_items, large_time_stamp, short_time_stamp = generate_timestamped_passwords(password_length)
  247.  
  248.         # If Up key is pressed.
  249.         elif key == curses.KEY_UP:
  250.             # Loop around backwards.
  251.             selected_index = (selected_index - 1 + len(menu_items)) % len(menu_items)
  252.  
  253.         # If Down key is pressed.
  254.         elif key == curses.KEY_DOWN:
  255.             # Loop around forwards.
  256.             selected_index = (selected_index + 1) % len(menu_items)
  257.  
  258.         # If Left key is pressed.
  259.         elif key == curses.KEY_LEFT:
  260.             # Reduce length of passwords.
  261.             password_length = max(MIN_PASSWORD_LENGTH, password_length - 1)
  262.             menu_items, large_time_stamp, short_time_stamp = generate_timestamped_passwords(password_length)
  263.  
  264.         # If Right key is pressed.
  265.         elif key == curses.KEY_RIGHT:
  266.             # Increase length of passwords.
  267.             password_length += 1
  268.             menu_items, large_time_stamp, short_time_stamp = generate_timestamped_passwords(password_length)
  269.  
  270.         # If Enter key is pressed.
  271.         # NOTE: On Windows the enter key appears to be \r, and on Linux its \n.
  272.         elif key in (curses.KEY_ENTER, ord('\r'), ord('\n')):
  273.             pyperclip.copy(menu_items[selected_index])
  274.             writeline(stdscr, f"\n Copied {menu_items[selected_index]} to clipboard.\n")
  275.             #  wait_for_enter_keypress(stdscr)
  276.             wait_for_any_keypress(stdscr) # Works better.
  277.  
  278.         # Else an unexpected key is pressed.
  279.         #  else:
  280.         #      try:
  281.         #          writeline(stdscr, "\n The pressed key '{}' {} is not associated with a menu function.\n".format(chr(key), key))
  282.         #      except ValueError:
  283.         #          writeline(stdscr, "\n The pressed key {} is not associated with a menu function.\n".format(key))
  284.         #      wait_for_any_keypress(stdscr)
  285.  
  286.         draw_menu(stdscr, menu_items, selected_index, large_time_stamp, short_time_stamp)
  287.  
  288. if __name__ == '__main__':
  289.     curses.wrapper(main)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement