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$&lt;salt&gt;$&lt;hashed mess&gt;</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$&lt;salt&gt;$&lt;hashed mess&gt;</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$&lt;salt&gt;$&lt;hashed mess&gt;</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$&lt;salt&gt;$&lt;hashed mess&gt;</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 '&lt;magic&gt;&lt;salt&gt;$&lt;hashed mess&gt;</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    }