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 }