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