package games.strategy.util; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; /** * A Java Implementation of the MD5Crypt function * Modified from the GANYMEDE network directory management system * released under the GNU General Public License * by the University of Texas at Austin * http://tools.arlut.utexas.edu/gash2/ * Original version from :Jonathan Abbey, jonabbey@arlut.utexas.edu * Modified by: Vladimir Silva, vladimir_silva@yahoo.com * Modification history: * 9/2005 * - Removed dependencies on a MD5 private implementation * - Added built-in java.security.MessageDigest (MD5) support * - Code cleanup * <br> * <br> * TODO Use SHA512(fast) or BCrypt(secure) in the future instead * this may be kept for backwards compatibility */ public class MD5Crypt { public static final String MAGIC = "$1$"; // Character set allowed for the salt string private static final String SALTCHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"; // Character set of the encrypted password: A-Za-z0-9./ private static final String itoa64 = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; /** * Function to return a string from the set: A-Za-z0-9./ * * @param size * Length of the string * @param v * value to be converted * @return A string of size (size) from the set A-Za-z0-9./ */ private static String to64(long v, int size) { final StringBuffer result = new StringBuffer(); while (--size >= 0) { result.append(itoa64.charAt((int) (v & 0x3f))); v >>>= 6; } return result.toString(); } private static void clearbits(final byte[] bits) { for (int i = 0; i < bits.length; i++) { bits[i] = 0; } } /** * convert an encoded unsigned byte value * into a int with the unsigned value. */ private static int bytes2u(final byte inp) { return inp & 0xff; } /** * LINUX/BSD MD5Crypt function. * * @param password * Password to be encrypted * @return The encrypted password as an MD5 hash */ public static String crypt(final String password) { final StringBuffer salt = new StringBuffer(); final java.util.Random rnd = new java.util.Random(); // build a random 8 chars salt while (salt.length() < 8) { final int index = (int) (rnd.nextFloat() * SALTCHARS.length()); salt.append(SALTCHARS.substring(index, index + 1)); } // crypt return crypt(password, salt.toString(), MAGIC); } /** * LINUX/BSD MD5Crypt function. * * @param salt * Random string used to initialize the MD5 engine * @param password * Password to be encrypted * @return The encrypted password as an MD5 hash */ public static String crypt(final String password, final String salt) { return crypt(password, salt, MAGIC); } /** * Linux/BSD MD5Crypt function. * * @param magic * $1$ for Linux/BSB, $apr1$ for Apache crypt * @param salt * 8 byte permutation string * @param password * user password * @return The encrypted password as an MD5 hash */ public static String crypt(final String password, String salt, final String magic) { if (password == null) { throw new IllegalArgumentException("Null password!"); } if (salt == null) { throw new IllegalArgumentException("Null salt!"); } if (magic == null) { throw new IllegalArgumentException("Null salt!"); } /* * Two MD5 hashes are used */ MessageDigest ctx; MessageDigest ctx1; try { ctx = MessageDigest.getInstance("md5"); ctx1 = MessageDigest.getInstance("md5"); } catch (final NoSuchAlgorithmException ex) { ex.printStackTrace(); return null; } /* Refine the Salt first */ /* If it starts with the magic string, then skip that */ if (salt.startsWith(magic)) { salt = salt.substring(magic.length()); } /* It stops at the first '$', max 8 chars */ if (salt.indexOf('$') != -1) { salt = salt.substring(0, salt.indexOf('$')); } if (salt.length() > 8) { salt = salt.substring(0, 8); } /** * Transformation set #1: * The password first, since that is what is most unknown * Magic string * Raw salt */ ctx.update(password.getBytes()); ctx.update(magic.getBytes()); ctx.update(salt.getBytes()); /* Then just as many characters of the MD5(pw,salt,pw) */ ctx1.update(password.getBytes()); ctx1.update(salt.getBytes()); ctx1.update(password.getBytes()); // ctx1.Final(); byte[] finalState = ctx1.digest(); for (int pl = password.length(); pl > 0; pl -= 16) { ctx.update(finalState, 0, pl > 16 ? 16 : pl); } /** * the original code claimed that finalState was being cleared * to keep dangerous bits out of memory, * but doing this is also required in order to get the right output. */ clearbits(finalState); /* Then something really weird... */ for (int i = password.length(); i != 0; i >>>= 1) { if ((i & 1) != 0) { ctx.update(finalState, 0, 1); } else { ctx.update(password.getBytes(), 0, 1); } } finalState = ctx.digest(); /** * and now, just to make sure things don't run too fast * On a 60 Mhz Pentium this takes 34 msec, so you would * need 30 seconds to build a 1000 entry dictionary... * (The above timings from the C version) */ for (int i = 0; i < 1000; i++) { try { ctx1 = MessageDigest.getInstance("md5"); } catch (final NoSuchAlgorithmException e0) { return null; } if ((i & 1) != 0) { ctx1.update(password.getBytes()); } else { ctx1.update(finalState, 0, 16); } if ((i % 3) != 0) { ctx1.update(salt.getBytes()); } if ((i % 7) != 0) { ctx1.update(password.getBytes()); } if ((i & 1) != 0) { ctx1.update(finalState, 0, 16); } else { ctx1.update(password.getBytes()); } // Final(); finalState = ctx1.digest(); } /* Now make the output string */ final StringBuffer result = new StringBuffer(); result.append(magic); result.append(salt); result.append("$"); /** * Build a 22 byte output string from the set: A-Za-z0-9./ */ long l = (bytes2u(finalState[0]) << 16) | (bytes2u(finalState[6]) << 8) | bytes2u(finalState[12]); result.append(to64(l, 4)); l = (bytes2u(finalState[1]) << 16) | (bytes2u(finalState[7]) << 8) | bytes2u(finalState[13]); result.append(to64(l, 4)); l = (bytes2u(finalState[2]) << 16) | (bytes2u(finalState[8]) << 8) | bytes2u(finalState[14]); result.append(to64(l, 4)); l = (bytes2u(finalState[3]) << 16) | (bytes2u(finalState[9]) << 8) | bytes2u(finalState[15]); result.append(to64(l, 4)); l = (bytes2u(finalState[4]) << 16) | (bytes2u(finalState[10]) << 8) | bytes2u(finalState[5]); result.append(to64(l, 4)); l = bytes2u(finalState[11]); result.append(to64(l, 2)); /* Don't leave anything around in vm they could use. */ clearbits(finalState); return result.toString(); } public static String getSalt(final String magic, final String encrypted) { if (!encrypted.startsWith(magic)) { throw new IllegalStateException("Magic doesnt mactch encrypted"); } final String valNoMagic = encrypted.substring(magic.length()); return valNoMagic.substring(0, valNoMagic.indexOf("$")); } }