<commit_before>#!/usr/bin/env python3

import sys
import os
import argparse
import getpass
import base64
import hashlib
from Crypto.Cipher import AES

###
# AES:

pad = lambda s: s + (AES.block_size - len(s) % AES.block_size) * chr(AES.block_size - len(s) % AES.block_size).encode()

def aes_encrypt( seed, key, raw ):
    raw = pad(raw)
    iv = seed[0:AES.block_size]
    cipher = AES.new( key, AES.MODE_CBC, iv )
    return base64.b64encode( iv + cipher.encrypt( raw ) ) 

###

def scramble( key, func='md5' ):
    # Ugly but effective, moreover this is not a webapp so who cares.
    # Will raise AttributeError if function is not valid ... auto validation FTW!
    key = key.encode("utf-8")
    try:
        return eval( 'hashlib.%s(key).digest()' % func )

    except AttributeError as e:
        print("[ERROR] Invalid scramble-func: '%s' is not a valid hashing function." % func)
        raise e

###

def convert_to_charset(password, specialchars):
    output = ""
    i      = 0
    slen  = len(specialchars)

    for c in password:
        c = chr(c)
        if c.isalnum():
            output += c
        else:
            output += specialchars[i % slen]
            i += 1

    return output        
   
def main():
    try:
        parser = argparse.ArgumentParser(description="Password scrambler")
        parser.add_argument('--file', dest="file", default=None, help="File used to initialize generation", required=True)
        parser.add_argument('--login', dest="login", default=None, help="Login for which you want to use the password", required=True)
        parser.add_argument('--special', dest="special", default="_&#", help="Whitelist of special characters, i.e: '_&#'")
        parser.add_argument('--length', dest="length", default=30, help="Length of the password, default=30", type=int)
        parser.add_argument('--scramble-func', dest="func", default='md5', help="Hashing function to use for input data scrambling, default=md5.\nOther functions can be found on hashlib module documentation.")
        args = parser.parse_args()

        # first thing first, fail if seed file does not exist
        with open( args.file, 'rb' ) as fd:
            raw = fd.read()

        password = getpass.getpass()
        key = scramble( password, args.func )    
        vec = scramble( args.login, args.func )

        aes_out1 = aes_encrypt( vec, key, raw )

        sha_digest = hashlib.sha512(aes_out1).digest()
        passlen    = len(password) % len(sha_digest)
        key2       = sha_digest[passlen: passlen+32]

        aes_out2 = aes_encrypt( key, key2, aes_out1 )
        
        start    = key[0] % len(aes_out2)
        portion  = aes_out2[start:]
        result   = hashlib.sha512(portion).digest()
        longpass = base64.b64encode(result)
        longpass = longpass[0:args.length]
        longpass = convert_to_charset(longpass,  sorted(args.special, reverse=True))

        print("---")
        print(longpass)
        print("---")<commit_msg>[FEATURE] Copy the generated password into the clipboard instead of displaying (optional)<commit_after>#!/usr/bin/env python3

import sys
import os
import argparse
import getpass
import base64
import hashlib
import pyperclip
from Crypto.Cipher import AES

###
# AES:

pad = lambda s: s + (AES.block_size - len(s) % AES.block_size) * chr(AES.block_size - len(s) % AES.block_size).encode()

def aes_encrypt( seed, key, raw ):
    raw = pad(raw)
    iv = seed[0:AES.block_size]
    cipher = AES.new( key, AES.MODE_CBC, iv )
    return base64.b64encode( iv + cipher.encrypt( raw ) ) 

###

def scramble( key, func='md5' ):
    # Ugly but effective, moreover this is not a webapp so who cares.
    # Will raise AttributeError if function is not valid ... auto validation FTW!
    key = key.encode("utf-8")
    try:
        return eval( 'hashlib.%s(key).digest()' % func )

    except AttributeError as e:
        print("[ERROR] Invalid scramble-func: '%s' is not a valid hashing function." % func)
        raise e

###

def convert_to_charset(password, specialchars):
    output = ""
    i      = 0
    slen  = len(specialchars)

    for c in password:
        c = chr(c)
        if c.isalnum():
            output += c
        else:
            output += specialchars[i % slen]
            i += 1

    return output        
   
def main():
    try:
        parser = argparse.ArgumentParser(description="Password scrambler")
        parser.add_argument('--file', dest="file", default=None, help="File used to initialize generation", required=True)
        parser.add_argument('--login', dest="login", default=None, help="Login for which you want to use the password", required=True)
        parser.add_argument('--special', dest="special", default="_&#", help="Whitelist of special characters, i.e: '_&#'")
        parser.add_argument('--length', dest="length", default=30, help="Length of the password, default=30", type=int)
        parser.add_argument('--clip', dest="clip", default=False, help="Copy the generated password into the clipboard instead of displaying", required=False, action="store_true")
        parser.add_argument('--scramble-func', dest="func", default='md5', help="Hashing function to use for input data scrambling, default=md5.\nOther functions can be found on hashlib module documentation.")
        args = parser.parse_args()

        # first thing first, fail if seed file does not exist
        with open( args.file, 'rb' ) as fd:
            raw = fd.read()

        password = getpass.getpass()
        key = scramble( password, args.func )    
        vec = scramble( args.login, args.func )

        aes_out1 = aes_encrypt( vec, key, raw )

        sha_digest = hashlib.sha512(aes_out1).digest()
        passlen    = len(password) % len(sha_digest)
        key2       = sha_digest[passlen: passlen+32]

        aes_out2 = aes_encrypt( key, key2, aes_out1 )
        
        start    = key[0] % len(aes_out2)
        portion  = aes_out2[start:]
        result   = hashlib.sha512(portion).digest()
        longpass = base64.b64encode(result)
        longpass = longpass[0:args.length]
        longpass = convert_to_charset(longpass,  sorted(args.special, reverse=True))

        print("---")
        if not args.clip:
            print("---")
            print(longpass)
            print("---")
        else:
            pyperclip.copy(longpass)
            print("<the generated password is in your clipboard>")
{'fraction_non_alphanumeric': 0.12605042016806722, 'fraction_numerical': 0.012449424214130096, 'mean_word_length': 2.68098510882016, 'pattern_counts': {'":': 0, '<': 4, '<?xml version=': 0, '>': 4, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '714310', 'n_tokens_mistral': 2067, 'n_tokens_neox': 1948, 'n_words': 552}