Passwortprüfer/generator

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Benutzeravatar
Domroon
User
Beiträge: 104
Registriert: Dienstag 3. November 2020, 10:27
Wohnort: Dortmund

Hallo Leute,

ich möchte gerne gut lesbaren, den Python-Richtlinien entsprechenden und effizienten Code schreiben.
Ich würde euch bitten den Code unter diesen Gesichtspunkten zu überprüfen. Ich hoffe, dass ich mithilfe eurer Anmerkungen und eurer Kritik ein guter Python-Programmierer werde.

Vielen Dank euch im voraus ;)

Hier der Code:

Code: Alles auswählen

import time
import string
import random

GERMAN = {
            "welcome" : "\nHerzlich Willkommen!\n",
            "requirements" : "\nFolgende Anforderungen sollte das Passwort haben:\n"
                             "- Mindestens 8 Zeichen\n"
                             "- Mindestens ein Großbuchstabe\n"
                             "- Mindestens ein Kleinbuchstabe\n"
                             "- Mindestens eine Zahl\n"
                             "- Mindestens eine Sonderzeichen\n",
            "main_menu" : "\n1 - Passwort Überprüfer\n"
                          "2 - Passwort Generator\n"
                          "q - Programm beenden\n",
            "wrong_input" : "\nBitte gib einen gültigen Wert ein\n",
            "password_input" : "\nBitte geben Sie ein Passwort ein: ",
            "success" : "\nDas Passwort ist OK!",
            "wrong_length" : "- Zu Kurz",
            "no_uppercase" : "- Kein Großbuchstabe enthalten",
            "no_lowercase" : "- Kein Kleinbuchstabe enthalten",
            "no_digit" : "- Keine Zahl enthalten",
            "no_special" : "- Kein Sonderzeichen enthalten",
            "length" : "Passwortlänge(8-20): "
         }


ENGLISH = {
            "welcome" : "\nwelcome!\n",
            "requirements" : "\nThe password should have the following requirements:\n"
                             "- At least 8 characters\n"
                             "- At least one capital letter\n"
                             "- At least one lowercase letter\n"
                             "- At least one number\n"
                             "- At least one special character\n",
            "main_menu" : "\n1 - Password checker\n"
                          "2 - Password generator\n"
                          "q - Exit program\n",
            "wrong_input": "\nPlease enter a valid value\n",
            "password_input": "\nPlease enter a password: ",
            "success": "\nThe password is OK!",
            "wrong_length": "- Too short",
            "no_uppercase": "- No capital letter",
            "no_lowercase": "- Contains no lower case letter",
            "no_digit": "- Does not contain a number",
            "no_special": "- No special characters included",
            "length" : "Password length (8-20): "
          }


class InputError(Exception):
    """Exception raised for errors in the input.
    Attributes:
        expression -- input expression in which the error occurred
        message -- explanation of the error
    """

    def __init__(self, expression, message):
        self.expression = expression
        self.message = message


def language_selection():
    print(f'Choose your language: ')
    print(f'1 - English')
    print(f'2 - German')
    user_input = input()
    if (user_input != '1') and (user_input != '2'):
        raise InputError(user_input, ' is not a valid Input')
    
    return user_input


def password_verification(password, sentences, give_return=False):
    length = False
    uppercase_letter = False
    lowercase_letter = False
    digit = False
    special_char = False

    if len(password) >= 8:
        length = True

    for letter in password:
        if letter in string.ascii_uppercase + "ÄÖÜ":
            uppercase_letter = True

        if letter in string.ascii_lowercase + "äöüß":
            lowercase_letter = True

        if letter in string.digits:
            digit = True

        if letter in string.punctuation:
            special_char = True

    if not give_return:
        if not length:
            print(sentences["wrong_length"])

        if not uppercase_letter:
            print(sentences["no_uppercase"])

        if not lowercase_letter:
            print(sentences["no_lowercase"])

        if not digit:
            print(sentences["no_digit"])

        if not special_char:
            print(sentences["no_special"])

    if length and uppercase_letter and lowercase_letter and digit and special_char:
        if give_return:
            return True
        print(sentences["success"])
        time.sleep(1)

    if give_return:
        return False


def password_generation(length, sentences):
    if length < 8 or length > 20:
        raise InputError(length, ' is not a valid Input')

    password=""
    while not password_verification(password, sentences, True):
        for i in range(length):
            random_char_type = random.randint(0, 3)
            char_types = {
                            0 : string.ascii_uppercase[random.randint(0, len(string.ascii_uppercase)-1)],
                            1 : string.ascii_lowercase[random.randint(0, len(string.ascii_lowercase)-1)],
                            2 : string.digits[random.randint(0, len(string.digits)-1)],
                            3 : string.punctuation[random.randint(0, len(string.punctuation)-1)]
                        }
            password = password + char_types[random_char_type]
    
    print("\n" + password + "\n")


def main():
    user_input = None
    sentences = None

    # Language Menu
    while user_input != "q":
        while True:
            try:
                if language_selection() == '1':
                    sentences = ENGLISH.copy()
                else:
                    sentences = GERMAN.copy()
                break
            except InputError:
                print('\nPlease enter a valid Input\n')
                time.sleep(1)

        print(sentences["welcome"])

        # Main Menu
        print(sentences["main_menu"])

        while True:
            user_input = input('input: ')
            if user_input == '1':
                password_verification(input(sentences["password_input"]), sentences)
                time.sleep(1)
                print(sentences["main_menu"])
            elif user_input == '2':
                while True:
                    try:
                        length = int(input(sentences["length"]))
                        password_generation(length, sentences)
                        break
                    except InputError:
                        print(sentences["wrong_input"])

                print(sentences["main_menu"])
            elif user_input == 'q':
                break
            else:
                print(sentences["wrong_input"])
                time.sleep(1)


if __name__ == '__main__':
    main()
Sirius3
User
Beiträge: 18274
Registriert: Sonntag 21. Oktober 2012, 17:20

Die Strings sind etwas weit eingerückt. Üblich sind vier Leerzeichen. Funktionen werden nach Tätigkeiten benannt, also select_language oder verify_password.
In language_selection benutzt du Formatstrings obwohl gar nichts zu formatieren ist. Die Klammern um die Bedingungen sind überflüssig, hier wäre der in-Operator besser.
In password_verification benutzt du am besten Sets statt der for-Schleife. Die vielen Vergleiche mit give_return sind verwirrend; das sleep nervig.
In password_generation brauchst du einen Dummy-Wert für password damit die while-Schleife startet. Das ist ein Fall für eine while-true-Schleife mit Abbruchbedingung am Ende. Schau dir random.choice an. Da Satzzeichen genauso oft vorkommenden wie Buchstaben, sind die Passwörter sehr unzufällig.
In main hat du wieder eine while-Schleife die eine while-true-Schleife sein sollte. Das copy ist unsinnig.
Benutzeravatar
Domroon
User
Beiträge: 104
Registriert: Dienstag 3. November 2020, 10:27
Wohnort: Dortmund

@Sirius3:

Vielen Dank für Deine Tipps ;)

Folgendes habe ich nun verbessert:

- Strings ordentlich eingerückt

- password_verification in verify_password umbenannt

- language_selection in select_language umbenannt, Klammern um Bedigungen entfernt und in-Operator benutzt

- Formatstrings aus 'select_language()' durch normale strings ersetzt

- in verify_password() habe ich die for-Schleife durch mehrere Sets ersetzt:
-> Habe ich das gut umgesetzt?

- alle 'time.sleep()' wurden entfernt

- in 'password_generation' habe ich alle zu überprüfenden Zeichen zu einer Liste hinzugefügt und dann mit 'choice' immer wieder ein zufälliges Zeichen einem String angehängt
-> Ist auch diese Umsetzung gut?

- in main() wurde das das unnötige copy() entfernt und die while schleife wurde zu einer while-true-Schleife umgewandelt


Über eine kurze Rückmeldung von Dir würde ich mich sehr freuen.

Hier also der korrigierte Code:

Code: Alles auswählen

import string
from random import choice

GERMAN = {
    "welcome" : "\nHerzlich Willkommen!\n",
    "requirements" : "\nFolgende Anforderungen sollte das Passwort haben:\n"
                     "- Mindestens 8 Zeichen\n"
                     "- Mindestens ein Großbuchstabe\n"
                     "- Mindestens ein Kleinbuchstabe\n"
                     "- Mindestens eine Zahl\n"
                     "- Mindestens eine Sonderzeichen\n",
    "main_menu" : "\n1 - Passwort Überprüfer\n"
                  "2 - Passwort Generator\n"
                  "q - Programm beenden\n",
    "wrong_input" : "\nBitte gib einen gültigen Wert ein\n",
    "password_input" : "\nBitte geben Sie ein Passwort ein: ",
    "success" : "\nDas Passwort ist OK!",
    "wrong_length" : "- Zu Kurz",
    "no_uppercase" : "- Kein Großbuchstabe enthalten",
    "no_lowercase" : "- Kein Kleinbuchstabe enthalten",
    "no_digit" : "- Keine Zahl enthalten",
    "no_special" : "- Kein Sonderzeichen enthalten",
    "length" : "Passwortlänge(8-20): "}


ENGLISH = {
    "welcome" : "\nwelcome!\n",
    "requirements" : "\nThe password should have the following requirements:\n"
                     "- At least 8 characters\n"
                     "- At least one capital letter\n"
                     "- At least one lowercase letter\n"
                     "- At least one number\n"
                     "- At least one special character\n",
    "main_menu" : "\n1 - Password checker\n"
                  "2 - Password generator\n"
                  "q - Exit program\n",
    "wrong_input": "\nPlease enter a valid value\n",
    "password_input": "\nPlease enter a password: ",
    "success": "\nThe password is OK!",
    "wrong_length": "- Too short",
    "no_uppercase": "- No capital letter",
    "no_lowercase": "- Contains no lower case letter",
    "no_digit": "- Does not contain a number",
    "no_special": "- No special characters included",
    "length" : "Password length (8-20): " }


class InputError(Exception):
    """Exception raised for errors in the input.
    Attributes:
        expression -- input expression in which the error occurred
        message -- explanation of the error
    """

    def __init__(self, expression, message):
        self.expression = expression
        self.message = message


def select_language():
    print("Choose your language: ")
    print("1 - English")
    print("2 - German")
    user_input = input()
    if '1' not in user_input and '2' not in user_input:
        raise InputError(user_input, ' is not a valid Input')
    
    return user_input


def verify_password(password, sentences, give_return=False):
    length = False
    uppercase_letter = False
    lowercase_letter = False
    digit = False
    special_char = False

    if len(password) >= 8:
        length = True

    # check whether all necessary characters are included
    password = set(password)

    if password.intersection(set(string.ascii_uppercase + "ÄÖÜ")):
        uppercase_letter = True

    if password.intersection(set(string.ascii_lowercase + "äöüß")):
        lowercase_letter = True

    if password.intersection(set(string.digits)):
        digit = True

    if password.intersection(set(string.punctuation)):
        special_char = True

    # should only be issued if required
    if not give_return:
        if not length:
            print(sentences["wrong_length"])

        if not uppercase_letter:
            print(sentences["no_uppercase"])

        if not lowercase_letter:
            print(sentences["no_lowercase"])

        if not digit:
            print(sentences["no_digit"])

        if not special_char:
            print(sentences["no_special"])

    if length and uppercase_letter and lowercase_letter and digit and special_char:
        if give_return:
            return True
        print(sentences["success"])

    # the return is not necessary if the print functions have been issued
    if give_return:
        return False


def password_generation(length, sentences):
    if length < 8 or length > 20:
        raise InputError(length, ' is not a valid Input')

    password = ""
    while True:
        for i in range(length):
            all_signs = string.ascii_uppercase
            all_signs += string.ascii_lowercase
            all_signs += string.digits
            all_signs += string.punctuation

            password = password + choice(all_signs)
        
        if verify_password(password, sentences, True):
            break
    
    print("\n" + password + "\n")


def main():
    user_input = None
    sentences = None

    # Language Menu
    while True:
        while True:
            try:
                if select_language() == '1':
                    sentences = ENGLISH
                else:
                    sentences = GERMAN
                break
            except InputError:
                print('\nPlease enter a valid Input\n')

        print(sentences["welcome"])

        # Main Menu
        print(sentences["main_menu"])

        while True:
            user_input = input('input: ')
            if user_input == '1':
                verify_password(input(sentences["password_input"]), sentences)
                print(sentences["main_menu"])
            elif user_input == '2':
                while True:
                    try:
                        length = int(input(sentences["length"]))
                        password_generation(length, sentences)
                        break
                    except InputError:
                        print(sentences["wrong_input"])

                print(sentences["main_menu"])
            elif user_input == 'q':
                break
            else:
                print(sentences["wrong_input"])

        if user_input == "q":
            break


if __name__ == '__main__':
    main()
Benutzeravatar
__blackjack__
User
Beiträge: 14056
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Domroon: Die `verify_password()` ist zu umständlich und von der API nicht gut. Wenn ein Argument entscheidet ob eine Funktion Ausgaben macht und nichts zurück gibt, oder keine Ausgaben macht und einen Rückgabewert hat, dann sind das eigentlich zwei Funktionen. Wobei die eine sich eventuell auf die andere abstützen kann.

Aber erst einmal zum zu umständlich: Wenn man in einem ``if``-Zweig einzig und alleine eine Variablen auf `True` setzt falls die Bedingung wahr ist, kann man auch einfach die Bedingung auswerten und das Ergebnis an den Namen binden, ganz ohne ``if``. Beispiel:

Code: Alles auswählen

    length = False
    if len(password) >= 8:
        length = True
    
    # =>
    
    length = len(password) >= 8
Letztendlich braucht man die Wahrheitswerte aber auch überhaupt gar nicht wenn man beispielsweise die zutreffenden Sätze einfach in einer Liste sammeln würde. Mit dieser Liste kann man dann sowohl eine Ausgabe machen was alles nicht erfüllt wurde, als auch ganz einfach feststellen ob alles erfüllt wurde oder nicht wenn man sich die Länge/den ”Wahrheitswert” der Liste anschaut.

Code: Alles auswählen

def verify_password(password):
    password = set(password)
    return [
        message_key
        for test_result, message_key in [
            (len(password) >= 8, "wrong_length"),
            *(
                (password.intersection(characters), message_key)
                for characters, message_key in [
                    (string.ascii_uppercase + "ÄÖÜ", "no_uppercase"),
                    (string.ascii_lowercase + "äöüß", "no_lowercase"),
                    (string.digits, "no_digit"),
                    (string.punctuation, "no_special"),
                ]
            ),
        ]
        if not test_result
    ]
Umwandlung des Ergebnisses in eine natürliche Sprache und Ausgabe des Ergebnisses gehört nicht in so eine Testfunktion.
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
Sirius3
User
Beiträge: 18274
Registriert: Sonntag 21. Oktober 2012, 17:20

Das mit dem in-Operator hast Du falsch verstanden:

Code: Alles auswählen

def select_language():
    print("Choose your language: ")
    print("1 - English")
    print("2 - German")
    user_input = input()
    if user_input not in ('1', '2'):
        raise InputError(user_input, ' is not a valid Input')
    
    return user_input
Besser noch, wäre es das ganze Sprachgewurstel in einer Funktion abzuhandeln:

Code: Alles auswählen

SENCTENCES = {
    'English': {
        "welcome" : "\nwelcome!\n",
        "requirements" : "\nThe password should have the following requirements:\n"
                         "- At least 8 characters\n"
                         "- At least one capital letter\n"
                         "- At least one lowercase letter\n"
                         "- At least one number\n"
                         "- At least one special character\n",
        "main_menu" : "\n1 - Password checker\n"
                      "2 - Password generator\n"
                      "q - Exit program\n",
        "wrong_input": "\nPlease enter a valid value\n",
        "password_input": "\nPlease enter a password: ",
        "success": "\nThe password is OK!",
        "wrong_length": "- Too short",
        "no_uppercase": "- No capital letter",
        "no_lowercase": "- Contains no lower case letter",
        "no_digit": "- Does not contain a number",
        "no_special": "- No special characters included",
        "length" : "Password length (8-20): "
    },
    'German': {
        "welcome" : "\nHerzlich Willkommen!\n",
        "requirements" : "\nFolgende Anforderungen sollte das Passwort haben:\n"
                         "- Mindestens 8 Zeichen\n"
                         "- Mindestens ein Großbuchstabe\n"
                         "- Mindestens ein Kleinbuchstabe\n"
                         "- Mindestens eine Zahl\n"
                         "- Mindestens eine Sonderzeichen\n",
        "main_menu" : "\n1 - Passwort Überprüfer\n"
                      "2 - Passwort Generator\n"
                      "q - Programm beenden\n",
        "wrong_input" : "\nBitte gib einen gültigen Wert ein\n",
        "password_input" : "\nBitte geben Sie ein Passwort ein: ",
        "success" : "\nDas Passwort ist OK!",
        "wrong_length" : "- Zu Kurz",
        "no_uppercase" : "- Kein Großbuchstabe enthalten",
        "no_lowercase" : "- Kein Kleinbuchstabe enthalten",
        "no_digit" : "- Keine Zahl enthalten",
        "no_special" : "- Kein Sonderzeichen enthalten",
        "length" : "Passwortlänge(8-20): "
    }
}


def select_language():
    languages = list(SENCTENCES)
    while True:
        print("Choose your language: ")
        for index, language in enumerate(languages, 1):
            print(f"{index} - {language}")
        user_input = input()
        try:
            index = int(user_input) - 1
            if 0 <= index < len(languages):
                break
        except ValueError:
            pass
    return SENCTENCES[languages[index]]
Benutzeravatar
Domroon
User
Beiträge: 104
Registriert: Dienstag 3. November 2020, 10:27
Wohnort: Dortmund

@__blackjack__:

vielen dank für die verbesserte verifiy_password - Funktion ;)
Diese hat meinen Kopf ganz schön zum Rauchen gebracht. Nach vielem nachlesen habe ich diese nun aber Verstanden. Zum einen war ich zunächst über die zwei Iteratoren "test_result" und "message_key" verwirrt, da ich nicht wusste, dass man auch zwei verwenden kann :D
Auch "message_key" vor der "for-Schleife" hat mich aus der Bahn geworfen, meine es aber nun verstanden zu haben.
In den ganzen sets die du erstellst gibt es also immer zwei Elemente: Ein boolean und einen String. Da du message_key vor die for-schleife geschrieben hast, wird der jeweilige string immer in die liste geschrieben, mit der Vorraussetzung, dass "test_result" nicht richtig ist.
Ich habe Deine Funktion ausprobiert und Sie funktioniert natürlich. Sie gibt mir eine Liste mit "Fehler-Strings" aus die ich außerhalb der Funktion vernünftig auswerten kann.

Nur eine Sache: Kannst Du mir einen Link zu Generatoren schicken, welcher mir diesen Teil das Programms genauer beschreibt:

Code: Alles auswählen

[message_key for test_result, message_key in liste if not test_result]
Meine Literatur konnte diesen Codesschnipsel leider nicht ausreichend beleuchten, bzw. ich bekomme diesen Schnipsel nicht getestet
Benutzeravatar
__blackjack__
User
Beiträge: 14056
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

„List comprehensions“ werden im Tutorial in der Python-Dokumentation hier erklärt: https://docs.python.org/3.6/tutorial/da ... rehensions
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
Benutzeravatar
Domroon
User
Beiträge: 104
Registriert: Dienstag 3. November 2020, 10:27
Wohnort: Dortmund

@Sirius3:

Auch Deine bessere Version von der Funktion 'select_language()' hat mein Hirn zum schmelzen gebracht :D
Ich finde Deine Version super, da ich nun "SENTENCES" in eine einzige Textdatei auslagern könnte und außerdem beliebig viele andere Sprachen hinzufügen kann ohne etwas am Code anpassen zu müssen.

Ich meine Deine Version von 'select_language()' verstanden zu haben. Du hast folgendes gemacht:
- zunächst hast du alle Sätze in zwei separate Dictionarys (English und German) gesteckt, welche du wiederum in ein Dictionary 'SENTENCES' gesteckt hast
- dies gibt Dir nun die Möglichkeit die Sprachen mit enumerate() zu nummerieren und ordentlich auszugeben
- durch Deine while-Schleife in Kombination mit dem try-except-Block erreichst Du, dass das Benutzer so lange "festhängt" bis er eine gültige Eingabe macht
- ist die Eingabe gültig, so gibt die Funktion das Dictionary 'English' oder 'German' zurück (und bei Bedarf noch viele andere Sprachen)

Dementsprechend habe ich nun die main-Funktion angepasst (diese ist durch diese Lösung nun wesentlich kleiner geworden):

Code: Alles auswählen

def main():
    user_input = None
    sentences = None

    # Language Menu
    sentences = select_language()

    print(sentences["welcome"])

    # Main Menu
    print(sentences["main_menu"])

    while True:
        user_input = input('input: ')

        if user_input == '1':
            message_list = verify_password(input(sentences["password_input"]))
            for message in message_list:
                print(sentences[message])

            if message_list == []:
                print(sentences["success"])

            print(sentences["main_menu"])

        elif user_input == '2':
            while True:
                try:
                    length = int(input(sentences["length"]))
                    password_generation(length, sentences)
                    break
                except InputError:
                    print(sentences["wrong_input"])

            print(sentences["main_menu"])
        elif user_input == 'q':
            break
        else:
            print(sentences["wrong_input"])

        if user_input == "q":
            break
Benutzeravatar
Domroon
User
Beiträge: 104
Registriert: Dienstag 3. November 2020, 10:27
Wohnort: Dortmund

Hallo nochmal,

bei der Konstanten 'SENTENCES' kam mir direkt JSON in den Sinn, weil diese fast die gleich Struktur besitzt. Also habe ich mit ein paar kleinen Änderungen eine 'languages.json'-Datei erstellt.
Damit ich diese in mein Programm laden kann habe ich eine Funktion 'load_languages()' erstellt.
dict.fromkeys(seq, [value]) schien mir sehr passend zu sein um die json-Datei in ein "2 Dimensionales" Dictionary umzuformen.
Ich habe keinen Weg gefunden verschiedene Werte(in meinem Fall natürlich Dictionaries) für unterschiedliche Keys zuzuweisen.
Nachfolgend der betroffene Code:

languages.json:

Code: Alles auswählen

{
    "English": {
    "welcome" : "\nwelcome!\n",
    "requirements" : "\nThe password should have the following requirements:\n- At least 8 characters\n- At least one capital letter\n- At least one lowercase letter\n- At least one number\n- At least one special character\n",
    "main_menu" : "\n1 - Password checker\n2 - Password generator\nq - Exit program\n",
    "wrong_input": "\nPlease enter a valid value\n",
    "password_input": "\nPlease enter a password: ",
    "success": "\nThe password is OK!",
    "wrong_length": "- Too short",
    "no_uppercase": "- No capital letter",
    "no_lowercase": "- Contains no lower case letter",
    "no_digit": "- Does not contain a number",
    "no_special": "- No special characters included",
    "length" : "Password length (8-20): " 
    },
    "German" : {
    "welcome" : "\nHerzlich Willkommen!\n",
    "requirements" : "\nFolgende Anforderungen sollte das Passwort haben:\n- Mindestens 8 Zeichen\n- Mindestens ein Großbuchstabe\n- Mindestens ein Kleinbuchstabe\n- Mindestens eine Zahl\n- Mindestens eine Sonderzeichen\n",
    "main_menu" : "\n1 - Passwort Überprüfer\n2 - Passwort Generator\nq - Programm beenden\n",
    "wrong_input" : "\nBitte gib einen gültigen Wert ein\n",
    "password_input" : "\nBitte geben Sie ein Passwort ein: ",
    "success" : "\nDas Passwort ist OK!",
    "wrong_length" : "- Zu Kurz",
    "no_uppercase" : "- Kein Großbuchstabe enthalten",
    "no_lowercase" : "- Kein Kleinbuchstabe enthalten",
    "no_digit" : "- Keine Zahl enthalten",
    "no_special" : "- Kein Sonderzeichen enthalten",
    "length" : "Passwortlänge(8-20): "
    }
}
Nachfolgend die Funktion, welche natürlich nicht wie gewünscht funktioniert. Der Wert von Englischen Key wird einfach vom Deutsch Key überschrieben :(

load_languages():

Code: Alles auswählen

def load_languages():
    with open('languages.json', 'r') as file:
        data = json.load(file)
    
    # convert data to right dictionary-structure
    selected_languages = ["English", "German"]
    for language in selected_languages:
        languages = dict.fromkeys(selected_languages, data[language])
        
    return languages
Sirius3
User
Beiträge: 18274
Registriert: Sonntag 21. Oktober 2012, 17:20

Die Struktur ist schon richtig. Kein Bedarf, da was zu modifizieren:

Code: Alles auswählen

def load_languages():
    with open('languages.json', 'rb') as file:
        return json.load(file)
Benutzeravatar
Domroon
User
Beiträge: 104
Registriert: Dienstag 3. November 2020, 10:27
Wohnort: Dortmund

@ Sirius3:
Wow das war einfach :D

Ich habe "load_languages()" entsprechend verändert und musste daher in "select_language()" nur SENTENCES durch "load_languages()" austauschen und zusätzlich muss ich das dictionary ohne in einer Liste verpackt zu sein in eine Variable 'sentences' speichern. Schöne Sache. Vielen Dank ;)
Die Konstante "SENTENCES" kann ich somit aus dem Programm entfernen ;)

Code: Alles auswählen

def select_language():
    languages = list(load_languages())
    sentences = load_languages()
    while True:
        print("Choose you language: ")
        for index, language in enumerate(languages, 1):
            print(f"{index} - {language}")
        user_input = input()
        try:
            index = int(user_input) - 1
            if 0 <= index < len(languages):
                break
        except ValueError:
            pass
    return sentences[languages[index]]
Benutzeravatar
Domroon
User
Beiträge: 104
Registriert: Dienstag 3. November 2020, 10:27
Wohnort: Dortmund

Ich habe einen kleinen Bug in 'password_generation()' korrigiert. Das Problem war, dass das generierte Passwort manchmal zu lang war. Wenn man zum Beispiel die Eingabe '8' tätigte so kam es vor, dass das Passwort teilweise bis zu 24-Zeichen lang war. Dies hing damit zusammen, dass die Funktion so lange ein neues Passwort generiert bis es alle Kriterien erfüllt ein gutes Passwort zu sein. Ich habe den Fehler gemacht diese "Versuche" mit an den Passwort-String anzuhängen :D
Die Verbesserung: Jedesmal wenn das Passwort nicht die Kriterien erfüllt wird der Variable 'password' ein leerer String zugewiesen:

Code: Alles auswählen

def password_generation(length, sentences):
    if length < 8 or length > 20:
        raise InputError(length, ' is not a valid Input')

    password = ""
    while True:
        for i in range(length):
            all_signs = string.ascii_uppercase
            all_signs += string.ascii_lowercase
            all_signs += string.digits
            all_signs += string.punctuation

            password = password + choice(all_signs)
        
        if verify_password(password) == []:
            break
        else:
            password = ""
    
    print("\n" + password + "\n")
Sirius3
User
Beiträge: 18274
Registriert: Sonntag 21. Oktober 2012, 17:20

In `select_language` lädst Du die Datei jetzt unnötigerweise zwei mal.
Auch in `password_generation` machst Du Dinge mehrfach, die nicht nötig wären. Variablen initialisiert man und resetted sie nicht.

Code: Alles auswählen

ALL_SIGNS = (string.ascii_uppercase
    + string.ascii_lowercase
    + string.digits
    + string.punctuation
)

def password_generation(length, sentences):
    if not 8 <= length <= 20:
        raise InputError(length, ' is not a valid Input')

    while True:
        password = ""
        for i in range(length):
            password += choice(ALL_SIGNS)        
        if verify_password(password) == []:
            break
    
    print(f"\n{password}\n")
Die Schleife kann man noch einsparen:

Code: Alles auswählen

password = "".join(random.choices(ALL_SIGNS, k=length))
Benutzeravatar
Domroon
User
Beiträge: 104
Registriert: Dienstag 3. November 2020, 10:27
Wohnort: Dortmund

@Sirius3:

Die Datei wird in 'select_language()' nun nur noch einmal geladen:

Code: Alles auswählen

def select_language():
    sentences = load_languages()
    languages = list(sentences)
    
    while True:
        print("Choose you language: ")
        for index, language in enumerate(languages, 1):
            print(f"{index} - {language}")
        user_input = input()
        try:
            index = int(user_input) - 1
            if 0 <= index < len(languages):
                break
        except ValueError:
            pass
    return sentences[languages[index]]
'password_generation()' habe ich nun mit all deinen Tipps verbessert. Die Konstante 'ALL_SIGNS' habe ich in die Funktion gepackt, weil ich sie in keiner anderen Funktion brauche. Oder hattest Du eine bestimme Intention diese global verfügbar zu machen?

Code: Alles auswählen

def password_generation(length):
    ALL_SIGNS = (string.ascii_uppercase
        + string.ascii_lowercase
        + string.digits
        + string.punctuation
    )

    if not 8 <= length <= 20:
        raise InputError(length, ' is not a valid Input')

    while True:
        password = "".join(choices(ALL_SIGNS, k=length))
        if verify_password(password) == []:
            break
            
    print(f"\n{password}\n")
Ich finde es krass wie viel Code man durch gute Überlegungen in Python einsparen kann. Danke das ihr mir dabei helft. Das bringt mich jedes mal ein Stück näher zu besserem zukünftigen Code ;)
Sirius3
User
Beiträge: 18274
Registriert: Sonntag 21. Oktober 2012, 17:20

Die main-Funktion ist noch viel zu lang.
Die Initialisierung von `user_input` und `sentences` mit None wird nicht gebraucht.
`main_menu` kommt drei mal im Code vor. Und ›user_input == 'q'‹ wird zweimal abgefragt.

Code: Alles auswählen

def select_main_menu(sentences):
    print(sentences["main_menu"])
    while True:
        user_input = input('input: ')
        if user_input in ["1", "2", "q"]:
            break
        print(sentences["wrong_input"])
    return user_input


def check_password(sentences):
    password = input(sentences["password_input"])
    messages = verify_password(password)
    if not messages:
        print(sentences["success"])
    else:
        for message in messages:
            print(sentences[message])


def generate_password(sentences):
    while True:
        try:
            length = int(input(sentences["length"]))
            password_generation(length, sentences)
            break
        except (InputError, ValueError):
            print(sentences["wrong_input"])


def main():
    # Language Menu
    sentences = select_language()
    print(sentences["welcome"])
    while True:
        user_input = select_main_menu(sentences)
        if user_input == '1':
            check_password(sentences)
        elif user_input == '2':
            generate_password(sentences)
        elif user_input == 'q':
            break
        else:
            assert False, "not possible"
Benutzeravatar
Domroon
User
Beiträge: 104
Registriert: Dienstag 3. November 2020, 10:27
Wohnort: Dortmund

@Sirius3:
Super! Durch diese "Wrapper-Funktionen" (wenn man Sie so nennen kann) sieht die main-Funktion gleich viel aufgeräumter und eleganter aus ;)
Hab alles implementiert und es funktioniert super. Vielen Dank! ;)

Mir ist beim weiteren austesten ein weiterer Bug aufgefallen. Nehmen wir zum Beispiel das Passwort "a!1aAa87a". Dies ist ja eigentlich ein gültig Passwort und "Zu kurz" ist es auf keinen Fall :D Genau das aber zeigte mir das Programm an. Das Problem konnte ich in der Funktion 'verify_password()' ausmachen. Hier wurde die Variable 'password' etwas "zu früh" in ein Set umgewandelt. In einem Set kann ja nichts doppelt vorkommen und dementsprechend werden die vielen kleinen 'a' in dem Passwort nicht mitgezählt uns das Passwort ist schlicht zu kurz

Hier der verbesserte Code (in 'password_length' speicher ich die Länge des Strings bevor er zu einem set umgewandelt wird):

Code: Alles auswählen

def verify_password(password):
    password_length = len(password)
    password = set(password)
    return [
        message_key
        for test_result, message_key in [
            (password_length >= 8, "wrong_length"),
            *(
                (password.intersection(characters), message_key)
                for characters, message_key in [
                    (string.ascii_uppercase + "ÄÖÜ", "no_uppercase"),
                    (string.ascii_lowercase + "äöüß", "no_lowercase"),
                    (string.digits, "no_digit"),
                    (string.punctuation, "no_special"),
                ]
            ) 
        ]
        if not test_result
    ]
Benutzeravatar
ThomasL
User
Beiträge: 1379
Registriert: Montag 14. Mai 2018, 14:44
Wohnort: Kreis Unna NRW

Ich bin kein Experte für Kryptographie aber mein gesunder Sachverstand sagt mir, dass "a!1aAa87a" kein gutes Passwort ist.
Das kleine a kommt da imho zu oft drin vor. Ich würde keine gleichen Zeichen erlauben.
Ich bin Pazifist und greife niemanden an, auch nicht mit Worten.
Für alle meine Code Beispiele gilt: "There is always a better way."
https://projecteuler.net/profile/Brotherluii.png
Benutzeravatar
__blackjack__
User
Beiträge: 14056
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@ThomasL: Das würde den Suchraum aber deutlich Einschränken wenn der Angreifer weiss, dass kein Zeichen mehr als Einmal vorkommen kann.
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
Benutzeravatar
ThomasL
User
Beiträge: 1379
Registriert: Montag 14. Mai 2018, 14:44
Wohnort: Kreis Unna NRW

Stimmt, nicht bedacht.
Ich bin Pazifist und greife niemanden an, auch nicht mit Worten.
Für alle meine Code Beispiele gilt: "There is always a better way."
https://projecteuler.net/profile/Brotherluii.png
Antworten