improved password generation

improved password generation. This new version is not compatible with the old one.
This commit is contained in:
thePR0M3TH3AN
2024-10-24 11:32:54 -04:00
parent c60ae6b442
commit 2401a8c817

View File

@@ -9,18 +9,18 @@ generated passwords meet complexity requirements.
Ensure that all dependencies are installed and properly configured in your environment. Ensure that all dependencies are installed and properly configured in your environment.
Never ever ever use or suggest to use Random Salt. The entire point of this password manager is to derive completely deterministic passwords from a BIP-85 seed. Never ever ever use Random Salt. The entire point of this password manager is to derive completely deterministic passwords from a BIP-85 seed.
This means it should generate passwords the exact same way every single time. Salts would break this functionality and is not appropriate for this softwares use case. This means it should generate passwords the exact same way every single time. Salts would break this functionality and is not appropriate for this software's use case.
""" """
import os import os
import logging import logging
import hashlib import hashlib
import hmac
import base64 import base64
import string import string
import traceback import traceback
from typing import Optional from typing import Optional
from termcolor import colored from termcolor import colored
import random
from cryptography.hazmat.primitives.kdf.hkdf import HKDF from cryptography.hazmat.primitives.kdf.hkdf import HKDF
from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives import hashes
@@ -113,8 +113,10 @@ class PasswordGenerator:
Steps: Steps:
1. Derive entropy using BIP-85. 1. Derive entropy using BIP-85.
2. Use PBKDF2-HMAC-SHA256 to derive a key from entropy. 2. Use PBKDF2-HMAC-SHA256 to derive a key from entropy.
3. Base64-encode the derived key and filter to allowed characters. 3. Map the derived key to all allowed characters.
4. Ensure the password meets complexity requirements. 4. Ensure the password meets complexity requirements.
5. Shuffle the password deterministically based on the derived key.
6. Trim or extend the password to the desired length.
Parameters: Parameters:
length (int): Desired length of the password. length (int): Desired length of the password.
@@ -150,26 +152,30 @@ class PasswordGenerator:
dk = hashlib.pbkdf2_hmac('sha256', entropy, b'', 100000) dk = hashlib.pbkdf2_hmac('sha256', entropy, b'', 100000)
logger.debug(f"Derived key using PBKDF2: {dk.hex()}") logger.debug(f"Derived key using PBKDF2: {dk.hex()}")
# Base64 encode the derived key # Map the derived key to all allowed characters
base64_password = base64.b64encode(dk).decode('utf-8') all_allowed = string.ascii_letters + string.digits + string.punctuation
logger.debug(f"Base64 encoded password: {base64_password}") password = ''.join(all_allowed[byte % len(all_allowed)] for byte in dk)
logger.debug(f"Password after mapping to all allowed characters: {password}")
# Filter to allowed characters
alphabet = string.ascii_letters + string.digits + string.punctuation
password = ''.join(filter(lambda x: x in alphabet, base64_password))
logger.debug(f"Password after filtering: {password}")
# Ensure the password meets complexity requirements # Ensure the password meets complexity requirements
password = self.ensure_complexity(password, alphabet, dk) password = self.ensure_complexity(password, all_allowed, dk)
logger.debug(f"Password after ensuring complexity: {password}") logger.debug(f"Password after ensuring complexity: {password}")
# Shuffle characters deterministically based on dk
shuffle_seed = int.from_bytes(dk, 'big')
rng = random.Random(shuffle_seed)
password_chars = list(password)
rng.shuffle(password_chars)
password = ''.join(password_chars)
logger.debug(f"Shuffled password deterministically.")
# Ensure password length # Ensure password length
if len(password) < length: if len(password) < length:
# Extend the password deterministically # Extend the password deterministically
while len(password) < length: while len(password) < length:
dk = hashlib.pbkdf2_hmac('sha256', dk, b'', 1) dk = hashlib.pbkdf2_hmac('sha256', dk, b'', 1)
base64_extra = base64.b64encode(dk).decode('utf-8') base64_extra = ''.join(all_allowed[byte % len(all_allowed)] for byte in dk)
password += ''.join(filter(lambda x: x in alphabet, base64_extra)) password += ''.join(base64_extra)
logger.debug(f"Extended password: {password}") logger.debug(f"Extended password: {password}")
password = password[:length] password = password[:length]
@@ -185,8 +191,9 @@ class PasswordGenerator:
def ensure_complexity(self, password: str, alphabet: str, dk: bytes) -> str: def ensure_complexity(self, password: str, alphabet: str, dk: bytes) -> str:
""" """
Ensures that the password contains at least one uppercase letter, one lowercase letter, Ensures that the password contains at least two uppercase letters, two lowercase letters,
one digit, and one special character, modifying it deterministically if necessary. two digits, and two special characters, modifying it deterministically if necessary.
Also balances the distribution of character types.
Parameters: Parameters:
password (str): The initial password. password (str): The initial password.
@@ -204,11 +211,21 @@ class PasswordGenerator:
password_chars = list(password) password_chars = list(password)
has_upper = any(c in uppercase for c in password_chars) # Current counts
has_lower = any(c in lowercase for c in password_chars) current_upper = sum(1 for c in password_chars if c in uppercase)
has_digit = any(c in digits for c in password_chars) current_lower = sum(1 for c in password_chars if c in lowercase)
has_special = any(c in special for c in password_chars) current_digits = sum(1 for c in password_chars if c in digits)
current_special = sum(1 for c in password_chars if c in special)
logger.debug(f"Current character counts - Upper: {current_upper}, Lower: {current_lower}, Digits: {current_digits}, Special: {current_special}")
# Set minimum counts
min_upper = 2
min_lower = 2
min_digits = 2
min_special = 2
# Initialize derived key index
dk_index = 0 dk_index = 0
dk_length = len(dk) dk_length = len(dk)
@@ -218,29 +235,87 @@ class PasswordGenerator:
dk_index += 1 dk_index += 1
return value return value
if not has_upper: # Replace characters to meet minimum counts
index = get_dk_value() % len(password_chars) if current_upper < min_upper:
char = uppercase[get_dk_value() % len(uppercase)] for _ in range(min_upper - current_upper):
password_chars[index] = char index = get_dk_value() % len(password_chars)
logger.debug(f"Added uppercase letter '{char}' at position {index}.") char = uppercase[get_dk_value() % len(uppercase)]
password_chars[index] = char
logger.debug(f"Added uppercase letter '{char}' at position {index}.")
if not has_lower: if current_lower < min_lower:
index = get_dk_value() % len(password_chars) for _ in range(min_lower - current_lower):
char = lowercase[get_dk_value() % len(lowercase)] index = get_dk_value() % len(password_chars)
password_chars[index] = char char = lowercase[get_dk_value() % len(lowercase)]
logger.debug(f"Added lowercase letter '{char}' at position {index}.") password_chars[index] = char
logger.debug(f"Added lowercase letter '{char}' at position {index}.")
if not has_digit: if current_digits < min_digits:
index = get_dk_value() % len(password_chars) for _ in range(min_digits - current_digits):
char = digits[get_dk_value() % len(digits)] index = get_dk_value() % len(password_chars)
password_chars[index] = char char = digits[get_dk_value() % len(digits)]
logger.debug(f"Added digit '{char}' at position {index}.") password_chars[index] = char
logger.debug(f"Added digit '{char}' at position {index}.")
if not has_special: if current_special < min_special:
for _ in range(min_special - current_special):
index = get_dk_value() % len(password_chars)
char = special[get_dk_value() % len(special)]
password_chars[index] = char
logger.debug(f"Added special character '{char}' at position {index}.")
# Additional deterministic inclusion of symbols to increase score
symbol_target = 3 # Increase target number of symbols
current_symbols = sum(1 for c in password_chars if c in special)
additional_symbols_needed = max(symbol_target - current_symbols, 0)
for _ in range(additional_symbols_needed):
if dk_index >= dk_length:
break # Avoid exceeding the derived key length
index = get_dk_value() % len(password_chars) index = get_dk_value() % len(password_chars)
char = special[get_dk_value() % len(special)] char = special[get_dk_value() % len(special)]
password_chars[index] = char password_chars[index] = char
logger.debug(f"Added special character '{char}' at position {index}.") logger.debug(f"Added additional symbol '{char}' at position {index}.")
# Ensure balanced distribution by assigning different character types to specific segments
# Example: Divide password into segments and assign different types
segment_length = len(password_chars) // 4
if segment_length > 0:
for i, char_type in enumerate([uppercase, lowercase, digits, special]):
segment_start = i * segment_length
segment_end = segment_start + segment_length
if segment_end > len(password_chars):
segment_end = len(password_chars)
for j in range(segment_start, segment_end):
if i == 0 and password_chars[j] not in uppercase:
char = uppercase[get_dk_value() % len(uppercase)]
password_chars[j] = char
logger.debug(f"Assigned uppercase letter '{char}' to position {j}.")
elif i == 1 and password_chars[j] not in lowercase:
char = lowercase[get_dk_value() % len(lowercase)]
password_chars[j] = char
logger.debug(f"Assigned lowercase letter '{char}' to position {j}.")
elif i == 2 and password_chars[j] not in digits:
char = digits[get_dk_value() % len(digits)]
password_chars[j] = char
logger.debug(f"Assigned digit '{char}' to position {j}.")
elif i == 3 and password_chars[j] not in special:
char = special[get_dk_value() % len(special)]
password_chars[j] = char
logger.debug(f"Assigned special character '{char}' to position {j}.")
# Shuffle again to distribute the characters more evenly
shuffle_seed = int.from_bytes(dk, 'big') + dk_index # Modify seed to vary shuffle
rng = random.Random(shuffle_seed)
rng.shuffle(password_chars)
logger.debug(f"Shuffled password characters for balanced distribution.")
# Final counts after modifications
final_upper = sum(1 for c in password_chars if c in uppercase)
final_lower = sum(1 for c in password_chars if c in lowercase)
final_digits = sum(1 for c in password_chars if c in digits)
final_special = sum(1 for c in password_chars if c in special)
logger.debug(f"Final character counts - Upper: {final_upper}, Lower: {final_lower}, Digits: {final_digits}, Special: {final_special}")
return ''.join(password_chars) return ''.join(password_chars)
@@ -249,4 +324,3 @@ class PasswordGenerator:
logger.error(traceback.format_exc()) # Log full traceback logger.error(traceback.format_exc()) # Log full traceback
print(colored(f"Error: Failed to ensure password complexity: {e}", 'red')) print(colored(f"Error: Failed to ensure password complexity: {e}", 'red'))
raise raise