/*
|
* 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-2013 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 org.opends.server.types.ByteSequence;
|
import org.opends.server.types.ByteString;
|
|
import java.security.MessageDigest;
|
import java.security.SecureRandom;
|
import java.security.NoSuchAlgorithmException;
|
import java.util.Arrays;
|
|
/**
|
* 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();
|
}
|
|
/**
|
* 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(ByteSequence 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(ByteSequence password, String salt)
|
throws NoSuchAlgorithmException
|
{
|
MessageDigest ctx, ctx1;
|
byte digest1[], digest[];
|
byte[] plaintextBytes = password.toByteArray();
|
|
/* 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(plaintextBytes);
|
|
/* 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(plaintextBytes);
|
ctx1.update(salt.getBytes());
|
ctx1.update(plaintextBytes);
|
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. */
|
Arrays.fill(digest1, (byte) 0);
|
|
/* Then something really weird... */
|
for (int i = password.length(); i != 0; i >>= 1)
|
{
|
if ((i & 1) != 0)
|
{
|
ctx.update(digest1[0]);
|
} else
|
{
|
ctx.update(plaintextBytes[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(plaintextBytes);
|
} else
|
{
|
ctx1.update(digest);
|
}
|
if ((i % 3) != 0)
|
{
|
ctx1.update(salt.getBytes());
|
}
|
if ((i % 7) != 0)
|
{
|
ctx1.update(plaintextBytes);
|
}
|
if ((i & 1) != 0)
|
{
|
ctx1.update(digest);
|
} else
|
{
|
ctx1.update(plaintextBytes);
|
}
|
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. */
|
Arrays.fill(digest, (byte) 0);
|
Arrays.fill(plaintextBytes, (byte) 0);
|
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(ByteString.valueOf(argv[0]),
|
argv[1]));
|
} else
|
{
|
System.out.println(BSDMD5Crypt.crypt(ByteString.valueOf(argv[0])));
|
}
|
} catch (Exception e)
|
{
|
System.err.println(e.getMessage().toString());
|
System.exit(1);
|
}
|
System.exit(0);
|
}
|
}
|