4
\$\begingroup\$

I created a fun password cracker using literal brute force, searching each character to see if it matches an ASCII character 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ. The password is randomly generated and will vary from 100,000 to 250,000 characters long. I use a wrapper timeit function to time the function and the output is a basic print statement using .format():

import random
import time

password = ""
attempted_password = ""
list_of_chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
for letter in range(0, random.randint(100000, 250000)):
    password += list_of_chars[random.randint(0, 61)]


def timeit(func):
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        print "The function {.__name__} took {:.15f} seconds to finish.".format(func, time.time() - start)
        return result
    return wrapper


@timeit
def solve_password(word):
    global attempted_password
    for character in word:
        for entry in list_of_chars:
            if character == entry:
                attempted_password += character
                continue
    return attempted_password

print "The password: {0:}\nLength of password was: {1:}\nIs correct? : {2:}".format(solve_password(password),
                                                                                    len(password),
                                                                                    attempted_password == password)

An example output (without the password):

The function solve_password took 3.540999889373779 seconds to finish.
The password: *password is here*
Length of password was: 246416
Is correct? : True

So my questions are:

  • Am I following coding standards for Python 2 (like PEP8)

  • Is there anyway to improve performance, readability, etc.

  • Is there any way to make my code more "Pythonic" (like a native Python coder)?

\$\endgroup\$
1
  • 1
    \$\begingroup\$ This is a very unrealistic password cracker, where you have the actual plaintext password there to compare so you can do it letter by letter... \$\endgroup\$
    – Graipher
    Commented Jan 7, 2017 at 8:53

2 Answers 2

4
\$\begingroup\$

I'll answer your first question separately. However, since your second and third questions are closely related, I'll give the same answers to both questions.

Am I following coding standards for Python 2 (like PEP8)

For the most part, your code complies with PEP8,

  • Spaces between operators
  • Two newlines between function definitions
  • Variables are lowercase with underscores as needed
  • etc...

The only thing I'd suggest is to break some of your longer statements up - such as your print statement at the end of your script - onto separate lines, with each separate line being indented.

However, even if sometimes you choose not to comply with a certain coding standard, make sure you are consistent with your naming conventions. I've seen Python code which is written Java style, but is still easy to read because the author was consistent in their style.

Is there any way to make my code more "Pythonic" (like a native Python coder)? and Is there anyway to improve performance, readability, etc.

  • Instead of having the list_of_chars variable, make use of the string module which already defines all alphanumeric characters:

    >>> import string
    >>> string.digits + string.ascii_letters 
    '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
    >>> 
    
  • Don't use global variables. There is rarely ever a good reason to use them. Instead of making attempted_password global, define it local to the solve_password() function. This makes much clearer where attempted_password is used.

  • Inside of your first for-loop, you never use the variable letter. Instead, simply use the variable _ which conveys to the reader that your only using this loop for code repetition:

    for _ in range(0, random.randint(100000, 250000)):
        password += list_of_chars[random.randint(0, 61)]
    
  • The builtin range() function will already start from zero if no other start value is specified. So there is no need to explicitly do it yourself:

    range(random.randint(100000, 250000)) # no need for setting 0
    
\$\endgroup\$
3
\$\begingroup\$

Whenever you are doing string addition in Python, you are probably doing it wrong. It is very slow, due to strings being immutable. Because of this Python will have to create a new string everytime you do string addition and copy over the content of the two strings you are adding.

As a fix, just use list and str.join. Also, creating a password from a possible list of characters is something you might want to do again, so I would put it into a function.

import string
chars = string.digits + string.ascii_letters

def create_password(chars):
    password = []
    for _ in range(random.randint(100000, 250000)):
        password.append(random.choice(chars))
    return "".join(password)

This can be even further simplified using random.sample:

def create_password(chars):
    length = random.randint(100000, 250000)
    return "".join(random.sample(chars, length))
\$\endgroup\$

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