License

Creative Commons License
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 Unported License.
Valid XHTML 1.0 Strict

Python implementation of the MySQL PASSWORD() function

Written by Christian Stigen Larsen — 2008-12-29

License

Creative Commons License
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 Unported License.

The other day at work, we had forgotten the root password on one of our seldomly used MySQL databases. And, of course, we really needed to access it.

First, I remembered that I had an account with a password I actually knew. After logging in, it turned out this account also had read-only access to the mysql.user table. The joy! I selected the password hashes and stared at them in the same way I stare at my car's engine if it refuses to work. Which is, with a blank look while scratching my butt.

I wanted to know how the PASSWORD() function was implemented in MySQL, so I downloaded the source code for clues.

After a simple find . -name '*password*' I was pointed to a file called sql/password.c and opened it for inspection:

/*
    MySQL 4.1.1 password hashing: SHA conversion (see RFC 2289, 3174) twice
    applied to the password string, and then produced octet sequence is
    converted to hex string.
    The result of this function is used as return value from PASSWORD() and
    is stored in the database.

    SYNOPSIS
    make_scrambled_password()
    buf       OUT buffer of size 2*SHA1_HASH_SIZE + 2 to store hex string
    password  IN  NULL-terminated password string
*/

void
make_scrambled_password(char *to, const char *password)
{
  SHA1_CONTEXT sha1_context;
  uint8 hash_stage2[SHA1_HASH_SIZE];

  mysql_sha1_reset(&sha1_context);

  /* stage 1: hash password */
  mysql_sha1_input(&sha1_context, (uint8 *) password, (uint) strlen(password));
  mysql_sha1_result(&sha1_context, (uint8 *) to);

  /* stage 2: hash stage1 output */
  mysql_sha1_reset(&sha1_context);
  mysql_sha1_input(&sha1_context, (uint8 *) to, SHA1_HASH_SIZE);

  /* separate buffer is used to pass 'to' in octet2hex */
  mysql_sha1_result(&sha1_context, hash_stage2);

  /* convert hash_stage2 to hex string */
  *to++= PVERSION41_CHAR;
  octet2hex(to, (char*) hash_stage2, SHA1_HASH_SIZE);
}

They simply ran SHA1 twice on the input, prepending an asterix to the hex digest.

Ok, this shouldn't be too hard to do in Python. I began typing:

import hashlib

def mysql_password(str):
        """
	Hash string twice with SHA1 and return uppercase hex digest,
	prepended with an asterix.

	This function is identical to the MySQL PASSWORD() function.
        """
	pass1 = hashlib.sha1(str).digest()
	pass2 = hashlib.sha1(pass1).hexdigest()
        return "*" + pass2.upper()

I quickly tested it with my account's password, just to make sure it worked as boldly advertised by the docstring.

Next I began implementing a permute() function to yield the next permutation of a string of characters. Well, actually, I just wrote the skeleton for it before realizing that the password would be quietly hiding in a search-space of, oh, about 1020 non-unique strings.

So, instead of googling for articles on SHA1 birthday-attacks, I did what every respectable cryptologist do: I threw passwords at the function and saw what stuck.

And wouldn't you know. After a couple of tries, one actually did!

I tried to log in...

Welcome to the MySQL monitor.

Wohoo! I was in, and had a silly story to tell.

 

blog comments powered by Disqus