2
\$\begingroup\$

I recently posted a program that I wrote that would change a password after being validated, the program can be found here.

I have rewritten this program, it has a couple new features:

  • Usernames
  • Decryption
  • Program related errors

How it works. At the beginning of the program you asked to make a username, that username is stored in a file, you are then asked to create a password, the password is run through a series of verification such as length, characters, integers, etc.. If the password completes verification that password is encrypted (using a simple encryption technique) and stored into a file. From there the password will be asked for to login. If you fail to put in the correct password to many times, both the user name and the password file are truncated back to 0.

Source:

# -*- encoding: UTF-8 -*-

import re
import getpass


class ValidatorError(Exception):
    pass


class SecondAuthenticationError(Exception):
    pass


class PasswordValidator(object):
    """ A password validator, used to validate a password for
        specific characters, numbers, and special characters """

    def __init__(self, password):
        self.password = password
        self.validators = [self.validate_integers(self.password), self.validate_spec_chars(self.password),
                           self.validate_length(self.password), self.validate_lower_case(self.password),
                           self.validate_upper_case(self.password)]

    def validate_length(self, password):  # Check the length, gotta be over 8
        if len(password) > 8:
            return True
        else:
            raise ValidatorError("Failed to validate length. Allowed length: (> 8)")

    def validate_upper_case(self, password):  # Check for upper case characters
        if re.search("[A-Z]", password) is not None:
            return True
        else:
            raise ValidatorError("Failed to validate upper case characters. (A - Z)")

    def validate_lower_case(self, password):  # Check for lower case characters
        if re.search("[a-z]", password) is not None:
            return True
        else:
            raise ValidatorError("Failed to validate lower case characters. (a - z)")

    def validate_integers(self, password):  # Check for integers
        if re.search("[0-9]", password) is not None:
            return True
        else:
            raise ValidatorError("Failed to validate integers. (0 - 9)")

    def validate_spec_chars(self, password):  # Check for special characters
        if re.search("[!$&*]", password) is not None:
            return True
        else:
            raise ValidatorError("Failed to validate special characters. Allowed: ( !$&* )")

    def validate(self):
        """ Validate everything. """
        try:
            return all(validators for validators in self.validators)
        except ValidatorError as e:
            print e
            return False


def encrypt(password):
    """ Encrypt the password simply example: bíFuo <- says Test!23456
    :type password: String """
    encrypt_arr = list(password)
    new_encrypt_arr = []

    for i in encrypt_arr:
        num = ord(i) + 3
        new_encrypt_arr.append(chr(num))

    return ''.join(new_encrypt_arr)


def decrypt(encrypted_password):
    """ Decrypt a password when needed
    :type encrypted_password: String (UTF-8) """
    decrypt_arr = list(encrypted_password)
    new_decrypt_arr = []

    for char in decrypt_arr:
        num = ord(char) - 3
        new_decrypt_arr.append(chr(num))

    return ''.join(new_decrypt_arr)


def obtain_password():
    """ Get the password, simple and easy with getpass library """
    return getpass.getpass('Change your password: ')


def second_authentication(password_to_verify):
    """ Verify that the password is what the user wants it to be,
        by asking for it a second time.
        :type password_to_verify: String """
    password = getpass.getpass('Enter password a second time: ')
    if encrypt(password) == encrypt(password_to_verify):
        with open('passwords.txt', 'a+') as f:
            f.write(encrypt(password_to_verify))
        return "Password changed successfully."
    else:
        raise SecondAuthenticationError("Passwords encryption did not match.")


def erase_all(file_list):
    """ Erase all data from the file because you where an idiot and
        forgot your password
        :type file_list: List """
    for files in file_list:
        open(files, 'w').close()


def user_login():
    """ Login and create a username, maybe """
    with open('username.txt', 'a+') as f:
        if f.readline() is "":
            username = raw_input("First login, enter a username to use: ")
            f.seek(0)
            f.write(username)
        else:
            login_id = raw_input("Enter username: ")
            f.seek(0)
            if login_id == f.readline():
                return True
            else:
                print "Invalid username."
                return False


def user_password():
    """ If you mess up all the data will be gone. """
    files = ['username.txt', 'passwords.txt']
    attempts = 3

    while True:
        password_to_compare = getpass.getpass("Enter password: ")

        with open('passwords.txt', 'a+') as password:
            if password.readline() is not "":
                password.seek(0)
                if password_to_compare == decrypt(password.readline()):
                    return True
                else:
                    print "Invalid password.."
                    if attempts == 0:
                        print "Exiting and erasing data.."
                        erase_all(files)
                        print "All data truncated."
                        break
                    else:
                        attempts -= 1
                        print "You have {} attempts left..".format(attempts)


if __name__ == '__main__':
    try:
        if user_login() is True:
            if user_password() is True:
                print "Welcome home.."
        else:
            password_to_verify = obtain_password()
            val = PasswordValidator(password_to_verify)
            if val.validate() is not False:
                print second_authentication(password_to_verify)
    except ValidatorError, SecondAuthenticationError:
        print "Failed to verify, password and username truncated"
        erase_all(['username.txt', 'password.txt'])
\$\endgroup\$
3
\$\begingroup\$

DRY

Validation

def validate_upper_case(self, password):  # Check for upper case characters
    if re.search("[A-Z]", password) is not None:
        return True
    else:
        raise ValidatorError("Failed to validate upper case characters. (A - Z)")

def validate_lower_case(self, password):  # Check for lower case characters
    if re.search("[a-z]", password) is not None:
        return True
    else:
        raise ValidatorError("Failed to validate lower case characters. (a - z)")

def validate_integers(self, password):  # Check for integers
    if re.search("[0-9]", password) is not None:
        return True
    else:
        raise ValidatorError("Failed to validate integers. (0 - 9)")

def validate_spec_chars(self, password):  # Check for special characters
    if re.search("[!$&*]", password) is not None:
        return True
    else:
        raise ValidatorError("Failed to validate special characters. Allowed: ( !$&* )")

All the functions have the form (the len check can be easily modified to take this form):

def validate_requirement(self, password):
    if re.search(regex_for_requirement, password) is not None:
        return True
    else:
        raise ValidatorError("Failed to validate " + error_for_requirement) 

You can build a table of regexes and errors:

REGEXES_AND_ERRORS = [
  ("[A-Z]", "upper case characters. (A - Z)"),
  ("[a-z]", "lower case characters. (A - Z)"),
  ...
]

And write a single function:

@staticmethod
def validate_requirement(password, regex_for_requirement, error_for_requirement):
    if re.search(regex_for_requirement, password) is not None:
        return True
    else:
        raise ValidatorError("Failed to validate " + error_for_requirement)

And instead of looping over functions, you can loop over simple arguments:

@staticmethod
def is_fully_valid(password):
    return all(validate_requirement(password) 
                 for regex, e in PasswordValidator.REGEXES_AND_ERRORS)

"Encryption"

def encrypt(password):
    """ Encrypt the password simply example: bíFuo <- says Test!23456
    :type password: String """
    encrypt_arr = list(password)
    new_encrypt_arr = []

    for i in encrypt_arr:
        num = ord(i) + 3
        new_encrypt_arr.append(chr(num))

    return ''.join(new_encrypt_arr)


def decrypt(encrypted_password):
    """ Decrypt a password when needed
    :type encrypted_password: String (UTF-8) """
    decrypt_arr = list(encrypted_password)
    new_decrypt_arr = []

    for char in decrypt_arr:
        num = ord(char) - 3
        new_decrypt_arr.append(chr(num))

    return ''.join(new_decrypt_arr)

Except for names the only difference is the single line num = ord(char) + 3 vs num = ord(char) - 3, you could give the operation as an argument:

def encrypt(password, operation=op.add):
    # ... same
    num = operation(ord(char), 3)
    # ... same

def decrypt(password):
    return encrypt(password, operation=op.sub)

Please note that you are just shifting all the characters by a given amount, like in a Caesar Cipher. This is very easy to reverse-engineer and decode without knowing the shift value.

The example in encrypt is wrong. Wrong examples are worse than no examples, doctest allows you to automatically check the correctness of your examples.

Finally note that encrypt is vastly overcomplicated, you can write it in a single line avoiding all those lists variables and conversions:

def encrypt(s, operation=operator.add, shift=3):
    return ''.join(chr(operation(ord(char), shift)) for char in s)
\$\endgroup\$
  • \$\begingroup\$ I know the example is wrong, I forgot all about that until now, the example is from the previous version. The encryption is very easy, and there's a purpose for that, I was going to use that encryption in order to create another program. I guess, however, I could make it a little more complicated. \$\endgroup\$ – YoYoYo I'm Awesome Sep 1 '16 at 14:59

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.