Package org.sonatype.security.ldap.dao.password.hash

Source Code of org.sonatype.security.ldap.dao.password.hash.MD5Crypt

/*
* Sonatype Nexus (TM) Open Source Version
* Copyright (c) 2007-2014 Sonatype, Inc.
* All rights reserved. Includes the third-party code listed at http://links.sonatype.com/products/nexus/oss/attributions.
*
* This program and the accompanying materials are made available under the terms of the Eclipse Public License Version 1.0,
* which accompanies this distribution and is available at http://www.eclipse.org/legal/epl-v10.html.
*
* Sonatype Nexus (TM) Professional Version is available from Sonatype, Inc. "Sonatype" and "Sonatype Nexus" are trademarks
* of Sonatype, Inc. Apache Maven is a trademark of the Apache Software Foundation. M2eclipse is a trademark of the
* Eclipse Foundation. All other trademarks are the property of their respective owners.
*/
/**
* FreeBSD-compatible md5-style password crypt,
* 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
* ----------------------------------------------------------------------------
*
* Retrieved from: http://www.koders.com/java/fid0A789D124DFABE0FA7F734697B6A85A141F90A76.aspx on 01/10/2012
*/

package org.sonatype.security.ldap.dao.password.hash;

import java.security.MessageDigest;
import java.security.SecureRandom;

/**
* Class containing static methods for encrypting passwords
* in FreeBSD md5 style.
*
* @author Nick Johnson <freebsd@spatula.net>
* @version 1.0
*/

public class MD5Crypt
{

  private static final String magic = "$1$";

  private static final String itoa64 = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";

  private static String cryptTo64(int value, int length) {
    StringBuilder output = new StringBuilder();

    while (--length >= 0) {
      output.append(itoa64.substring(value & 0x3f, (value & 0x3f) + 1));
      value >>= 6;
    }

    return (output.toString());
  }

  /**
   * Encrypts a password using FreeBSD-style md5-based encryption
   *
   * @param password The cleartext password to be encrypted
   * @return The encrypted password, or an empty string on error
   * @throws java.security.NoSuchAlgorithmException
   *          if java.security
   *          does not support MD5
   */
  public final String crypt(String password) throws java.security.NoSuchAlgorithmException {
    StringBuilder salt = new StringBuilder();
    SecureRandom randgen = new SecureRandom();
    while (salt.length() < 8) {
      int index = (int) (randgen.nextFloat() * itoa64.length());
      salt.append(itoa64.substring(index, index + 1));
    }
    return crypt(password, salt.toString());
  }

  /**
   * Encrypts a password using FreeBSD-style md5-based encryption
   *
   * @param password The cleartext password to be encrypted
   * @param salt     The salt used to add some entropy to the encryption
   * @return The encrypted password, or an empty string on error
   * @throws java.security.NoSuchAlgorithmException
   *          if java.security
   *          does not support MD5
   */
  public final String crypt(String password, String salt)
      throws java.security.NoSuchAlgorithmException
  {

    /* First get the salt into a proper format.  It can be no more than
         * 8 characters, and if it starts with the magic string, it should
         * be skipped.
     */

    if (salt.startsWith(magic)) {
      salt = salt.substring(magic.length());
    }

    int saltEnd = salt.indexOf('$');
    if (saltEnd != -1) {
      salt = salt.substring(0, saltEnd);
    }

    if (salt.length() > 8) {
      salt = salt.substring(0, 8);
    }

    /* now we have a properly formatted salt */

    int saltLength = salt.length();

    MessageDigest md5_1, md5_2;

    md5_1 = MessageDigest.getInstance("MD5");

        /* First we update one MD5 with the password, magic string, and salt */
    md5_1.update(password.getBytes());
    md5_1.update(magic.getBytes());
    md5_1.update(salt.getBytes());

    md5_2 = MessageDigest.getInstance("MD5");

    /* Now start a second MD5 with the password, salt, and password again */
    md5_2.update(password.getBytes());
    md5_2.update(salt.getBytes());
    md5_2.update(password.getBytes());

    byte[] md5_2_digest = md5_2.digest();

    int md5Size = md5_2_digest.length; // XXX
    int pwLength = password.length();

    /* Update the first MD5 a few times starting at the first
         * character of the second MD5 digest using the smaller
        * of the MD5 length or password length as the number of
     * bytes to use in the update.
     */
    for (int i = pwLength; i > 0; i -= md5Size) {
      md5_1.update(md5_2_digest, 0, i > md5Size ? md5Size : i);
    }

    /* the FreeBSD code does a memset to 0 on "final" (md5_2_digest) here
     * which may be a bug, since it references "final" again if the
     * conditional below is true, meaning it always is equal to 0
     */

    md5_2.reset();

    /* Again, update the first MD5 a few times, this time
      * using either 0 (see above) or the first byte of the
      * password, depending on the lowest order bit's value
     */
    byte[] pwBytes = password.getBytes();
    for (int i = pwLength; i > 0; i >>= 1) {
      if ((i & 1) == 1) {
        md5_1.update((byte) 0);
      }
      else {
        md5_1.update(pwBytes[0]);
      }
    }

    /* Set up the output string. It'll look something like
     * $1$salt$ to begin with
     */
    StringBuilder output = new StringBuilder(magic);
    output.append(salt);
    output.append("$");

    byte[] md5_1_digest = md5_1.digest();

    /* According to the original source, this bit of madness
     * is introduced to slow things down.  It also further
     * mutates the result.
     */
    byte[] saltBytes = salt.getBytes();
    for (int i = 0; i < 1000; i++) {
      md5_2.reset();
      if ((i & 1) == 1) {
        md5_2.update(pwBytes);
      }
      else {
        md5_2.update(md5_1_digest);
      }
      if (i % 3 != 0) {
        md5_2.update(saltBytes);
      }
      if (i % 7 != 0) {
        md5_2.update(pwBytes);
      }
      if ((i & 1) != 0) {
        md5_2.update(md5_1_digest);
      }
      else {
        md5_2.update(pwBytes);
      }
      md5_1_digest = md5_2.digest();
    }

    /* Reorder the bytes in the digest and convert them to base64 */
    int value;
    value = ((md5_1_digest[0] & 0xff) << 16) | ((md5_1_digest[6] & 0xff) << 8) | (md5_1_digest[12] & 0xff);
    output.append(cryptTo64(value, 4));
    value = ((md5_1_digest[1] & 0xff) << 16) | ((md5_1_digest[7] & 0xff) << 8) | (md5_1_digest[13] & 0xff);
    output.append(cryptTo64(value, 4));
    value = ((md5_1_digest[2] & 0xff) << 16) | ((md5_1_digest[8] & 0xff) << 8) | (md5_1_digest[14] & 0xff);
    output.append(cryptTo64(value, 4));
    value = ((md5_1_digest[3] & 0xff) << 16) | ((md5_1_digest[9] & 0xff) << 8) | (md5_1_digest[15] & 0xff);
    output.append(cryptTo64(value, 4));
    value = ((md5_1_digest[4] & 0xff) << 16) | ((md5_1_digest[10] & 0xff) << 8) | (md5_1_digest[5] & 0xff);
    output.append(cryptTo64(value, 4));
    value = md5_1_digest[11] & 0xff;
    output.append(cryptTo64(value, 2));

    /* Drop some hints to the GC */
    md5_1 = null;
    md5_2 = null;
    md5_1_digest = null;
    md5_2_digest = null;
    pwBytes = null;
    saltBytes = null;
    password = salt = "";
    saltLength = pwLength = 0;

    return output.toString();
  }

}
TOP

Related Classes of org.sonatype.security.ldap.dao.password.hash.MD5Crypt

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.