I am trying to guess the paswords from the etc/shadow file(it has 43 user/passwords). And I have been given some hits about the passwords:

  • Length is between 4 and 8 characters
  • There can be only 2 numbers and only at the end
  • Capital letters only in the beginning

So I started just with a small group composed by 4 character with 2 digits in it. But it takes so much time to process:

import crypt
import string
import itertools
import datetime

dir = "shadow3"
file = open(dir, 'r').readlines() #Read all the 43 hashes

username = []
hashed = []
c = 0
cc = 0

for x in file: #Split the hash and the username
    usr, hshd, wtf, iss, this, thing, here, doing, example = x.split(':')
#GRUPO1 4 caracteres 2 numeros
letters = string.ascii_lowercase
digits = string.digits
for x in itertools.product(group1,group1b):  #Join the possible iterations
  string=''.join([''.join(k) for k in x])
for y in grupo1:#Get the one of the iterations and try it 
  for x in hashed: #Verify if that iteration is the password to any of the 43 users
    rehashed = crypt.crypt(prueba, x)
    if rehashed == x: #Password is found
        print('La contraseña del usuario ' + username[c] + ' es ' + prueba)
        cc = 1
    c = c + 1
if cc == 0: #after all iterations password is not found
    print('Lo sentimos "' + prueba + '" no es la contraseña de ningun usuario')

How can I improve the efficiency of this? I have a GTX 1070 if it helps for any kind of GPU processing.(I have no idea of this)

Just this small part of 4 characters is taking me for hours, it has still not finished.

  • \$\begingroup\$ cant you hash prueba without the need of user hashes? Don´t understand the need of rehashed \$\endgroup\$
    – juvian
    Nov 23, 2018 at 17:39
  • \$\begingroup\$ As long as I know, to verify if the password is correct I have to crypt the password with the hash, then if the hash generated in that is equal to the initial hash then it is correct. But I'm really new into this, maybe if you could put me an example of what you mean \$\endgroup\$
    – 19mike95
    Nov 23, 2018 at 17:46
  • \$\begingroup\$ 8 characters are already 26^6 * 10^2 possibilities, will take too long \$\endgroup\$
    – juvian
    Nov 23, 2018 at 18:02
  • 1
    \$\begingroup\$ @juvian “_can’t you hash prueba without the need of the user hashes”? No. If I set my password as “S3cr3t”, the system adds a two character random salt, say “xy” before the hashing, and stores the hash with the salt prefix “xy”, such as “xy7A2cyZTu”. If another user also uses the password “S3cr3t”, but “3w” was used as the salt, their password might be hashed as “3wHj85dFh” ... completely different despite being the same password. Hashing the trial password with the stored hash is just passing the correct salt (first 2 characters) to use when hashing the trial password. \$\endgroup\$
    – AJNeufeld
    Nov 24, 2018 at 6:36

1 Answer 1


Starting with the code in my answer to your related question, we can factor out the source of the words being guessed and encapsulate it all in functions:

def get_users_to_crack(file_name):
    users = {}
    with open(file_name) as file:
        for line in file:
            username, hashed_password, *_ = line.split(':')
            users[username] = hashed_password
    return users

def crack(users, words):
    cracked_users = {}
    for password in words:
        if not users:
            print("Cracked all passwords")
            return cracked_users, {}
        for username, hashed_password in users.items():
            if crypt.crypt(password, hashed_password) == hashed_password:
                print(f'La contraseña del usuario {username} es {password}')
                cracked_users[username] = password
                del users[username]
    return cracked_users, users

Now we just need a supplier of words. This can be either from a file, similar to your other question:

def dictionary_attack(file_name):
    with open(file_name) as file:
        for line in file:
            word = line.rstrip('\n').capitalize()
            if 4 <= len(word) <= 8:
                yield word

Or you build it yourself, according to the scheme you supplied here:

from string import ascii_lowercase as a_z, ascii_uppercase as A_Z, digits
from itertools import product

def get_chars(length):
    """Produces the allowed chars according to the scheme given
    assert length > 2
    return [a_z + A_Z] + [a_z] * (length - 3) + [a_z + digits] * 2

def brute_force(min_length, max_length, get_chars):
    for length in range(min_length, max_length + 1):
        for word in product(*get_chars(length)):
            yield "".join(word)

You can then combine them into one nice tool:

if __name__ == "__main__":
    users = get_users_to_crack("shadow3")
    n = len(users)

    users_cracked_1, users = crack(users, dictionary_attack('out68.lst'))
    print(f"Cracked {len(users_cracked_1)} out of {n} users passwords using a dictionary attack")

    users_cracked_2, users = crack(users, brute_force(4, 8, get_chars))
    print(f"Cracked {len(users_cracked_2)} out of {n} users passwords using brute force")

    if users:
        print(f"{len(users)} users passwords not cracked")

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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