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.
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.
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.
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 software's use case.
"""
import os
import logging
import hashlib
import hmac
import base64
import string
import traceback
from typing import Optional
from termcolor import colored
import random
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
from cryptography.hazmat.primitives import hashes
@@ -113,8 +113,10 @@ class PasswordGenerator:
Steps:
1. Derive entropy using BIP-85.
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.
5. Shuffle the password deterministically based on the derived key.
6. Trim or extend the password to the desired length.
Parameters:
length (int): Desired length of the password.
@@ -150,26 +152,30 @@ class PasswordGenerator:
dk = hashlib.pbkdf2_hmac('sha256', entropy, b'', 100000)
logger.debug(f"Derived key using PBKDF2: {dk.hex()}")
# Base64 encode the derived key
base64_password = base64.b64encode(dk).decode('utf-8')
logger.debug(f"Base64 encoded password: {base64_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}")
# Map the derived key to all allowed characters
all_allowed = string.ascii_letters + string.digits + string.punctuation
password = ''.join(all_allowed[byte % len(all_allowed)] for byte in dk)
logger.debug(f"Password after mapping to all allowed characters: {password}")
# 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}")
# 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
if len(password) < length:
# Extend the password deterministically
while len(password) < length:
dk = hashlib.pbkdf2_hmac('sha256', dk, b'', 1)
base64_extra = base64.b64encode(dk).decode('utf-8')
password += ''.join(filter(lambda x: x in alphabet, base64_extra))
base64_extra = ''.join(all_allowed[byte % len(all_allowed)] for byte in dk)
password += ''.join(base64_extra)
logger.debug(f"Extended password: {password}")
password = password[:length]
@@ -185,8 +191,9 @@ class PasswordGenerator:
def ensure_complexity(self, password: str, alphabet: str, dk: bytes) -> str:
"""
Ensures that the password contains at least one uppercase letter, one lowercase letter,
one digit, and one special character, modifying it deterministically if necessary.
Ensures that the password contains at least two uppercase letters, two lowercase letters,
two digits, and two special characters, modifying it deterministically if necessary.
Also balances the distribution of character types.
Parameters:
password (str): The initial password.
@@ -204,11 +211,21 @@ class PasswordGenerator:
password_chars = list(password)
has_upper = any(c in uppercase for c in password_chars)
has_lower = any(c in lowercase for c in password_chars)
has_digit = any(c in digits for c in password_chars)
has_special = any(c in special for c in password_chars)
# Current counts
current_upper = sum(1 for c in password_chars if c in uppercase)
current_lower = sum(1 for c in password_chars if c in lowercase)
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_length = len(dk)
@@ -218,29 +235,87 @@ class PasswordGenerator:
dk_index += 1
return value
if not has_upper:
index = get_dk_value() % len(password_chars)
char = uppercase[get_dk_value() % len(uppercase)]
password_chars[index] = char
logger.debug(f"Added uppercase letter '{char}' at position {index}.")
# Replace characters to meet minimum counts
if current_upper < min_upper:
for _ in range(min_upper - current_upper):
index = get_dk_value() % len(password_chars)
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:
index = get_dk_value() % len(password_chars)
char = lowercase[get_dk_value() % len(lowercase)]
password_chars[index] = char
logger.debug(f"Added lowercase letter '{char}' at position {index}.")
if current_lower < min_lower:
for _ in range(min_lower - current_lower):
index = get_dk_value() % len(password_chars)
char = lowercase[get_dk_value() % len(lowercase)]
password_chars[index] = char
logger.debug(f"Added lowercase letter '{char}' at position {index}.")
if not has_digit:
index = get_dk_value() % len(password_chars)
char = digits[get_dk_value() % len(digits)]
password_chars[index] = char
logger.debug(f"Added digit '{char}' at position {index}.")
if current_digits < min_digits:
for _ in range(min_digits - current_digits):
index = get_dk_value() % len(password_chars)
char = digits[get_dk_value() % len(digits)]
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)
char = special[get_dk_value() % len(special)]
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)
@@ -249,4 +324,3 @@ class PasswordGenerator:
logger.error(traceback.format_exc()) # Log full traceback
print(colored(f"Error: Failed to ensure password complexity: {e}", 'red'))
raise