Test MySQL password strength

Newer versions of MySQL stores passwords using a double SHA-1 hash. Because of this, administrators can’t tell what someone’s password is. What if you want to make sure no users are using weak passwords?

This is a simple Python script that will connect to your local MySQL database, pull out the password hashes for all users, and either run a dictionary search or a brute force search on it. Either give it an argument for the dictionary file, or give it a number for the length of passwords you want to test. If you want to use a different character set (e.g. include special characters), change the characterSet variable near the top of the script.

On my system, I was able to test around 50,000 words per second. Note that this cannot be used to recover the root MySQL password, as it requires the root password to connect to the database in the first place.

#!/usr/bin/python
 
import MySQLdb
import sha
import sys
import os
import getpass
 
alpha = "abcdefghijklmnopqrstuvwxyz"
numbers = "0123456789"
# Set this to a string with the character set you want to check
# Examples:
characterSet = alpha
#characterSet = alpha + alpha.upper()
#characterSet = numbers
#characterSet = alpha + alpha.upper() + numbers
 
def recurse(width, logins):
  for i in range(0, width + 1):
    print "Checking width %d" % i
    recurse2(i, logins, 0, "")
    print ""
 
def recurse2(width, logins, position, string):
  for char in characterSet:
    # Show progress
    if position == 0:
      print char,
      sys.stdout.flush()
 
    if (position < width - 1):
      recurse2(width, logins, position + 1, string + char)
    else:
      hash = sha.new(sha.new(string + char).digest())\
          .hexdigest()
      for login in logins:
        if hash == login[2]:
          print "\n'%s'@'%s': '%s'\n" %\
              (login[0], login[1], string + char)
          logins.remove(login)
 
if len(sys.argv) > 1:
  password = getpass.getpass('Enter the root password: ')
  conn = MySQLdb.connect(host = "localhost",
    user = "root",
    passwd = password,
    db = "mysql")
  cursor = conn.cursor()
  cursor.execute("SELECT User, Host, Password from user;")
  logins = cursor.fetchall()
  cursor.close()
  conn.close()
 
  # Preprocess the logins
  processed = []
  for login in logins:
    ignore = False
    # Ignore empty passwords
    if 0 == len(login[2]):
      print "'%s'@'%s' has empty password, ignoring" %\
          (login[0], login[1])
      ignore = True
    # Ignore duplicates
    for p in processed:
      if p[2] == login[2][1:].lower():
        print "'%s'@'%s' has same password as '%s'@'%s', ignoring" %\
            (login[0], login[1], p[0], p[1])
        ignore = True
    if not ignore:
      processed.append((login[0], login[1], login[2][1:].lower()))
 
  if os.path.isfile(sys.argv[1]):
    f = open(sys.argv[1])
    lines = f.readlines()
    f.close()
 
    for line in lines:
      hash = sha.new(sha.new(line[:-1]).digest()).hexdigest()
      for login in processed:
        if hash == login[2]:
          print "'%s'@'%s': '%s'" % (login[0], login[1], line[:-1])
          break
  else:
    try:
      depth = int(sys.argv[1])
    except:
      print "Usage: %s [dictionary file | brute force depth]" % sys.argv[0]
    recurse(depth, processed)
else:
  print "Usage: %s [dictionary file | brute force depth]" % sys.argv[0]
Advertisements
This entry was posted in Uncategorized and tagged . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s