Ovid has asked for the wisdom of the Perl Monks concerning the following question:

I have a database which stores an MD5 digest of the user's password. Here's the method which creates the digest:

sub create_digest_from_password { my ( $self, $pass ) = @_; my $md5 = new Digest::MD5; $md5->add( $pass ); $md5->add( $self->{ _salt } ); $md5->b64digest; }

This method has worked fine. However, I now have a method that two other programmers are using that allows them to update the password in the database. How can I ensure that they are only putting in an MD5 digest and not the plaintext password? I could test on length (the base 64 digest is exactly 22 characters), but that won't work if someone creates a password that is 22 characters long.

The actual method call that they use looks like this:

my $sec = Foo::Security->new; my $success = $sec->update_admin_user( { user => 'bboop', first_name => 'Betty', last_name => 'Boop', password => $enc_passwo +rd } );

user is mandatory, all other fields are optional, though at least one other must exist. I could easily modify the method so that the password key has a plaintext password and the method encrypts it. That would mean that the programmer doesn't have to worry about it, but then I have the reverse problem: how do I ensure that they passed a plaintext password and not the MD5 digest?

Is this an issue that can be solved programmatically or is this simply a training issue? I'd like to solve this programatically, if possible, because I have no control over who is going to maintain this code in the future.

Cheers,
Ovid

Vote for paco!

Join the Perlmonks Setiathome Group or just click on the the link and check out our stats.

Replies are listed 'Best First'.
Re: MD5 Password Validation
by chromatic (Archbishop) on Aug 28, 2001 at 00:15 UTC
    Another approach is to remove all knowledge of the MD5 operation from the external interface. Provide a method to compare a password against the database. Internally, it runs MD5 on the potential password, comparing the results against the stored password.

    Don't provide a method that allows anyone to access the password in the database. Decouple the mechanics of storing a password from updating user information. How the password is stored is of much less concern than if users can update it.

    update_admin_user() would then call create_digest_from_password() behind the scenes, and people who program to that interface would simply pass in a plain text password.

Re: MD5 Password Validation
by dws (Chancellor) on Aug 27, 2001 at 23:01 UTC
    Is this an issue that can be solved programmatically or is this simply a training issue?

    It's a bit of both.

    You might be able to resolve the issue by providing an additional argument (key) in case a caller already has an MD5 key in hand and wishes to use it. How about requiring that they pass exactly one of plaintextpassword or </code>md5password</code>? That removes any ambiguity from the interface, and eliminates any errors caused by people not understanding what is required for the password argument pair.

Re: MD5 Password Validation
by idnopheq (Chaplain) on Aug 27, 2001 at 22:38 UTC
    Ew! I think doing the 22 char test is a valid first step. After all, how many users actually will use a 22 char password. Secondly, for the really paraniod, one could then run a quick dictionary test against it just to ensure. Thirdly, an occasional vgrep of the password data may be needed ( darn that very capable human brain! ).

    But the best answer is, IMHO, educational in nature. Just like you cannot legislate against stupidity, you cannot alway program against it either.

    UPDATE: You could also have the "admin" function kick off an email or somesuch each time to remind the priviliged of their duty to hash. It would be annoying for them, sure. But they could never complain that they didn't know better.

    HTH
    --
    idnopheq
    Apply yourself to new problems without preparation, develop confidence in your ability to to meet situations as they arrise.

Re: MD5 Password Validation
by grinder (Bishop) on Aug 28, 2001 at 02:24 UTC
    If you're storing the digest directly in a database column, then forget the base64 representation, and just store the 16 byte binary data. You can always prettify it for debugging ex post facto.

    You could then treat the digest as a 128 bit vector, and count the bits set. Statistically, for a random digest, half of them will be on, half will be off. You need to set a cut-off threshold, where you consider a digest is a fake, e.g. the ratio of on bits to off bits should never be worse than 56/72.

    If someone tries to stuff "thisismypassword" in that field, bells are gonna start ringing, because at the very least, the 8th bit in each byte is not set, which already sets you up with an imbalance.

    update: here's some code I hacked up to look at the problem:

    #! /usr/bin/perl -w use strict; my $pw = shift || 'thisismypassword'; my $ones = 0; foreach( split //, $pw ) { my $bitmap = sprintf '%b', ord $_; $ones += ($bitmap =~ tr/1/1/); } print "$ones/128 bits set\n";

    Unfortunately, this shows that it's dreadfully easy to come up with a balanced number of on/off bits. Here's another thought: look at what this produces:

    foreach( split //, $pw ) { printf "%08b\n", ord $_; }

    This produces something like:

    01110100 01101000 01101001 01110011 01101001 01110011 01101101 01111001 01110000 01100001 01110011 01110011 01110111 01101110 01110010 01100100

    Going down the columns, we would expect, as in the right most columns, to see about half ones and half zeros. This is not what we see in the leftmost columns, ergo, this cannot be an MD5 digest. What you really want to do is to run a quick statistical check on those 128 bits. Something like a Kruskal-Wallis or Mann-Whitney test (but IANAS).

    --
    g r i n d e r
Re (tilly) 1: MD5 Password Validation
by tilly (Archbishop) on Aug 28, 2001 at 19:38 UTC
    First of all I would have your method just take the password and not worry about their trying to encrypt it. That centralizes the MD5 logic in one place, and it is unlikely to run into problems because programmers generally like simple APIs. (But test anyways.)

    However if you wanted to test for MD5 codes, you could check for 22 characters and not matching /[^0-9a-f]/. Of course that will only work if your programmers use the hex encoding, but people are very, very unlikely to accidentally pick that kind of password. OTOH if they use base 64, you are so out of luck. Therefore there is no easy way to catch all of the possible MD5 encodings that they might use. On the flip side, the fact that they might use the wrong MD5 encoding is reason enough in my books to not scatter the encoding logic...