001 /*
002 * This file was extracted from Ganymede 1.0.11 (http://tools.arlut.utexas.edu/gash2/).
003 */
004
005 /*
006
007 MD5Crypt.java
008
009 Created: 3 November 1999
010 Release: $Name: $
011 Version: $Revision: 1.1 $
012 Last Mod Date: $Date: 2004/07/12 13:35:20 $
013 Java Port By: Jonathan Abbey, jonabbey@arlut.utexas.edu
014 Original C Version:
015 ----------------------------------------------------------------------------
016 "THE BEER-WARE LICENSE" (Revision 42):
017 <phk@login.dknet.dk> wrote this file. As long as you retain this notice you
018 can do whatever you want with this stuff. If we meet some day, and you think
019 this stuff is worth it, you can buy me a beer in return. Poul-Henning Kamp
020 ----------------------------------------------------------------------------
021
022 -----------------------------------------------------------------------
023
024 Ganymede Directory Management System
025
026 Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002
027 The University of Texas at Austin.
028
029 Contact information
030
031 Web site: http://www.arlut.utexas.edu/gash2
032 Author Email: ganymede_author@arlut.utexas.edu
033 Email mailing list: ganymede@arlut.utexas.edu
034
035 US Mail:
036
037 Computer Science Division
038 Applied Research Laboratories
039 The University of Texas at Austin
040 PO Box 8029, Austin TX 78713-8029
041
042 Telephone: (512) 835-3200
043
044 This program is free software; you can redistribute it and/or modify
045 it under the terms of the GNU General Public License as published by
046 the Free Software Foundation; either version 2 of the License, or
047 (at your option) any later version.
048
049 This program is distributed in the hope that it will be useful,
050 but WITHOUT ANY WARRANTY; without even the implied warranty of
051 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
052 GNU General Public License for more details.
053
054 You should have received a copy of the GNU General Public License
055 along with this program; if not, write to the Free Software
056 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
057 02111-1307, USA
058
059 */
060
061 package md5;
062
063 /*------------------------------------------------------------------------------
064 class
065 MD5Crypt
066
067 ------------------------------------------------------------------------------*/
068
069 /**
070 * <p>This class defines a method,
071 * {@link MD5Crypt#crypt(java.lang.String, java.lang.String) crypt()}, which
072 * takes a password and a salt string and generates an OpenBSD/FreeBSD/Linux-compatible
073 * md5-encoded password entry.</p>
074 *
075 * <p>Created: 3 November 1999</p>
076 * <p>Release: $Name: $</p>
077 * <p>Version: $Revision: 1.1 $</p>
078 * <p>Last Mod Date: $Date: 2004/07/12 13:35:20 $</p>
079 * <p>Java Code By: Jonathan Abbey, jonabbey@arlut.utexas.edu</p>
080 * <p>Original C Version:<pre>
081 * ----------------------------------------------------------------------------
082 * "THE BEER-WARE LICENSE" (Revision 42):
083 * <phk@login.dknet.dk> wrote this file. As long as you retain this notice you
084 * can do whatever you want with this stuff. If we meet some day, and you think
085 * this stuff is worth it, you can buy me a beer in return. Poul-Henning Kamp
086 * ----------------------------------------------------------------------------
087 * </pre></p>
088 *
089 * @author Jonathan Abbey <jonabbey at arlut.utexas.edu>
090 */
091
092 public final class MD5Crypt {
093
094 /**
095 *
096 * Command line test rig.
097 *
098 */
099
100 static public void main(String argv[])
101 {
102 if ((argv.length < 1) || (argv.length > 3))
103 {
104 System.err.println("Usage: MD5Crypt [-apache] password salt");
105 System.exit(1);
106 }
107
108 if (argv.length == 3)
109 {
110 System.err.println(MD5Crypt.apacheCrypt(argv[1], argv[2]));
111 }
112 else if (argv.length == 2)
113 {
114 System.err.println(MD5Crypt.crypt(argv[0], argv[1]));
115 }
116 else
117 {
118 System.err.println(MD5Crypt.crypt(argv[0]));
119 }
120
121 System.exit(0);
122 }
123
124 static private final String SALTCHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
125
126 static private final String itoa64 = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
127
128 static private final String to64(long v, int size)
129 {
130 StringBuffer result = new StringBuffer();
131
132 while (--size >= 0)
133 {
134 result.append(itoa64.charAt((int) (v & 0x3f)));
135 v >>>= 6;
136 }
137
138 return result.toString();
139 }
140
141 static private final void clearbits(byte bits[])
142 {
143 for (int i = 0; i < bits.length; i++)
144 {
145 bits[i] = 0;
146 }
147 }
148
149 /**
150 * convert an encoded unsigned byte value into a int
151 * with the unsigned value.
152 */
153
154 static private final int bytes2u(byte inp)
155 {
156 return (int) inp & 0xff;
157 }
158
159 /**
160 * <p>This method actually generates a OpenBSD/FreeBSD/Linux PAM compatible
161 * md5-encoded password hash from a plaintext password and a
162 * salt.</p>
163 *
164 * <p>The resulting string will be in the form '$1$<salt>$<hashed mess></p>
165 *
166 * @param password Plaintext password
167 *
168 * @return An OpenBSD/FreeBSD/Linux-compatible md5-hashed password field.
169 */
170
171 static public final String crypt(String password)
172 {
173 StringBuffer salt = new StringBuffer();
174 java.util.Random randgen = new java.util.Random();
175
176 /* -- */
177
178 while (salt.length() < 8)
179 {
180 int index = (int) (randgen.nextFloat() * SALTCHARS.length());
181 salt.append(SALTCHARS.substring(index, index+1));
182 }
183
184 return MD5Crypt.crypt(password, salt.toString());
185 }
186
187 /**
188 * <p>This method actually generates a OpenBSD/FreeBSD/Linux PAM compatible
189 * md5-encoded password hash from a plaintext password and a
190 * salt.</p>
191 *
192 * <p>The resulting string will be in the form '$1$<salt>$<hashed mess></p>
193 *
194 * @param password Plaintext password
195 * @param salt A short string to use to randomize md5. May start with $1$, which
196 * will be ignored. It is explicitly permitted to pass a pre-existing
197 * MD5Crypt'ed password entry as the salt. crypt() will strip the salt
198 * chars out properly.
199 *
200 * @return An OpenBSD/FreeBSD/Linux-compatible md5-hashed password field.
201 */
202
203 static public final String crypt(String password, String salt)
204 {
205 return MD5Crypt.crypt(password, salt, "$1$");
206 }
207
208 /**
209 * <p>This method generates an Apache MD5 compatible
210 * md5-encoded password hash from a plaintext password and a
211 * salt.</p>
212 *
213 * <p>The resulting string will be in the form '$apr1$<salt>$<hashed mess></p>
214 *
215 * @param password Plaintext password
216 *
217 * @return An Apache-compatible md5-hashed password string.
218 */
219
220 static public final String apacheCrypt(String password)
221 {
222 StringBuffer salt = new StringBuffer();
223 java.util.Random randgen = new java.util.Random();
224
225 /* -- */
226
227 while (salt.length() < 8)
228 {
229 int index = (int) (randgen.nextFloat() * SALTCHARS.length());
230 salt.append(SALTCHARS.substring(index, index+1));
231 }
232
233 return MD5Crypt.apacheCrypt(password, salt.toString());
234 }
235
236 /**
237 * <p>This method actually generates an Apache MD5 compatible
238 * md5-encoded password hash from a plaintext password and a
239 * salt.</p>
240 *
241 * <p>The resulting string will be in the form '$apr1$<salt>$<hashed mess></p>
242 *
243 * @param password Plaintext password
244 * @param salt A short string to use to randomize md5. May start with $apr1$, which
245 * will be ignored. It is explicitly permitted to pass a pre-existing
246 * MD5Crypt'ed password entry as the salt. crypt() will strip the salt
247 * chars out properly.
248 *
249 * @return An Apache-compatible md5-hashed password string.
250 */
251
252 static public final String apacheCrypt(String password, String salt)
253 {
254 return MD5Crypt.crypt(password, salt, "$apr1$");
255 }
256
257 /**
258 * <p>This method actually generates md5-encoded password hash from
259 * a plaintext password, a salt, and a magic string.</p>
260 *
261 * <p>There are two magic strings that make sense to use here.. '$1$' is the
262 * magic string used by the FreeBSD/Linux/OpenBSD MD5Crypt algorithm, and
263 * '$apr1$' is the magic string used by the Apache MD5Crypt algorithm.</p>
264 *
265 * <p>The resulting string will be in the form '<magic><salt>$<hashed mess></p>
266 *
267 * @param password Plaintext password @param salt A short string to
268 * use to randomize md5. May start with the magic string, which
269 * will be ignored. It is explicitly permitted to pass a
270 * pre-existing MD5Crypt'ed password entry as the salt. crypt()
271 * will strip the salt chars out properly.
272 *
273 * @return An md5-hashed password string.
274 */
275
276 static public final String crypt(String password, String salt, String magic)
277 {
278 /* This string is magic for this algorithm. Having it this way,
279 * we can get get better later on */
280
281 byte finalState[];
282 MD5 ctx, ctx1;
283 long l;
284
285 /* -- */
286
287 /* Refine the Salt first */
288
289 /* If it starts with the magic string, then skip that */
290
291 if (salt.startsWith(magic))
292 {
293 salt = salt.substring(magic.length());
294 }
295
296 /* It stops at the first '$', max 8 chars */
297
298 if (salt.indexOf('$') != -1)
299 {
300 salt = salt.substring(0, salt.indexOf('$'));
301 }
302
303 if (salt.length() > 8)
304 {
305 salt = salt.substring(0, 8);
306 }
307
308 ctx = new MD5();
309
310 ctx.Update(password); // The password first, since that is what is most unknown
311 ctx.Update(magic); // Then our magic string
312 ctx.Update(salt); // Then the raw salt
313
314 /* Then just as many characters of the MD5(pw,salt,pw) */
315
316 ctx1 = new MD5();
317 ctx1.Update(password);
318 ctx1.Update(salt);
319 ctx1.Update(password);
320 finalState = ctx1.Final();
321
322 for (int pl = password.length(); pl > 0; pl -= 16)
323 {
324 ctx.Update(finalState, pl > 16? 16 : pl);
325 }
326
327 /* the original code claimed that finalState was being cleared
328 to keep dangerous bits out of memory, but doing this is also
329 required in order to get the right output. */
330
331 clearbits(finalState);
332
333 /* Then something really weird... */
334
335 for (int i = password.length(); i != 0; i >>>=1)
336 {
337 if ((i & 1) != 0)
338 {
339 ctx.Update(finalState, 1);
340 }
341 else
342 {
343 ctx.Update(password.getBytes(), 1);
344 }
345 }
346
347 finalState = ctx.Final();
348
349 /*
350 * and now, just to make sure things don't run too fast
351 * On a 60 Mhz Pentium this takes 34 msec, so you would
352 * need 30 seconds to build a 1000 entry dictionary...
353 *
354 * (The above timings from the C version)
355 */
356
357 for (int i = 0; i < 1000; i++)
358 {
359 ctx1 = new MD5();
360
361 if ((i & 1) != 0)
362 {
363 ctx1.Update(password);
364 }
365 else
366 {
367 ctx1.Update(finalState, 16);
368 }
369
370 if ((i % 3) != 0)
371 {
372 ctx1.Update(salt);
373 }
374
375 if ((i % 7) != 0)
376 {
377 ctx1.Update(password);
378 }
379
380 if ((i & 1) != 0)
381 {
382 ctx1.Update(finalState, 16);
383 }
384 else
385 {
386 ctx1.Update(password);
387 }
388
389 finalState = ctx1.Final();
390 }
391
392 /* Now make the output string */
393
394 StringBuffer result = new StringBuffer();
395
396 result.append(magic);
397 result.append(salt);
398 result.append("$");
399
400 l = (bytes2u(finalState[0]) << 16) | (bytes2u(finalState[6]) << 8) | bytes2u(finalState[12]);
401 result.append(to64(l, 4));
402
403 l = (bytes2u(finalState[1]) << 16) | (bytes2u(finalState[7]) << 8) | bytes2u(finalState[13]);
404 result.append(to64(l, 4));
405
406 l = (bytes2u(finalState[2]) << 16) | (bytes2u(finalState[8]) << 8) | bytes2u(finalState[14]);
407 result.append(to64(l, 4));
408
409 l = (bytes2u(finalState[3]) << 16) | (bytes2u(finalState[9]) << 8) | bytes2u(finalState[15]);
410 result.append(to64(l, 4));
411
412 l = (bytes2u(finalState[4]) << 16) | (bytes2u(finalState[10]) << 8) | bytes2u(finalState[5]);
413 result.append(to64(l, 4));
414
415 l = bytes2u(finalState[11]);
416 result.append(to64(l, 2));
417
418 /* Don't leave anything around in vm they could use. */
419 clearbits(finalState);
420
421 return result.toString();
422 }
423 }