2
\$\begingroup\$

There are two reasons why many people choose weak passwords over stronger, more complicated ones:

  1. They don't know that their passwords are insecure
  2. They don't want to change to a better password because they are generally more complicated and harder to remember

So, I came up with this program: a password trainer! It helps you learn a new password, which should hopefully make point no. 2 more appealing.

While I am not new to the Python language, this my first attempt at correct styling according to PEP 0008. I have been focousing on the correct use of docstrings and commenting.

Code:

"""
Password Trainer v1.0
Copyright (c) 2016 angussidney
Released under the MIT license

This program helps you remember long, complicated, (and most importantly) secure passwords.
"""

import os

PASSWORD = "password123" # Insert your password here 
MAXIMUM_TRIES = 3 # The maximum number of attempts before the program stops annoying the user.

def main():
    """Prompts the user for their password and provides feedback."""
    tries = 0
    while tries < MAXIMUM_TRIES:
        password_attempt = raw_input("Please type your password: ")
        print
        if password_attempt == PASSWORD:
            print "You entered your password correctly."
            break # Skips directly to end of session
        else:
            print "Your attempt was incorrect.\n"
            print_differences(password_attempt)
            raw_input("\nPress any key to continue...")
            tries += 1
            clear_screen()
    print "\nPassword training is complete for this session."
    raw_input("Press any key to continue...")

def clear_screen():
    """Clears the screen for a new password attempt."""
    os.system("cls" if os.name=="nt" else "clear")

def print_differences(attempt):
    """Prints the differences between the password attempt and the real password.
    Raises an error if the attempt is of the wrong length.

    Keyword arguments:
    attempt -- the password attempt

    Example output:
    Correct password: password123

    Your attempt:     passwood124
    Errors:                 x   x
    Corrections:            r   3
    """
    if len(attempt) != len(PASSWORD):
        print "Your password attempt was of the wrong length."
        print "Your attempt should have been %s characters long." % str(len(PASSWORD))
        print "No password accuracy breakdown available."
    else:
        print "Correct password: %s\n" % PASSWORD
        print "Your attempt:     %s" % attempt
        print "Errors:           ",
        for letter in range(len(PASSWORD)):
            if attempt[letter] == PASSWORD[letter]:
                print "\b ", # \b backspace character must be used to remove automatically added space
            else:
                print "\bx",
        print
        print "Corrections:      ",
        for letter in range(len(PASSWORD)):
            if attempt[letter] == PASSWORD[letter]:
                print "\b ",
            else:
                print "\b%s" % PASSWORD[letter],
        print

if __name__ == "__main__":
    main()

Notes:

  • Currently, the program simply returns an error if the length of the password attempt is different to that of the actual password. This will be changed in v2.0.
  • The program only works when run in the console. It does not work in IDLE (because the clear screen function and the backspace characters won't work)

What needs reviewing:

I would be greatful if you could review the following items:

  • Is my code following the PEP 0008 standard? Especially the comments and docstrings?
  • Is there a better way to print the corrections and errors, without using backspace characters (\b)?
\$\endgroup\$
2
\$\begingroup\$

Style : PEP Compliance

Your code looks nice and is well documented. You can use tools to check your code compliance to PEP 8 and PEP 257 : pep8 and pydocstyle (formerly pep257).

PEP 8 warnings are about spacing and line length. PEP 257 warnings are about verbal form used (should be imperative and not declarative).

Style : a step further

A few details in your code are not very pythonic. The main one if the way you write loops. At a rule of thumb, any time you write range(len in Python code, there might be a better way. Ned Batchelder's talk about loops might interest you. In your case, you could use zip or itertools.izip is what you need.

Your code becomes :

    for p, a in zip(PASSWORD, attempt):
        if a == p:
            print "\b ", # \b backspace character must be used to remove automatically added space
        else:
            print "\bx",
    print
    print "Corrections:      ",
    for p, a in zip(PASSWORD, attempt):
        if a == p:
            print "\b ",
        else:
            print "\b%s" % p,

On a pretty similar topic, your while loop could be written with range or xrange.

for try_index in range(MAXIMUM_TRIES):
    password_attempt = 'passwwrd123'  # raw_input("Please type your password: ")
    print
    if password_attempt == PASSWORD:
        print "You entered your password correctly."
        break # Skips directly to end of session
    else:
        print "Your attempt was incorrect.\n"
        print_differences(password_attempt)
        raw_input("\nPress any key to continue...")

(I've used try_index as a loop variable to make intent clearer but the usage/convention is to use _ as a variable name if you do not use its value).

Making things more simple

You call print multiple times which adds whitespace that you are trying to remove later on. It would be easier to build the string you want to print and then print it in one go.

Using multiple Python techniques :

  • list comprehension

  • join

  • conditional expression

You can write :

    print "Correct password: %s\n" % PASSWORD
    print "Your attempt:     %s" % attempt
    errors = "".join(" " if a == p else "x" for p, a in zip(PASSWORD, attempt))
    print "Errors:           %s" % errors
    correction = "".join(" " if a == p else p for p, a in zip(PASSWORD, attempt))
    print "Corrections:      %s" % correction
    print

More advices

If you are learning Python, it would be a good idea to give Python 3 a try. There is not much to change in your case but the earlier you take the good habits, the easier it will be.

Regarding your length comparison, you could try to use izip_longest to handle scenarios with strings of different lengths.

\$\endgroup\$
  • \$\begingroup\$ Thanks for your input. I do know how to code in Python 3 (or I at least know the main differences), and I was debating which version to use, however I decided to use Python 2 because the py2exe module works with it. \$\endgroup\$ – angussidney Feb 7 '16 at 7:20
3
\$\begingroup\$
password_attempt = raw_input(...)

When dealing with passwords, you want to try and minimize the amount of information any bypasser can see on the screen.

Python offers the getpass module to ease such inputs:

import getpass

password_attempt = getpass.getpass(...)

As such, you should also avoid printing plain passwords and attempts.

\$\endgroup\$
  • \$\begingroup\$ Thanks for the input! But remember my code does display the full correct password if your attempt is incorrect, so this kinda defeats the purpose. But I will remember this for future reference! \$\endgroup\$ – angussidney Feb 7 '16 at 7:24
  • \$\begingroup\$ @angussidney displaying the correct password is really bad, and shouldn't be done. A better version could be to display only the wrong characters with corresponding correct characters (if less than 50% were wrong). \$\endgroup\$ – holroy Feb 7 '16 at 15:16

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.