Support password hashes imported from other wiki systems
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).
1 diff -urN moin-1.9.3/MoinMoin/user.py moin-1.9.3-patch/MoinMoin/user.py
2 --- moin-1.9.3/MoinMoin/user.py 2010-06-26 23:46:44.000000000 +0200
3 +++ moin-1.9.3-patch/MoinMoin/user.py 2010-10-24 00:04:12.000000000 +0200
4 @@ -15,7 +15,8 @@
5 * storage code
6
7 @copyright: 2000-2004 Juergen Hermann <jh@web.de>,
8 - 2003-2007 MoinMoin:ThomasWaldmann
9 + 2003-2007 MoinMoin:ThomasWaldmann,
10 + 2010 Michael Foetsch <foetsch@yahoo.com>
11 @license: GNU GPL, see COPYING for details.
12 """
13
14 @@ -24,9 +25,14 @@
15 from MoinMoin.support.python_compatibility import hash_new, hmac_new
16
17 from MoinMoin import config, caching, wikiutil, i18n, events
18 -from MoinMoin.util import timefuncs, random_string
19 +from MoinMoin.util import timefuncs, random_string, md5crypt
20 from MoinMoin.wikiutil import url_quote_plus
21
22 +try:
23 + import crypt
24 +except ImportError:
25 + crypt = None
26 +
27
28 def getUserList(request):
29 """ Get a list of all (numerical) user IDs.
30 @@ -506,13 +512,36 @@
31 if not password:
32 return False, False
33
34 - if epwd[:5] == '{SHA}':
35 - enc = '{SHA}' + base64.encodestring(hash_new('sha1', password.encode('utf-8')).digest()).rstrip()
36 - if epwd == enc:
37 - data['enc_password'] = encodePassword(password) # upgrade to SSHA
38 - return True, True
39 - return False, False
40 -
41 + # Check and upgrade passwords from earlier MoinMoin versions and
42 + # passwords imported from other wiki systems.
43 + for method in ['{SHA}', '{APR1}', '{MD5}', '{DES}']:
44 + if epwd.startswith(method):
45 + d = epwd[len(method):]
46 + if method == '{SHA}':
47 + enc = base64.encodestring(
48 + hash_new('sha1', password.encode('utf-8')).digest()).rstrip()
49 + elif method == '{APR1}':
50 + # d is of the form "$apr1$<salt>$<hash>"
51 + salt = d.split('$')[2]
52 + enc = md5crypt.apache_md5_crypt(password.encode('utf-8'),
53 + salt.encode('ascii'))
54 + elif method == '{MD5}':
55 + # d is of the form "$1$<salt>$<hash>"
56 + salt = d.split('$')[2]
57 + enc = md5crypt.unix_md5_crypt(password.encode('utf-8'),
58 + salt.encode('ascii'))
59 + elif method == '{DES}':
60 + if crypt is None:
61 + return False, False
62 + # d is 2 characters salt + 11 characters hash
63 + salt = d[:2]
64 + enc = crypt.crypt(password.encode('utf-8'), salt.encode('ascii'))
65 +
66 + if epwd == method + enc:
67 + data['enc_password'] = encodePassword(password) # upgrade to SSHA
68 + return True, True
69 + return False, False
70 +
71 if epwd[:6] == '{SSHA}':
72 data = base64.decodestring(epwd[6:])
73 salt = data[20:]
74 diff -urN moin-1.9.3/MoinMoin/util/md5crypt.py moin-1.9.3-patch/MoinMoin/util/md5crypt.py
75 --- moin-1.9.3/MoinMoin/util/md5crypt.py 1970-01-01 01:00:00.000000000 +0100
76 +++ moin-1.9.3-patch/MoinMoin/util/md5crypt.py 2010-10-23 20:42:31.000000000 +0200
77 @@ -0,0 +1,166 @@
78 +#########################################################
79 +# md5crypt.py
80 +#
81 +# 0423.2000 by michal wallace http://www.sabren.com/
82 +# based on perl's Crypt::PasswdMD5 by Luis Munoz (lem@cantv.net)
83 +# based on /usr/src/libcrypt/crypt.c from FreeBSD 2.2.5-RELEASE
84 +#
85 +# MANY THANKS TO
86 +#
87 +# Carey Evans - http://home.clear.net.nz/pages/c.evans/
88 +# Dennis Marti - http://users.starpower.net/marti1/
89 +#
90 +# For the patches that got this thing working!
91 +#
92 +#########################################################
93 +"""md5crypt.py - Provides interoperable MD5-based crypt() function
94 +
95 +SYNOPSIS
96 +
97 + import md5crypt.py
98 +
99 + cryptedpassword = md5crypt.md5crypt(password, salt);
100 +
101 +DESCRIPTION
102 +
103 +unix_md5_crypt() provides a crypt()-compatible interface to the
104 +rather new MD5-based crypt() function found in modern operating systems.
105 +It's based on the implementation found on FreeBSD 2.2.[56]-RELEASE and
106 +contains the following license in it:
107 +
108 + "THE BEER-WARE LICENSE" (Revision 42):
109 + <phk@login.dknet.dk> wrote this file. As long as you retain this notice you
110 + can do whatever you want with this stuff. If we meet some day, and you think
111 + this stuff is worth it, you can buy me a beer in return. Poul-Henning Kamp
112 +
113 +apache_md5_crypt() provides a function compatible with Apache's
114 +.htpasswd files. This was contributed by Bryan Hart <bryan@eai.com>.
115 +
116 +"""
117 +
118 +MAGIC = '$1$' # Magic string
119 +ITOA64 = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
120 +
121 +import hashlib
122 +
123 +def to64 (v, n):
124 + ret = ''
125 + while (n - 1 >= 0):
126 + n = n - 1
127 + ret = ret + ITOA64[v & 0x3f]
128 + v = v >> 6
129 + return ret
130 +
131 +
132 +def apache_md5_crypt (pw, salt):
133 + # change the Magic string to match the one used by Apache
134 + return unix_md5_crypt(pw, salt, '$apr1$')
135 +
136 +
137 +def unix_md5_crypt(pw, salt, magic=None):
138 +
139 + if magic==None:
140 + magic = MAGIC
141 +
142 + # Take care of the magic string if present
143 + if salt[:len(magic)] == magic:
144 + salt = salt[len(magic):]
145 +
146 +
147 + # salt can have up to 8 characters:
148 + import string
149 + salt = string.split(salt, '$', 1)[0]
150 + salt = salt[:8]
151 +
152 + ctx = pw + magic + salt
153 +
154 + md5 = hashlib.md5()
155 + md5.update(pw + salt + pw)
156 + final = md5.digest()
157 +
158 + for pl in range(len(pw),0,-16):
159 + if pl > 16:
160 + ctx = ctx + final[:16]
161 + else:
162 + ctx = ctx + final[:pl]
163 +
164 +
165 + # Now the 'weird' xform (??)
166 +
167 + i = len(pw)
168 + while i:
169 + if i & 1:
170 + ctx = ctx + chr(0) #if ($i & 1) { $ctx->add(pack("C", 0)); }
171 + else:
172 + ctx = ctx + pw[0]
173 + i = i >> 1
174 +
175 + md5 = hashlib.md5()
176 + md5.update(ctx)
177 + final = md5.digest()
178 +
179 + # The following is supposed to make
180 + # things run slower.
181 +
182 + # my question: WTF???
183 +
184 + for i in range(1000):
185 + ctx1 = ''
186 + if i & 1:
187 + ctx1 = ctx1 + pw
188 + else:
189 + ctx1 = ctx1 + final[:16]
190 +
191 + if i % 3:
192 + ctx1 = ctx1 + salt
193 +
194 + if i % 7:
195 + ctx1 = ctx1 + pw
196 +
197 + if i & 1:
198 + ctx1 = ctx1 + final[:16]
199 + else:
200 + ctx1 = ctx1 + pw
201 +
202 +
203 + md5 = hashlib.md5()
204 + md5.update(ctx1)
205 + final = md5.digest()
206 +
207 +
208 + # Final xform
209 +
210 + passwd = ''
211 +
212 + passwd = passwd + to64((int(ord(final[0])) << 16)
213 + |(int(ord(final[6])) << 8)
214 + |(int(ord(final[12]))),4)
215 +
216 + passwd = passwd + to64((int(ord(final[1])) << 16)
217 + |(int(ord(final[7])) << 8)
218 + |(int(ord(final[13]))), 4)
219 +
220 + passwd = passwd + to64((int(ord(final[2])) << 16)
221 + |(int(ord(final[8])) << 8)
222 + |(int(ord(final[14]))), 4)
223 +
224 + passwd = passwd + to64((int(ord(final[3])) << 16)
225 + |(int(ord(final[9])) << 8)
226 + |(int(ord(final[15]))), 4)
227 +
228 + passwd = passwd + to64((int(ord(final[4])) << 16)
229 + |(int(ord(final[10])) << 8)
230 + |(int(ord(final[5]))), 4)
231 +
232 + passwd = passwd + to64((int(ord(final[11]))), 2)
233 +
234 +
235 + return magic + salt + '$' + passwd
236 +
237 +
238 +## assign a wrapper function:
239 +md5crypt = unix_md5_crypt
240 +
241 +if __name__ == "__main__":
242 + print unix_md5_crypt("cat", "hat")
243 +
244
Implemented by: http://hg.moinmo.in/moin/1.9/rev/69668ad0cae7