001/*
002 * CDDL HEADER START
003 *
004 * The contents of this file are subject to the terms of the
005 * Common Development and Distribution License, Version 1.0 only
006 * (the "License").  You may not use this file except in compliance
007 * with the License.
008 *
009 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
010 * or http://forgerock.org/license/CDDLv1.0.html.
011 * See the License for the specific language governing permissions
012 * and limitations under the License.
013 *
014 * When distributing Covered Code, include this CDDL HEADER in each
015 * file and include the License file at legal-notices/CDDLv1_0.txt.
016 * If applicable, add the following below this CDDL HEADER, with the
017 * fields enclosed by brackets "[]" replaced with your own identifying
018 * information:
019 *      Portions Copyright [yyyy] [name of copyright owner]
020 *
021 * CDDL HEADER END
022 *
023 *
024 * Portions Copyright 2010-2015 ForgeRock AS
025 *
026 * BSD-compatible md5 password crypt
027 * Ported to Java from C based on crypt-md5.c by Poul-Henning Kamp,
028 * which was distributed with the following notice:
029 * ----------------------------------------------------------------------------
030 * "THE BEER-WARE LICENSE" (Revision 42):
031 * <phk@login.dknet.dk> wrote this file.  As long as you retain this notice you
032 * can do whatever you want with this stuff. If we meet some day, and you think
033 * this stuff is worth it, you can buy me a beer in return.   Poul-Henning Kamp
034 * ----------------------------------------------------------------------------
035 */
036package org.opends.server.util;
037
038import org.forgerock.opendj.ldap.ByteSequence;
039import org.forgerock.opendj.ldap.ByteString;
040
041import java.security.MessageDigest;
042import java.security.SecureRandom;
043import java.security.NoSuchAlgorithmException;
044import java.util.Arrays;
045
046/**
047 * BSD MD5 Crypt algorithm, ported from C.
048 *
049 * @author ludo
050 */
051public final class BSDMD5Crypt {
052
053  private static final String magic = "$1$";
054  private static final int saltLength = 8;
055  private static final String itoa64 =
056          "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
057
058  private static String intTo64(int value, int length)
059  {
060    StringBuilder output = new StringBuilder();
061
062    while (--length >= 0)
063    {
064      output.append(itoa64.charAt(value & 0x3f));
065      value >>= 6;
066    }
067
068    return output.toString();
069  }
070
071    /**
072   * Encode the supplied password in BSD MD5 crypt form, using
073   * a random salt.
074   *
075   * @param password A password to encode.
076   *
077   * @return An encrypted string.
078   *
079   * @throws NoSuchAlgorithmException If the MD5 algorithm is not supported.
080   *
081   */
082  public static String crypt(ByteSequence password)
083          throws NoSuchAlgorithmException
084  {
085    SecureRandom randomGenerator = new SecureRandom();
086    StringBuilder salt = new StringBuilder();
087
088    /* Generate some random salt */
089    while (salt.length() < saltLength)
090    {
091      int index = (int) (randomGenerator.nextFloat() * itoa64.length());
092      salt.append(itoa64.charAt(index));
093    }
094
095    return BSDMD5Crypt.crypt(password, salt.toString());
096  }
097
098  /**
099   * Encode the supplied password in BSD MD5 crypt form, using
100   * provided salt.
101   *
102   * @param password A password to encode.
103   *
104   * @param salt A salt string of any size, of which only the first
105   * 8 bytes will be considered.
106   *
107   * @return An encrypted string.
108   *
109   * @throws NoSuchAlgorithmException If the MD5 algorithm is not supported.
110   *
111   */
112  public static String crypt(ByteSequence password, String salt)
113          throws NoSuchAlgorithmException
114  {
115    MessageDigest ctx, ctx1;
116    byte digest1[], digest[];
117    byte[] plaintextBytes = password.toByteArray();
118
119    /* First skip the magic string */
120    if (salt.startsWith(magic))
121    {
122      salt = salt.substring(magic.length());
123    }
124
125    /* Salt stops at the first $, max saltLength chars */
126    int saltEnd = salt.indexOf('$');
127    if (saltEnd != -1)
128    {
129      salt = salt.substring(0, saltEnd);
130    }
131
132    if (salt.length() > saltLength)
133    {
134      salt = salt.substring(0, saltLength);
135    }
136
137    ctx = MessageDigest.getInstance("MD5");
138
139    /* The password first, since that is what is most unknown */
140    ctx.update(plaintextBytes);
141
142    /* Then our magic string */
143    ctx.update(magic.getBytes());
144
145    /* Then the raw salt */
146    ctx.update(salt.getBytes());
147
148    /* Then just as many characters of the MD5(password,salt,password) */
149    ctx1 = MessageDigest.getInstance("MD5");
150    ctx1.update(plaintextBytes);
151    ctx1.update(salt.getBytes());
152    ctx1.update(plaintextBytes);
153    digest1 = ctx1.digest();
154
155
156    for (int pl = password.length(); pl > 0; pl -= 16)
157    {
158      ctx.update(digest1, 0, pl > 16 ? 16 : pl);
159    }
160
161    /* Don't leave anything around in vm they could use. */
162    Arrays.fill(digest1, (byte) 0);
163
164    /* Then something really weird... */
165    for (int i = password.length(); i != 0; i >>= 1)
166    {
167      if ((i & 1) != 0)
168      {
169        ctx.update(digest1[0]);
170      } else
171      {
172        ctx.update(plaintextBytes[0]);
173      }
174    }
175
176    /* Now make the output string */
177    StringBuilder output = new StringBuilder();
178    output.append(magic);
179    output.append(salt);
180    output.append("$");
181
182    digest = ctx.digest();
183
184    /*
185     * and now, just to make sure things don't run too fast
186     * On a 60 MHz Pentium this takes 34 msec, so you would
187     * need 30 seconds to build a 1000 entry dictionary...
188     */
189    for (int i = 0; i < 1000; i++)
190    {
191      ctx1 = MessageDigest.getInstance("MD5");
192
193      if ((i & 1) != 0)
194      {
195        ctx1.update(plaintextBytes);
196      } else
197      {
198        ctx1.update(digest);
199      }
200      if (i % 3 != 0)
201      {
202        ctx1.update(salt.getBytes());
203      }
204      if (i % 7 != 0)
205      {
206        ctx1.update(plaintextBytes);
207      }
208      if ((i & 1) != 0)
209      {
210        ctx1.update(digest);
211      } else
212      {
213        ctx1.update(plaintextBytes);
214      }
215      digest = ctx1.digest();
216    }
217
218    int l;
219
220    l = ((digest[0] & 0xff) << 16) | ((digest[6] & 0xff) << 8)
221            | (digest[12] & 0xff);
222    output.append(intTo64(l, 4));
223    l = ((digest[1] & 0xff) << 16) | ((digest[7] & 0xff) << 8)
224            | (digest[13] & 0xff);
225    output.append(intTo64(l, 4));
226    l = ((digest[2] & 0xff) << 16) | ((digest[8] & 0xff) << 8)
227            | (digest[14] & 0xff);
228    output.append(intTo64(l, 4));
229    l = ((digest[3] & 0xff) << 16) | ((digest[9] & 0xff) << 8)
230            | (digest[15] & 0xff);
231    output.append(intTo64(l, 4));
232    l = ((digest[4] & 0xff) << 16) | ((digest[10] & 0xff) << 8)
233            | (digest[5] & 0xff);
234    output.append(intTo64(l, 4));
235    l = digest[11] & 0xff;
236    output.append(intTo64(l, 2));
237
238    /* Don't leave anything around in vm they could use. */
239    Arrays.fill(digest, (byte) 0);
240    Arrays.fill(plaintextBytes, (byte) 0);
241    ctx = null;
242    ctx1 = null;
243
244    return output.toString();
245
246  }
247
248  /**
249   * Getter to the BSD MD5 magic string.
250   *
251   * @return the magic string for this crypt algorithm
252   */
253  public static String getMagicString()
254  {
255    return magic;
256  }
257
258  /**
259   * Main test method.
260   *
261   * @param argv The array of test arguments
262   *
263   */
264  public static void main(String argv[])
265  {
266    if (argv.length < 1 || argv.length > 2)
267    {
268      System.err.println("Usage: BSDMD5Crypt password salt");
269      System.exit(1);
270    }
271    try
272    {
273      if (argv.length == 2)
274      {
275        System.out.println(BSDMD5Crypt.crypt(ByteString.valueOfUtf8(argv[0]),
276                argv[1]));
277      } else
278      {
279        System.out.println(BSDMD5Crypt.crypt(ByteString.valueOfUtf8(argv[0])));
280      }
281    } catch (Exception e)
282    {
283      System.err.println(e.getMessage());
284      System.exit(1);
285    }
286    System.exit(0);
287  }
288}