/* * This file is part of gwap, an open platform for games with a purpose * * Copyright (C) 2013 * Project play4science * Lehr- und Forschungseinheit für Programmier- und Modellierungssprachen * Ludwig-Maximilians-Universität München * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package gwap.authentication; 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 */ public class MD5Crypt { // Character set allowed for the salt string static private final String SALTCHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"; // Character set of the encrypted password: A-Za-z0-9./ static private final String itoa64 = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; /** * Function to return a string from the set: A-Za-z0-9./ * @return A string of size (size) from the set A-Za-z0-9./ * @param size Length of the string * @param v value to be converted */ static private final String to64( long v, int size ) { StringBuffer result = new StringBuffer(); while ( --size >= 0 ) { result.append( itoa64.charAt( ( int )( v & 0x3f ) ) ); v >>>= 6; } return result.toString(); } static private final void clearbits( 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. */ static private final int bytes2u( byte inp ) { return ( int )inp & 0xff; } /** * LINUX/BSD MD5Crypt function * @return The encrypted password as an MD5 hash * @param password Password to be encrypted */ static public final String crypt(String password) { StringBuffer salt = new StringBuffer(); java.util.Random rnd = new java.util.Random(); // build a random 8 chars salt while (salt.length() < 8) { int index = (int) (rnd.nextFloat() * SALTCHARS.length()); salt.append(SALTCHARS.substring(index, index+1)); } // crypt return crypt(password, salt.toString(), "$1$"); } /** * LINUX/BSD MD5Crypt function * @return The encrypted password as an MD5 hash * @param salt Random string used to initialize the MD5 engine * @param password Password to be encrypted */ static public final String crypt(String password, String salt) { return crypt(password, salt, "$1$"); } /** * Linux/BSD MD5Crypt function * @throws java.lang.Exception * @return The encrypted password as an MD5 hash * @param magic $1$ for Linux/BSB, $apr1$ for Apache crypt * @param salt 8 byte permutation string * @param password user password */ static public final String crypt( String password, String salt, String magic ) { byte finalState[]; long l; /** * Two MD5 hashes are used */ MessageDigest ctx, ctx1; try { ctx = MessageDigest.getInstance( "md5" ); ctx1 = MessageDigest.getInstance( "md5" ); } catch (NoSuchAlgorithmException ex) { System.err.println(ex); 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() ); finalState = ctx1.digest(); // ctx1.Final(); 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 (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() ); } finalState = ctx1.digest(); // Final(); } /* Now make the output string */ 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./ */ 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(); } /** * Test subroutine * @param args */ static final String USAGE = "MD5Crypt <password> <salt>"; public static void main(String[] args) { try { if ( args.length != 2) System.err.println(USAGE); else System.out.println(MD5Crypt.crypt(args[0], args[1])); } catch (Exception ex) { System.err.println(ex); } } }