changeset 5720:69668ad0cae7

add support for other password hashes (thanks to Michael Foetsch) From the feature request: When converting user accounts from a different wiki system, the password hash might be different from {SSHA}, which MoinMoin uses. When we converted http://www.gnewsense.org/ from PmWiki to MoinMoin, some of the user accounts used {DES} hashes, some used {APR1}. We wanted the >3000 users to be able to log in to the new site without any action on their part. MoinMoin already accepts {SHA} hashes and upgrades them to {SSHA}. This patch adds support for: * {DES} (Unix crypt(3)) * {MD5} (MD5-based crypt()) * {APR1} (Apache .htpasswd).
author Thomas Waldmann <tw AT waldmann-edv DOT de>
date Sun, 24 Oct 2010 00:26:04 +0200
parents 422feb8d48ac
children a4a7f275b7b3
files MoinMoin/support/md5crypt.py MoinMoin/user.py
diffstat 2 files changed, 203 insertions(+), 7 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/support/md5crypt.py	Sun Oct 24 00:26:04 2010 +0200
@@ -0,0 +1,166 @@
+#########################################################
+# md5crypt.py
+#
+# 0423.2000 by michal wallace http://www.sabren.com/
+# based on perl's Crypt::PasswdMD5 by Luis Munoz (lem@cantv.net)
+# based on /usr/src/libcrypt/crypt.c from FreeBSD 2.2.5-RELEASE
+#
+# MANY THANKS TO
+#
+#  Carey Evans - http://home.clear.net.nz/pages/c.evans/
+#  Dennis Marti - http://users.starpower.net/marti1/
+#
+#  For the patches that got this thing working!
+#
+#########################################################
+"""md5crypt.py - Provides interoperable MD5-based crypt() function
+
+SYNOPSIS
+
+        import md5crypt.py
+
+        cryptedpassword = md5crypt.md5crypt(password, salt);
+
+DESCRIPTION
+
+unix_md5_crypt() provides a crypt()-compatible interface to the
+rather new MD5-based crypt() function found in modern operating systems.
+It's based on the implementation found on FreeBSD 2.2.[56]-RELEASE and
+contains the following license in it:
+
+ "THE BEER-WARE LICENSE" (Revision 42):
+ <phk@login.dknet.dk> wrote this file.  As long as you retain this notice you
+ can do whatever you want with this stuff. If we meet some day, and you think
+ this stuff is worth it, you can buy me a beer in return.   Poul-Henning Kamp
+
+apache_md5_crypt() provides a function compatible with Apache's
+.htpasswd files. This was contributed by Bryan Hart <bryan@eai.com>.
+
+"""
+
+MAGIC = '$1$'                   # Magic string
+ITOA64 = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
+
+import hashlib
+
+def to64 (v, n):
+    ret = ''
+    while (n - 1 >= 0):
+        n = n - 1
+        ret = ret + ITOA64[v & 0x3f]
+        v = v >> 6
+    return ret
+
+
+def apache_md5_crypt (pw, salt):
+    # change the Magic string to match the one used by Apache
+    return unix_md5_crypt(pw, salt, '$apr1$')
+
+
+def unix_md5_crypt(pw, salt, magic=None):
+
+    if magic==None:
+        magic = MAGIC
+
+    # Take care of the magic string if present
+    if salt[:len(magic)] == magic:
+        salt = salt[len(magic):]
+
+
+    # salt can have up to 8 characters:
+    import string
+    salt = string.split(salt, '$', 1)[0]
+    salt = salt[:8]
+
+    ctx = pw + magic + salt
+
+    md5 = hashlib.md5()
+    md5.update(pw + salt + pw)
+    final = md5.digest()
+
+    for pl in range(len(pw),0,-16):
+        if pl > 16:
+            ctx = ctx + final[:16]
+        else:
+            ctx = ctx + final[:pl]
+
+
+    # Now the 'weird' xform (??)
+
+    i = len(pw)
+    while i:
+        if i & 1:
+            ctx = ctx + chr(0)  #if ($i & 1) { $ctx->add(pack("C", 0)); }
+        else:
+            ctx = ctx + pw[0]
+        i = i >> 1
+
+    md5 = hashlib.md5()
+    md5.update(ctx)
+    final = md5.digest()
+
+    # The following is supposed to make
+    # things run slower.
+
+    # my question: WTF???
+
+    for i in range(1000):
+        ctx1 = ''
+        if i & 1:
+            ctx1 = ctx1 + pw
+        else:
+            ctx1 = ctx1 + final[:16]
+
+        if i % 3:
+            ctx1 = ctx1 + salt
+
+        if i % 7:
+            ctx1 = ctx1 + pw
+
+        if i & 1:
+            ctx1 = ctx1 + final[:16]
+        else:
+            ctx1 = ctx1 + pw
+
+
+        md5 = hashlib.md5()
+        md5.update(ctx1)
+        final = md5.digest()
+
+
+    # Final xform
+
+    passwd = ''
+
+    passwd = passwd + to64((int(ord(final[0])) << 16)
+                           |(int(ord(final[6])) << 8)
+                           |(int(ord(final[12]))),4)
+
+    passwd = passwd + to64((int(ord(final[1])) << 16)
+                           |(int(ord(final[7])) << 8)
+                           |(int(ord(final[13]))), 4)
+
+    passwd = passwd + to64((int(ord(final[2])) << 16)
+                           |(int(ord(final[8])) << 8)
+                           |(int(ord(final[14]))), 4)
+
+    passwd = passwd + to64((int(ord(final[3])) << 16)
+                           |(int(ord(final[9])) << 8)
+                           |(int(ord(final[15]))), 4)
+
+    passwd = passwd + to64((int(ord(final[4])) << 16)
+                           |(int(ord(final[10])) << 8)
+                           |(int(ord(final[5]))), 4)
+
+    passwd = passwd + to64((int(ord(final[11]))), 2)
+
+
+    return magic + salt + '$' + passwd
+
+
+## assign a wrapper function:
+md5crypt = unix_md5_crypt
+
+if __name__ == "__main__":
+    print unix_md5_crypt("cat", "hat")
+
--- a/MoinMoin/user.py	Wed Oct 13 15:24:40 2010 +0200
+++ b/MoinMoin/user.py	Sun Oct 24 00:26:04 2010 +0200
@@ -15,11 +15,18 @@
       * storage code
 
     @copyright: 2000-2004 Juergen Hermann <jh@web.de>,
-                2003-2007 MoinMoin:ThomasWaldmann
+                2003-2007 MoinMoin:ThomasWaldmann,
+                2010 Michael Foetsch <foetsch@yahoo.com>
     @license: GNU GPL, see COPYING for details.
 """
 
 import os, time, codecs, base64
+import md5crypt
+
+try:
+    import crypt
+except ImportError:
+    crypt = None
 
 from MoinMoin.support.python_compatibility import hash_new, hmac_new
 
@@ -506,12 +513,35 @@
         if not password:
             return False, False
 
-        if epwd[:5] == '{SHA}':
-            enc = '{SHA}' + base64.encodestring(hash_new('sha1', password.encode('utf-8')).digest()).rstrip()
-            if epwd == enc:
-                data['enc_password'] = encodePassword(password) # upgrade to SSHA
-                return True, True
-            return False, False
+        # Check and upgrade passwords from earlier MoinMoin versions and
+        # passwords imported from other wiki systems.
+        for method in ['{SHA}', '{APR1}', '{MD5}', '{DES}']:
+            if epwd.startswith(method):
+                d = epwd[len(method):]
+                if method == '{SHA}':
+                    enc = base64.encodestring(
+                        hash_new('sha1', password.encode('utf-8')).digest()).rstrip()
+                elif method == '{APR1}':
+                    # d is of the form "$apr1$<salt>$<hash>"
+                    salt = d.split('$')[2]
+                    enc = md5crypt.apache_md5_crypt(password.encode('utf-8'),
+                                                    salt.encode('ascii'))
+                elif method == '{MD5}':
+                    # d is of the form "$1$<salt>$<hash>"
+                    salt = d.split('$')[2]
+                    enc = md5crypt.unix_md5_crypt(password.encode('utf-8'),
+                                                  salt.encode('ascii'))
+                elif method == '{DES}':
+                    if crypt is None:
+                        return False, False
+                    # d is 2 characters salt + 11 characters hash
+                    salt = d[:2]
+                    enc = crypt.crypt(password.encode('utf-8'), salt.encode('ascii'))
+
+                if epwd == method + enc:
+                    data['enc_password'] = encodePassword(password) # upgrade to SSHA
+                    return True, True
+                return False, False
 
         if epwd[:6] == '{SSHA}':
             data = base64.decodestring(epwd[6:])