From e179fec1f865ede349873a436d5eb2ed4e98da9b Mon Sep 17 00:00:00 2001 From: Mike Crute Date: Tue, 21 Apr 2009 21:44:29 -0400 Subject: Initial import from subversion. --- __init__.py | 0 htpasswd.py | 35 ++++++++++++++++ md5.py | 134 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ password.py | 31 ++++++++++++++ 4 files changed, 200 insertions(+) create mode 100644 __init__.py create mode 100644 htpasswd.py create mode 100755 md5.py create mode 100644 password.py diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/htpasswd.py b/htpasswd.py new file mode 100644 index 0000000..3641c34 --- /dev/null +++ b/htpasswd.py @@ -0,0 +1,35 @@ +#!/usr/bin/python +""" +Apache htaccess File Library +by Mike Crute on July 12, 2008 +for SoftGroup Interactive, Inc. +Released under the terms of the BSD license. + +A collection of classes and functions to manipulate apache htaccess files. +""" + +__all__ = [ "generate_user" ] + +def hash_password(passwd, ctype="crypt"): + """Create an Apache-style password hash. + This is basically just a simplfified interface to apachelib.password + for use in generating htaccess files. Valid ctypes are crypt, sha and + md5. + """ + if ctype is "crypt": + from apachelib.password import crypt_password + return crypt_password(passwd) + elif ctype is "sha": + from apachelib.password import sha_password + return sha_password(passwd) + elif ctype is "md5": + from apachelib.password import md5_password + return md5_password(passwd) + + # We should never get here + raise ValueError("%s is not a valid value for ctype." % ctype) + +def generate_user(username, passwd, ctype="crypt"): + """Generate a single htaccess line. + """ + return "%s:%s" % (username, hash_password(passwd, ctype)) diff --git a/md5.py b/md5.py new file mode 100755 index 0000000..917612e --- /dev/null +++ b/md5.py @@ -0,0 +1,134 @@ +#!/usr/bin/python +""" +Apache Portable Runtime (APR) MD5 Calculation +by Mike Crute on July 12, 2008 +for SoftGroup Interactive, Inc. +Released under the terms of the BSD license. + +This is basically a feature-complete implementation of the MD5 +algorithm used by the APR, Apache and the htpasswd tool. APR +takes a different approach to generating MD5 sums which is a +little bit wierd. + +This is a pythonic adaption of the C code in the APR library. +If there are any questions please refer directly to the C code. +You'll find it in apache subversion under +/ap/apr-util/crypto/apr_md5.c +""" + +import string +from hashlib import md5 +from random import random +from math import floor + +__all__ = [ "generate_md5", "generate_salt", "generate_short_salt" ] + +# Defined as such for brevity +md5digest = lambda x: md5(x).digest() + +def ap_to64(input, count=4): + """Weird-ass implementation of base64 conversion used by the + APR library. + """ + chars = "./0123456789%s%s" % (string.uppercase, string.lowercase) + output = "" + input = int(input) # Need ints to do binary math + + for i in range(0, count): + output += chars[input & 0x3f] # Take 6 bits right + input >>= 6 # shift by 6 bits + + return output + +def generate_short_salt(): + """Generate a short, 2-character salt. + This is suitable for use in the htpasswd crypt routine. + """ + return generate_salt()[0:2] + +def generate_salt(): + """Mine a little salt for your passwords. + Returns 8 random characters in base64. It is 4 + 4 because the original + implmentation was in C and they wanted it it fit nicely in an integer. + """ + salt = ap_to64(floor(random() * 16777215)) + salt += ap_to64(floor(random() * 16777215)) + return salt + +def generate_md5(passwd, salt=None): + """Generate an APRfied MD5 hash. + This was adapted directly from the C code in the APR library. + It was made more pythonic where possible but please reference + the APR code for a better understanding of what's going on. + """ + # I know not what this means or how it may change in the future + # (well OK, its the APR version that generated the hash) and + # I don't think it is really "checked" by Apache but I'm too + # lazy to confirm this. + MAGIC_TOKEN = "$apr1$" + + # Mainly just used for testing but why not leave it? + salt = salt if salt else generate_salt() + + # Start with our password in the clear, a little magic and + # a pinch of salt + message = "%s%s%s" % (passwd, MAGIC_TOKEN, salt) + + # Then just as many characters of the MD5(pw, salt, pw) + retval = md5digest(passwd + salt + passwd) + passlen = len(passwd) + while passlen > 0: + end = 16 if passlen > 16 else passlen + message += retval[0:end] + passlen -= 16 + + # Then something really wierd + passlen = len(passwd) + while passlen != 0: + if passlen & 1: + message += chr(0) + else: + message += passwd[0] + passlen >>= 1 + + retval = md5digest(message) + + for i in range(0, 1000): + if i & 1: + message = passwd + else: + message = retval[0:16] + + if i % 3: + message += salt + + if i % 7: + message += passwd + + if i & 1: + message += retval[0:16] + else: + message += passwd + + retval = md5digest(message) + + # Now make the output string + output = ap_to64((ord(retval[0]) << 16) | (ord(retval[6]) << 8) | ord(retval[12])) + output += ap_to64((ord(retval[1]) << 16) | (ord(retval[7]) << 8) | ord(retval[13])) + output += ap_to64((ord(retval[2]) << 16) | (ord(retval[8]) << 8) | ord(retval[14])) + output += ap_to64((ord(retval[3]) << 16) | (ord(retval[9]) << 8) | ord(retval[15])) + output += ap_to64((ord(retval[4]) << 16) | (ord(retval[10]) << 8) | ord(retval[5])) + output += ap_to64(ord(retval[11]), 2) + + return "%s%s$%s" % (MAGIC_TOKEN, salt, output) + +def test_driver(): + """Run this to verify that the library is functioning properly. + This one test case should be enough to verify the functionality. + """ + output = generate_md5("test", "yTbof...") + assert output == "$apr1$yTbof...$r3r2AZWwYNbWRfNmLfrEh1" + print "Passed the test!" + +if __name__ == "__main__": + test_driver() diff --git a/password.py b/password.py new file mode 100644 index 0000000..3570cf4 --- /dev/null +++ b/password.py @@ -0,0 +1,31 @@ +#!/usr/bin/python +""" +Apache Password Hash Generation Functions +by Mike Crute on July 12, 2008 +for SoftGroup Interactive, Inc. +Released under the terms of the BSD license. + +A collection of functions used to generate Apache-style password hashes. +Algorithm information was collected for various sources around the web +and from analysis of the APR C code. +""" + +def crypt_password(passwd): + """Generate Apache-style CRYPT password hash. + """ + from crypt import crypt + from apachelib.md5 import generate_short_salt + return crypt(passwd, generate_short_salt()) + +def sha_password(passwd): + """Generate Apache-style SHA1 password hash. + """ + from hashlib import sha1 + from base64 import b64encode + return "{SHA}%s" % b64encode(sha1(passwd).digest()) + +def md5_password(passwd): + """Generate Apache-style MD5 password hash. + """ + from apachelib.md5 import generate_md5 + return generate_md5(passwd) -- cgit v1.2.3