/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (the "License"). You may not use this file except in compliance
* with the License.
*
* You can obtain a copy of the license at
* trunk/opends/resource/legal-notices/OpenDS.LICENSE
* or https://OpenDS.dev.java.net/OpenDS.LICENSE.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at
* trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
* add the following below this CDDL HEADER, with the fields enclosed
* by brackets "[]" replaced with your own identifying information:
* Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*
*
* Copyright 2010 ForgeRock AS
*
* BSD-compatible md5 password crypt
* Ported to Java from C based on crypt-md5.c by Poul-Henning Kamp,
* which was distributed with the following notice:
* ----------------------------------------------------------------------------
* "THE BEER-WARE LICENSE" (Revision 42):
* <phk@login.dknet.dk> wrote this file. As long as you retain this notice you
* can do whatever you want with this stuff. If we meet some day, and you think
* this stuff is worth it, you can buy me a beer in return. Poul-Henning Kamp
* ----------------------------------------------------------------------------
*/
package org.opends.server.util;
import java.security.MessageDigest;
import java.security.SecureRandom;
import java.security.NoSuchAlgorithmException;
/**
* BSD MD5 Crypt algorithm, ported from C.
*
* @author ludo
*/
public final class BSDMD5Crypt {
private final static String magic = "$1$";
private final static int saltLength = 8;
private final static String itoa64 =
"./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
private static String intTo64(int value, int length)
{
StringBuilder output = new StringBuilder();
while (--length >= 0)
{
output.append(itoa64.charAt((value & 0x3f)));
value >>= 6;
}
return (output.toString());
}
/* Clear bytes, equivalent of memset */
static private void clearBytes(byte bytes[])
{
for (int i = 0; i < bytes.length; i++)
{
bytes[i] = 0;
}
}
/**
* Encode the supplied password in BSD MD5 crypt form, using
* a random salt.
*
* @param password A password to encode.
*
* @return An encrypted string.
*
* @throws NoSuchAlgorithmException If the MD5 algorithm is not supported.
*
*/
static public String crypt(String password)
throws NoSuchAlgorithmException
{
SecureRandom randomGenerator = new SecureRandom();
StringBuilder salt = new StringBuilder();
/* Generate some random salt */
while (salt.length() < saltLength)
{
int index = (int) (randomGenerator.nextFloat() * itoa64.length());
salt.append(itoa64.charAt(index));
}
return BSDMD5Crypt.crypt(password, salt.toString());
}
/**
* Encode the supplied password in BSD MD5 crypt form, using
* provided salt.
*
* @param password A password to encode.
*
* @param salt A salt string of any size, of which only the first
* 8 bytes will be considered.
*
* @return An encrypted string.
*
* @throws NoSuchAlgorithmException If the MD5 algorithm is not supported.
*
*/
static public String crypt(String password, String salt)
throws NoSuchAlgorithmException
{
MessageDigest ctx, ctx1;
byte digest1[], digest[];
/* First skip the magic string */
if (salt.startsWith(magic))
{
salt = salt.substring(magic.length());
}
/* Salt stops at the first $, max saltLength chars */
int saltEnd = salt.indexOf('$');
if (saltEnd != -1)
{
salt = salt.substring(0, saltEnd);
}
if (salt.length() > saltLength)
{
salt = salt.substring(0, saltLength);
}
ctx = MessageDigest.getInstance("MD5");
/* The password first, since that is what is most unknown */
ctx.update(password.getBytes());
/* Then our magic string */
ctx.update(magic.getBytes());
/* Then the raw salt */
ctx.update(salt.getBytes());
/* Then just as many characters of the MD5(password,salt,password) */
ctx1 = MessageDigest.getInstance("MD5");
ctx1.update(password.getBytes());
ctx1.update(salt.getBytes());
ctx1.update(password.getBytes());
digest1 = ctx1.digest();
for (int pl = password.length(); pl > 0; pl -= 16)
{
ctx.update(digest1, 0, pl > 16 ? 16 : pl);
}
/* Don't leave anything around in vm they could use. */
clearBytes(digest1);
/* Then something really weird... */
for (int i = password.length(); i != 0; i >>= 1)
{
if ((i & 1) != 0)
{
ctx.update(digest1[0]);
} else
{
ctx.update(password.getBytes()[0]);
}
}
/* Now make the output string */
StringBuilder output = new StringBuilder();
output.append(magic);
output.append(salt);
output.append("$");
digest = 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...
*/
for (int i = 0; i < 1000; i++)
{
ctx1 = MessageDigest.getInstance("MD5");
if ((i & 1) != 0)
{
ctx1.update(password.getBytes());
} else
{
ctx1.update(digest);
}
if ((i % 3) != 0)
{
ctx1.update(salt.getBytes());
}
if ((i % 7) != 0)
{
ctx1.update(password.getBytes());
}
if ((i & 1) != 0)
{
ctx1.update(digest);
} else
{
ctx1.update(password.getBytes());
}
digest = ctx1.digest();
}
int l;
l = ((digest[0] & 0xff) << 16) | ((digest[6] & 0xff) << 8)
| (digest[12] & 0xff);
output.append(intTo64(l, 4));
l = ((digest[1] & 0xff) << 16) | ((digest[7] & 0xff) << 8)
| (digest[13] & 0xff);
output.append(intTo64(l, 4));
l = ((digest[2] & 0xff) << 16) | ((digest[8] & 0xff) << 8)
| (digest[14] & 0xff);
output.append(intTo64(l, 4));
l = ((digest[3] & 0xff) << 16) | ((digest[9] & 0xff) << 8)
| (digest[15] & 0xff);
output.append(intTo64(l, 4));
l = ((digest[4] & 0xff) << 16) | ((digest[10] & 0xff) << 8)
| (digest[5] & 0xff);
output.append(intTo64(l, 4));
l = (digest[11] & 0xff);
output.append(intTo64(l, 2));
/* Don't leave anything around in vm they could use. */
clearBytes(digest);
ctx = null;
ctx1 = null;
return output.toString();
}
/**
* Getter to the BSD MD5 magic string.
*
* @return the magic string for this crypt algorithm
*/
static public String getMagicString()
{
return magic;
}
/**
* Main test method.
*
* @param argv The array of test arguments
*
*/
static public void main(String argv[])
{
if ((argv.length < 1) || (argv.length > 2))
{
System.err.println("Usage: BSDMD5Crypt password salt");
System.exit(1);
}
try
{
if (argv.length == 2)
{
System.out.println(BSDMD5Crypt.crypt(argv[0], argv[1]));
} else
{
System.out.println(BSDMD5Crypt.crypt(argv[0]));
}
} catch (Exception e)
{
System.err.println(e.getMessage().toString());
System.exit(1);
}
System.exit(0);
}
}