crypt.METHOD_CRYPT is not callable so the traceback that you provided doesn't correspond to the code in your question. crypt.METHOD_CRYPT could be used as the second parameter for crypt.crypt() function.
Also as @martineau pointed out wordchars is a tuple but you need a string to pass to the crypt.crypt() function.
From the docs:
Since a few crypt(3) extensions allow different values, with different
sizes in the salt, it is recommended to use the full crypted password
as salt when checking for a password.
To find a plain text from a defined character set given its crypted form: salt plus hash, you could:
from crypt import crypt
from itertools import product
from string import ascii_letters, digits
def decrypt(crypted, charset=ascii_letters + digits):
# find hash for all 4-char strings from the charset
# and compare with the given hash
for candidate in map(''.join, product(charset, repeat=4)):
if crypted == crypt(candidate, crypted):
return candidate
Example
salt, hashed = 'qb', '1Y.qWr.DHs6'
print(decrypt(salt + hashed))
# -> e2e4
assert crypt('e2e4', 'qb') == (salt + hashed)
The assert line makes sure that calling crypt with the word e2e4 and the salt qb produces qb1Y.qWr.DHs6 where qb is the salt.
Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 4, in decrypt TypeError: must be string, not tuple