mirror of
https://github.com/PR0M3TH3AN/SeedPass.git
synced 2025-09-08 07:18:47 +00:00
improved password generation
improved password generation. This new version is not compatible with the old one.
This commit is contained in:
@@ -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
|
||||
|
||||
|
Reference in New Issue
Block a user