MD5Crypt.java

/*
 * GovWay - A customizable API Gateway 
 * https://govway.org
 * 
 * Copyright (c) 2005-2024 Link.it srl (https://link.it). 
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 3, as published by
 * the Free Software Foundation.
 *
 * 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */


package org.openspcoop2.utils.crypt;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;

import org.openspcoop2.utils.random.RandomGenerator;

/**
 * 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
 *
 * @author Sandra Giangrandi (sandra@link.it)
 * @author $Author$
 * @version $Rev$, $Date$
 */

@Deprecated
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";

	public static String newSalt() {

        int c = 0;
        byte[] arrByte = new byte[2];

        RandomGenerator randomGenerator = new RandomGenerator(false);
        randomGenerator.nextRandomBytes(arrByte);
        
        for (int i = 0; i<2; i++) {
            c = arrByte[i] >> 6;  // div(64)
            c = ((arrByte[i] & 0xff) - (c<<6));
            if ( c <= 11 ) // 46-57 ./0..9
                c+=46;
            if ( c >= 12 && c <= 37 ) // 65-90 a..z
               c+=(65-12);     
            if ( c >= 38 && c <= 63 ) // 97-122 A..Z
                c+=(97-38);
            arrByte[i] = (byte) c;
        }

        String pw = new String(arrByte);
        return pw;
    }
	
	/**
	 * 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) {
		StringBuilder result = new StringBuilder();

		while (--size >= 0) {
			result.append(MD5Crypt.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 inp & 0xff;
	}

	private static SecureRandom _rnd = null;
	private static synchronized void initRandom() {
		if(_rnd==null) {
			_rnd = new SecureRandom();
		}
	}
	private static java.util.Random getRandom() {
		if(_rnd==null) {
			initRandom();
		}
		return _rnd;
	}
	
	/**
	 * 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) {
		StringBuilder salt = new StringBuilder();

		// build a random 8 chars salt
		while (salt.length() < 8) {
			int index = (int) (getRandom().nextFloat() * MD5Crypt.SALTCHARS.length());
			salt.append(MD5Crypt.SALTCHARS.substring(index, index + 1));
		}

		// crypt
		return MD5Crypt.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 MD5Crypt.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.
		 */

		MD5Crypt.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 */

		StringBuilder result = new StringBuilder();

		result.append(magic);
		result.append(salt);
		result.append("$");

		/**
		 * Build a 22 byte output string from the set: A-Za-z0-9./
		 */
		l = (MD5Crypt.bytes2u(finalState[0]) << 16) | (MD5Crypt.bytes2u(finalState[6]) << 8) | MD5Crypt.bytes2u(finalState[12]);
		result.append(MD5Crypt.to64(l, 4));

		l = (MD5Crypt.bytes2u(finalState[1]) << 16) | (MD5Crypt.bytes2u(finalState[7]) << 8) | MD5Crypt.bytes2u(finalState[13]);
		result.append(MD5Crypt.to64(l, 4));

		l = (MD5Crypt.bytes2u(finalState[2]) << 16) | (MD5Crypt.bytes2u(finalState[8]) << 8) | MD5Crypt.bytes2u(finalState[14]);
		result.append(MD5Crypt.to64(l, 4));

		l = (MD5Crypt.bytes2u(finalState[3]) << 16) | (MD5Crypt.bytes2u(finalState[9]) << 8) | MD5Crypt.bytes2u(finalState[15]);
		result.append(MD5Crypt.to64(l, 4));

		l = (MD5Crypt.bytes2u(finalState[4]) << 16) | (MD5Crypt.bytes2u(finalState[10]) << 8) | MD5Crypt.bytes2u(finalState[5]);
		result.append(MD5Crypt.to64(l, 4));

		l = MD5Crypt.bytes2u(finalState[11]);
		result.append(MD5Crypt.to64(l, 2));

		/* Don't leave anything around in vm they could use. */
		MD5Crypt.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(MD5Crypt.USAGE);
			} else {
				System.out.println(MD5Crypt.crypt(args[0], args[1]));
			}

		} catch (Exception ex) {
			System.err.println(ex);
		}
	}

}