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);
}
}
}